~8 minutes

ActiveModel: Model

I started reading ahead into ActiveRecord…whoa…that’s some crazy stuff right there. Mind was blown with the sprawling nature of it…so many avenues and byways of logic, it’s going to take some serious time to get to the bottom of that. In the mean time, let’s jump into ActiveModel in earnest.

Getting Started with ActiveModel::Model

ActiveModel has a lot of things going on too, but it’s easy to see how they build on one another, so let’s start with ActiveModel::Model

class Coffee
  include ActiveModel::Model
  attr_accessor :roast, :brand

coffee = Coffee.new(roast: "French Roast", brand: "Slate")
coffee.brand # => "Slate"
coffee.roast # => "French Roast"

Really easy. But, how does it do this and what other features do we get via this one include? Here is the source for model.rb:

module Model
  def self.included(base)
    base.class_eval do
      extend  ActiveModel::Naming
      extend  ActiveModel::Translation
      include ActiveModel::Validations
      include ActiveModel::Conversion

  def initialize(params={})
    params.each do |attr, value|
      self.public_send("#{attr}=", value)
    end if params

  def persisted?


Looking at the code above should make it super easy to see how the Coffee.new works: we iterate over the implied hash of attributes and call public_send on the setter method provided via attr_accessor.

Invokes the method identified by symbol, passing it any arguments specified. Unlike send, public_send calls public methods only.

Straightforward…moving on.


This module gives our class some methods which are very handy for routing and the naming of database tables, stuff like route_key and singular_route_key, all through the magic of ActiveSupport’s Inflector. Take a peek:

  def initialize(klass, namespace = nil, name = nil)
    @name = name || klass.name

    raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if @name.blank?

    @unnamespaced = @name.sub(/^#{namespace.name}::/, '') if namespace
    @klass        = klass
    @singular     = _singularize(@name)
    @plural       = ActiveSupport::Inflector.pluralize(@singular)
    @element      = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(@name))
    @human        = ActiveSupport::Inflector.humanize(@element)
    @collection   = ActiveSupport::Inflector.tableize(@name)
    @param_key    = (namespace ? _singularize(@unnamespaced) : @singular)
    @i18n_key     = @name.underscore.to_sym

    @route_key          = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural.dup)
    @singular_route_key = ActiveSupport::Inflector.singularize(@route_key)
    @route_key << "_index" if @plural == @singular

So for instance:

Coffee.model_name.route_key # => "coffees"
Coffee.model_name.singular_route_key # => "coffee"
Coffee.model_name.param_key # => "coffee"
Coffee.model_name.collection # => "coffees"
Coffee.model_name.human # => "Coffee"



This is a minor interface for Rails’ i18n implementation that basically leverages Naming for the purpose of translating your object’s name into other localities. It has some savviness for scopes and ancestors, but I’m going to skip it.


Very small piece of code that handles some to_param and to_model methods. Skipping.


You already know what this is going to provide:

class Coffee
  include ActiveModel::Model
  attr_accessor :roast, :brand
  validate do |coffee|
    errors.add(:roast, 'must be french') unless roast =~ /french/i
    errors.add(:brand, 'must not be charbucks') if brand =~ /charbucks/i

coffee = Coffee.new(roast: "French Roast", brand: "Slate")
coffee.brand # => "Slate"
coffee.roast # => "French Roast"
coffee.valid? # => true
coffee.errors.messages # => {}

coffee = Coffee.new(roast: "French Roast", brand: "Charbucks")
coffee.brand # => "Charbucks"
coffee.roast # => "French Roast"
coffee.valid? # => false
coffee.errors.messages # => {:brand=>["must not be charbucks"]}

So how does it work? Well, ActiveModel::Validations stripped down to the basics looks like:

module Validations
  extend ActiveSupport::Concern

  included do
    extend ActiveModel::Callbacks
    extend ActiveModel::Translation

    extend  HelperMethods
    include HelperMethods

    attr_accessor :validation_context
    define_callbacks :validate, scope: :name

    class_attribute :_validators
    self._validators = Hash.new { |h,k| h[k] = [] }

  module ClassMethods
    def validates_each(*attr_names, &block)
      validates_with BlockValidator, _merge_attributes(attr_names), &block

    def validate(*args, &block)
      options = args.extract_options!
      if options.key?(:on)
        options = options.dup
        options[:if] = Array(options[:if])
        options[:if].unshift("validation_context == :#{options[:on]}")
      args << options
      set_callback(:validate, *args, &block)

  def errors
    @errors ||= Errors.new(self)

  def valid?(context = nil)
    current_context, self.validation_context = validation_context, context
    self.validation_context = current_context


    def run_validations!
      run_callbacks :validate

Of course, there is a whole directory of specific validations and additional logic underneath this one, but for now let’s focus on the bones.

When we include Validations, it pulls in ActiveModel::Callbacks and defines a callback for validation. It also creates a nifty _validators hash with a specific constructor block. If you haven’t used an initialization block on your Hashes yet, check it out:

foo = Hash.new { |h,k| h[k] = [] } # => {}
foo[:a] # => []

bar = Hash.new # => {}
bar[:a] # => nil

Ok, so we have a callback added to our model as well as a hash keyed on model attribute with an array of validators. Callbacks are very complicated (and very cool), but we can take a glimpse via knowing how define_callbacks works:

coffee.send('_validate_callbacks') # => [#<ActiveSupport::Callbacks::Callback:0x007fbc75e13f00 @klass=Coffee, @kind=:before, @chain=[...], @options={:if=>[], :unless=>[]}, @raw_filter=#<Proc:0x007fbc75e0c138@-:5>, @_is_object_filter=false, @filter="_callback_before_1(self)", @compiled_options="true">]

Looks like our model now has a before callback with a Proc being called. I’ll have to do a follow up post on the magic of Callbacks, but for now I’m sure you can guess what code is inside (our validation code).

So when do our validations get run? From the above we can see run_callbacks manually triggered whenever we call valid? but what wasn’t as obvious to me is that run_callbacks :validate is what triggers the callback procs generated via set_callback in validate(*args,&block).

Linearly then:

  • we include ActiveModel::Model which includes ActiveModel::Validations
  • we specify a validates in our model which gets translated into a set_callback with the right trigger conditions (before, after, context)
  • valid? triggers the execution of all scoped callbacks in the right call order (fancy chains were made)
  • an ActiveModel::Errors object is populated which then provides the errors and messages as necessary

For curiousity’s sake, what runs the callbacks? This does!

    def run_callbacks(kind, &block)
      cbs = send("_#{kind}_callbacks")
      if cbs.empty?
        yield if block_given?
        runner = cbs.compile
        e = Filters::Environment.new(self, false, nil, block)

Whoa, looks cool. Next time, we will dig into it…I know, ActiveSupport again, but it’s just so magical I can’t resist!

Have fun reading the source!

Tagged with: programming

My first novel is coming soon-ish!

Check out Singular