This project is archived and is in readonly mode.
Object#respond_to? and Object#inspect raise exception after allocate
Reported by Jean-Baptiste Escoyez | June 24th, 2010 @ 04:28 PM
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?
Comments and changes to this ticket
-
Jeff Kreeftmeijer November 1st, 2010 @ 05:06 PM
- Tag cleared.
- Importance changed from to Low
Automatic cleanup of spam.
-
Santiago Pastorino February 2nd, 2011 @ 04:20 PM
- 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.
-
Santiago Pastorino February 2nd, 2011 @ 04:20 PM
- State changed from open to stale
-
dvdplm February 7th, 2011 @ 10:48 AM
- 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) && string =~ /^---/ YAML::load(string) rescue string end</code>
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?("@attributes") end # Returns an array of names for the attributes available on this object sorted alphabetically. def attribute_names # @attributes.keys.sort instance_variable_defined?("@attributes") ? @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
-
Jean-Baptiste Escoyez February 7th, 2011 @ 08:19 PM
@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
Create your profile
Help contribute to this project by taking a few moments to create your personal profile. Create your profile »
<h2 style="font-size: 14px">Tickets have moved to Github</h2>
The new ticket tracker is available at <a href="https://github.com/rails/rails/issues">https://github.com/rails/rails/issues</a>