Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

Object#respond_to? and Object#inspect raise exception after allocate

#4957

After creating an ActiveRecord object with Object#allocate, calling Object#respond_to? will raise the following error:

Class: <NoMethodError>
Message: <"undefined method `keys' for nil:NilClass">
---Backtrace---
/Users/jbescoyez/Code/rails/activerecord/lib/active_record/base.rb:1495:in `attribute_names'
/Users/jbescoyez/Code/rails/activerecord/lib/active_record/base.rb:1552:in `attributes'
/Users/jbescoyez/Code/rails/activerecord/lib/active_record/attribute_methods.rb:57:in `attribute_method?'
/Users/jbescoyez/Code/rails/activemodel/lib/active_model/attribute_methods.rb:394:in `match_attribute_method?'
/Users/jbescoyez/Code/rails/activemodel/lib/active_model/attribute_methods.rb:393:in `each'
/Users/jbescoyez/Code/rails/activemodel/lib/active_model/attribute_methods.rb:393:in `match_attribute_method?'
/Users/jbescoyez/Code/rails/activemodel/lib/active_model/attribute_methods.rb:378:in `respond_to?'
/Users/jbescoyez/Code/rails/activerecord/lib/active_record/attribute_methods.rb:52:in `respond_to?'
---------------

and Object#inspect will raise:

Class: <NoMethodError>
Message: <"undefined method `has_key?' for nil:NilClass">
---Backtrace---
/Users/jbescoyez/Code/rails/activerecord/lib/active_record/base.rb:1490:in `has_attribute?'
/Users/jbescoyez/Code/rails/activerecord/lib/active_record/base.rb:1642:in `inspect'
/Users/jbescoyez/Code/rails/activerecord/lib/active_record/base.rb:1641:in `collect'
/Users/jbescoyez/Code/rails/activerecord/lib/active_record/base.rb:1641:in `inspect'
---------------

What's happening here?

Since Object#allocate does not call initialize, @attributes is not defined for the object. However, Object#respond_to? and Object#inspect are overridden in ActiveRecord and rely on this instance variable.

When is it critical?

Some C libraries like "Syck", the YAML parser, use respond_to? in combination with allocate.

I.e. to load a serialized ActiveRecord object:

YAML.load(YAML.dump(Topic.new))

It will raise an exception when loading the serialized object.

Also, the console will call inspect after the call to allocate which will raise an exception too

The solution I propose

I've created a patch for this bug which will ensure that the @attributes instance variable is defined before calling methods on it.

Where I'm not sure

While running the tests, I noticed several warnings was thrown since other variables like @new_record was neither defined. Hence, I added a precondition verifying it is defined before calling it. Am I right to do that?

Reported by Jean-Baptiste Escoyez · June 24th, 2010 @ 04:28 PM

State: stale
Milestone: none
Assigned to: nobody
Importance: Low

Activity

  1. Magnus Bergmark
    Magnus Bergmark

    I'm having this problem too.

    October 25th, 2010 @ 09:08 AM

  2. Jeff Kreeftmeijer
    Jeff Kreeftmeijer
    • Tag cleared.
    • Importance changed from to Low

    Automatic cleanup of spam.

    November 1st, 2010 @ 05:06 PM

  3. Santiago Pastorino
    Santiago Pastorino
    • State changed from new to open

    This issue has been automatically marked as stale because it has not been commented on for at least three months.

    The resources of the Rails core team are limited, and so we are asking for your help. If you can still reproduce this error on the 3-0-stable branch or on master, please reply with all of the information you have about it and add "[state:open]" to your comment. This will reopen the ticket for review. Likewise, if you feel that this is a very important feature for Rails to include, please reply with your explanation so we can consider it.

    Thank you for all your contributions, and we hope you will understand this step to focus our efforts where they are most helpful.

    February 2nd, 2011 @ 04:20 PM

  4. Santiago Pastorino
    Santiago Pastorino
    • State changed from open to stale

    February 2nd, 2011 @ 04:20 PM

  5. dvdplm
    dvdplm
    • Tag set to 3.0.3, active_record, attributes, persistence, serialization, yaml

    I can reproduce this. Serialization of objects that rely on #initialize -- particularly AR objects -- fails in AR::Base#object_from_yaml:

          def object_from_yaml(string)

        return string unless string.is_a?(String) &amp;&amp; string =~ /^---/
        YAML::load(string) rescue string
      end</code>
    
    
    
    
    ...and we get the raw string back.

    Applying the patch attached here (manually, as it doesn't apply as is) fixes the issue completely as far as I can tell (base.rb, starting at line 1505):

          # Returns true if the given attribute is in the attributes hash

      def has_attribute?(attr_name)
        # @attributes.has_key?(attr_name.to_s)
        @attributes.has_key?(attr_name.to_s) if instance_variable_defined?(&quot;@attributes&quot;)
      end
    
      # Returns an array of names for the attributes available on this object sorted alphabetically.
      def attribute_names
        # @attributes.keys.sort
        instance_variable_defined?(&quot;@attributes&quot;) ? @attributes.keys.sort : []
      end</code>
    
    
    
    

    To work around this we tried the wrapping approach suggested by MarianTheisen here: https://rails.lighthouseapp.com/projects/8994/tickets/5525-after-up... to no avail. I actually suspect that bug is only partially fixed and that fixing this one would be the definitive fix for the other as well.

    Attaching a barebones rails 3.0.3 app (two models, Beef and Bone; Beef has a serialized column where we want to stick an instance of Bone) where the following console session will demonstrate the breakage:

    >> Beef
      SQL (0.6ms)  SHOW TABLES
    => Beef(id: integer, name: string, details: text, created_at: datetime, updated_at: datetime)
    >> b = Beef.new(name: 'Rosa', details: [Bone.first])
      Bone Load (0.4ms)  SELECT `bones`.* FROM `bones` LIMIT 1
    => #<Beef id: nil, name: "Rosa", details: [#<Bone id: 1, type: nil, size: 10, created_at: "2011-02-07 09:47:47", updated_at: "2011-02-07 09:47:47">], created_at: nil, updated_at: nil>
    >> b.details
    => [#<Bone id: 1, type: nil, size: 10, created_at: "2011-02-07 09:47:47", updated_at: "2011-02-07 09:47:47">]
    >> b.save!
      SQL (0.1ms)  BEGIN
      SQL (0.9ms)  describe `beefs`
      AREL (0.2ms)  INSERT INTO `beefs` (`name`, `details`, `created_at`, `updated_at`) VALUES ('Rosa', '--- \n- !ruby/object:Bone \n attributes: \n id: 1\n type: \n size: 10\n created_at: 2011-02-07 09:47:47 Z\n updated_at: 2011-02-07 09:47:47 Z\n attributes_cache: {}\n\n changed_attributes: {}\n\n destroyed: false\n marked_for_destruction: false\n persisted: true\n previously_changed: {}\n\n readonly: false\n', '2011-02-07 09:48:39', '2011-02-07 09:48:39')
      SQL (0.1ms)  COMMIT
    => true
    >> b.details
    => [#<Bone id: 1, type: nil, size: 10, created_at: "2011-02-07 09:47:47", updated_at: "2011-02-07 09:47:47">]
    >> b.reload.details
      Beef Load (0.3ms)  SELECT `beefs`.* FROM `beefs` WHERE (`beefs`.`id` = 1) LIMIT 1
    => "--- \n- !ruby/object:Bone \n  attributes: \n    id: 1\n    type: \n    size: 10\n    created_at: 2011-02-07 09:47:47 Z\n    updated_at: 2011-02-07 09:47:47 Z\n  attributes_cache: {}\n\n  changed_attributes: {}\n\n  destroyed: false\n  marked_for_destruction: false\n  persisted: true\n  previously_changed: {}\n\n  readonly: false\n"
    

    Please don't hesitate to contact me for further details or information. We would really like to see this bug fixed for 3.0.4

    Thank you so much for all your hard work.

    David

    February 7th, 2011 @ 10:48 AM

  6. Jean-Baptiste Escoyez
    Jean-Baptiste Escoyez

    @dvdplm Thank you for the follow up. I agree with you that this bug has to be fixed. It is not normal that ActiveRecord breaks Ruby core API. I will update the patch by the end of the week.

    JB

    February 7th, 2011 @ 08:19 PM