Search code examples
rubyruby-on-rails-3activemodel

ActiveModel fields not mapped to accessors


Using Rails 3 and ActiveModel, I am unable to use the self. syntax to get the value of an attribute inside an ActiveModel based object.

In the following code, in save method, self.first_name evaluates to nil where @attributes[:first_name] evaluates to 'Firstname' (the value passed in from the controller when initializing the object).

In ActiveRecord this seems to work, but when building the same class in ActiveModel, it does not. How do you refer to a field using accessors in an ActiveModel based class?

class Card
  include ActiveModel::Validations
  extend ActiveModel::Naming 
  include ActiveModel::Conversion
  include ActiveModel::Serialization
  include ActiveModel::Serializers::Xml

  validates_presence_of :first_name

  def initialize(attributes = {})
    @attributes = attributes
  end

  #DWT TODO we need to make sure that the attributes initialize the accessors properyl, and in the same way they would if this was ActiveRecord
  attr_accessor :attributes, :first_name

  def read_attribute_for_validation(key)
    @attributes[key]
  end

  #save to the web service
  def save
    Rails.logger.info "self vs attribute:\n\t#{self.first_name}\t#{@attributes["first_name"]}"
  end

  ...

end

Solution

  • I figured it out. The "hack" that I mentioned as a comment to Marian's answer actually turns out to be exactly how the accessors for ActiveRecord classes are generated. Here's what I did:

    class MyModel
      include ActiveModel::AttributeMethods
    
      attribute_method_suffix  "="  # attr_writers
      attribute_method_suffix  ""   # attr_readers
    
      define_attribute_methods [:foo, :bar]
    
      # ActiveModel expects attributes to be stored in @attributes as a hash
      attr_reader :attributes
    
      private
    
      # simulate attribute writers from method_missing
      def attribute=(attr, value)
        @attributes[attr] = value
      end
    
      # simulate attribute readers from method_missing
      def attribute(attr)
        @attributes[attr]
      end
    end
    

    You can see the same thing if you look in ActiveRecord's source code (lib/active_record/attribute_methods/{read,write}.rb).