This project is archived and is in readonly mode.

#3165 open
Łukasz Bandzarewicz

ActiveRecord::MissingAttributeError after update to rails v 2.3.4

Reported by Łukasz Bandzarewicz | September 8th, 2009 @ 03:23 PM

After update Rails to 2.3.4 version I get following exception:

  1) Error:
test_tagging(BacklogItemTest):
ActiveRecord::MissingAttributeError: missing attribute: domain_id
    app/models/mixins/domain_checks.rb:54:in `ensure_same_domain'
    app/models/mixins/domain_checks.rb:35:in `after_find'
    vendor/rails/activerecord/lib/active_record/callbacks.rb:347:in `send'
    vendor/rails/activerecord/lib/active_record/callbacks.rb:347:in `callback'
    vendor/rails/activerecord/lib/active_record/base.rb:1653:in `send'
    vendor/rails/activerecord/lib/active_record/base.rb:1653:in `instantiate'
    vendor/rails/activerecord/lib/active_record/base.rb:661:in `find_by_sql'
    vendor/rails/activerecord/lib/active_record/base.rb:661:in `collect!'
    vendor/rails/activerecord/lib/active_record/base.rb:661:in `find_by_sql'
    vendor/rails/activerecord/lib/active_record/base.rb:1548:in `find_every'
    vendor/rails/activerecord/lib/active_record/base.rb:1505:in `find_initial'
    vendor/rails/activerecord/lib/active_record/base.rb:692:in `exists?'
    vendor/rails/activerecord/lib/active_record/associations/association_collection.rb:380:in `send'
    vendor/rails/activerecord/lib/active_record/associations/association_collection.rb:380:in `method_missing'
    vendor/rails/activerecord/lib/active_record/base.rb:2143:in `with_scope'
    vendor/rails/activerecord/lib/active_record/associations/association_proxy.rb:206:in `send'
    vendor/rails/activerecord/lib/active_record/associations/association_proxy.rb:206:in `with_scope'
    vendor/rails/activerecord/lib/active_record/associations/association_collection.rb:376:in `method_missing'
    vendor/rails/activerecord/lib/active_record/associations/association_collection.rb:336:in `include?'
    test/unit/backlog_item_test.rb:159:in `test_tagging'
    vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:62:in `__send__'
    vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:62:in `run

Error occurs when in test I'm doing something like that:

item.tags.include? tags(:banana)

..it generates following sql query:

Tag Load (0.2ms)   SELECT tags.id FROM tags INNER JOIN backlog_item_tags ON tags.id = backlog_item_tags.tag_id WHERE (tags.id = 59467727) AND ((backlog_item_tags.backlog_item_id = 1737950289)) ORDER BY name ASC LIMIT 1

Comments and changes to this ticket

  • Adam Byrtek

    Adam Byrtek September 9th, 2009 @ 09:37 AM

    The SQL query is fine, id is enough to do include?. The problem is that models are instantiated having only id, without other properties and after_find assumes fully instantiated models.

  • Adam Byrtek

    Adam Byrtek September 9th, 2009 @ 10:06 AM

    • Tag changed from 2.3.4, activerecord, exception to 2.3.4, activerecord, exception, patch

    Trivial patch attached, do "SELECT *" instead of "SELECT id". This is done only on a single row (LIMIT 1) so there is no need for micro-optimization.

  • Jorge Dias

    Jorge Dias September 10th, 2009 @ 12:15 PM

    I was having the same error, applied the patch and now it works

  • Marek Kowalski

    Marek Kowalski September 17th, 2009 @ 04:34 PM

    I spend some time debugging this issue and the problem is with the ActiveRecord::Base.exists? method. In Rails 2.3.2 it looked like this:

         def exists?(id_or_conditions = {})
            connection.select_all(
              construct_finder_sql(
                :select     => "#{quoted_table_name}.#{primary_key}",
                :conditions => expand_id_conditions(id_or_conditions),
                :limit      => 1
              ),
              "#{name} Exists"
            ).size > 0
          end
    

    Method above doesn't instantiate the record nor does it call the after_find filter chain.

    In Rails 2.3.4 exists? method calls find_initial with :select => "#{table}.#{primary_key}". This causes record to be instantiated and after_find filter called. However the only attribute loaded is 'id', so calling any other method inside the filter will cause the ArgumentError.

    IMO after_find filter should be called but with all the columns loaded

  • Tim Connor

    Tim Connor September 26th, 2009 @ 08:16 PM

    This also pops up if you try to access any attributes in an after_initialize, which almost makes it seem like a regression from about 2007, or so. Not sure that the patch will fix that part of the bug.

  • kbrock

    kbrock October 2nd, 2009 @ 09:08 AM

    This patch should fix after_initialize

    But I still like the previous way of testing exists.

    It did not pull back fields, it did not instantiate an object. It just pulled back the id.

    I'd go back to the previous code. And better yet, Id tweak the select clause to optimize even more (do :select => 1)
    That way it could possibly only use the index and not even touch the data page depending upon the exists clause.

    suggestion:

          def exists?(id_or_conditions = {})
            connection.select_all(
              construct_finder_sql(
                :select     => 1,
                :conditions => expand_id_conditions(id_or_conditions),
                :limit      => 1
              ),
              "#{name} Exists"
            ).size > 0
          end
    

    Thanks for writing this up

  • Adam Byrtek

    Adam Byrtek October 2nd, 2009 @ 07:32 PM

    One way or another it would be nice to have this resolved by a Rails commiter.

  • Kieran P

    Kieran P October 8th, 2009 @ 01:38 AM

    +1 This problem has caused some issues in our application. The fixes above don't help though.

    @@@ 1) Error: test: The topic related image slideshow when several images are related to a topic, the slideshow should be populated in the session on selected image visit. (TopicsControllerTest):
    ActiveRecord::MissingAttributeError: missing attribute: basket_id

    vendor/rails/activerecord/lib/active_record/association_preload.rb:309:in `send'
    vendor/rails/activerecord/lib/active_record/association_preload.rb:309:in `preload_belongs_to_association'
    vendor/rails/activerecord/lib/active_record/association_preload.rb:308:in `each'
    vendor/rails/activerecord/lib/active_record/association_preload.rb:308:in `preload_belongs_to_association'
    vendor/rails/activerecord/lib/active_record/association_preload.rb:120:in `send'
    vendor/rails/activerecord/lib/active_record/association_preload.rb:120:in `preload_one_association'
    vendor/rails/activesupport/lib/active_support/ordered_hash.rb:97:in `each'
    vendor/rails/activesupport/lib/active_support/ordered_hash.rb:97:in `each'
    vendor/rails/activerecord/lib/active_record/association_preload.rb:114:in `preload_one_association'
    vendor/rails/activerecord/lib/active_record/association_preload.rb:91:in `preload_associations'
    vendor/rails/activerecord/lib/active_record/association_preload.rb:90:in `preload_associations'
    vendor/rails/activerecord/lib/active_record/association_preload.rb:90:in `each'
    vendor/rails/activerecord/lib/active_record/association_preload.rb:90:in `preload_associations'
    vendor/rails/activerecord/lib/active_record/base.rb:1549:in `find_every'
    vendor/rails/activerecord/lib/active_record/base.rb:615:in `find'
    vendor/rails/activerecord/lib/active_record/associations/association_collection.rb:60:in `find'
    lib/image_slideshow.rb:194:in `find_related_images'
    lib/image_slideshow.rb:139:in `populate_slideshow'
    lib/image_slideshow.rb:119:in `prepare_slideshow'
    vendor/rails/activesupport/lib/active_support/callbacks.rb:178:in `send'
    vendor/rails/activesupport/lib/active_support/callbacks.rb:178:in `evaluate_method'
    vendor/rails/activesupport/lib/active_support/callbacks.rb:166:in `call'
    vendor/rails/actionpack/lib/action_controller/filters.rb:225:in `call'
    vendor/rails/actionpack/lib/action_controller/filters.rb:629:in `run_before_filters'
    vendor/rails/actionpack/lib/action_controller/filters.rb:615:in `call_filters'
    vendor/rails/actionpack/lib/action_controller/filters.rb:610:in `perform_action_without_benchmark'
    vendor/rails/actionpack/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue'
    vendor/rails/activesupport/lib/active_support/core_ext/benchmark.rb:17:in `ms'
    /opt/ruby-enterprise-1.8.7-20090928/lib/ruby/1.8/benchmark.rb:308:in `realtime'
    vendor/rails/activesupport/lib/active_support/core_ext/benchmark.rb:17:in `ms'
    vendor/rails/actionpack/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue'
    vendor/rails/actionpack/lib/action_controller/rescue.rb:160:in `perform_action_without_flash'
    vendor/rails/actionpack/lib/action_controller/flash.rb:146:in `perform_action'
    vendor/rails/actionpack/lib/action_controller/base.rb:532:in `send'
    vendor/rails/actionpack/lib/action_controller/base.rb:532:in `process_without_filters'
    vendor/rails/actionpack/lib/action_controller/filters.rb:606:in `process'
    vendor/rails/actionpack/lib/action_controller/test_process.rb:567:in `process_with_test'
    vendor/rails/actionpack/lib/action_controller/test_process.rb:447:in `process'
    vendor/rails/actionpack/lib/action_controller/test_process.rb:398:in `get'
    lib/image_slideshow_test_helper.rb:22:in `__bind_1254962019_963356'
    /opt/ruby-enterprise-1.8.7-20090928/lib/ruby/gems/1.8/gems/thoughtbot-shoulda-2.10.2/lib/shoulda/context.rb:351:in `call'
    /opt/ruby-enterprise-1.8.7-20090928/lib/ruby/gems/1.8/gems/thoughtbot-shoulda-2.10.2/lib/shoulda/context.rb:351:in `test: The topic related image slideshow when several images are related to a topic, the slideshow should be populated in the session on selected image visit. '
    vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:62:in `__send__'
    vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:62:in `run'
    
    
    
  • Tim Connor

    Tim Connor October 19th, 2009 @ 09:34 PM

    I wonder what we have to do to get this in the queue for 2.3.5?

  • Randy Souza

    Randy Souza October 21st, 2009 @ 01:58 PM

    +1 -- bitten by this same problem.

  • Matt Jones

    Matt Jones October 21st, 2009 @ 10:37 PM

    • Assigned user set to “Michael Koziarski”

    This is being caused by the fix introduced in #2543, which switched to using find_initial to properly account for scoping applied to the association. Assigning to Koz, as he handled the original ticket.

  • Caius

    Caius October 22nd, 2009 @ 03:08 PM

    This also pops up if you try to access any attributes in an after_initialize

    +1 - I'm running into this trying to set default values in after_initialise.

  • Rob Olson

    Rob Olson October 24th, 2009 @ 09:16 AM

    The issue is that find_by_sql is

    This bug bit me as well. Instead of changing the select query from 'id' to '*', I believe the correct fix is to not instantiate the ActiveRecord object for calls to #exists?. In the ticket that created this bug, #2543, Peter Marklund proposed adding a :instantiate = false option to find_every that would tell find_by_sql to not instantiate the records.

    I have attached a patch with a test which demonstrates this bug by attempting to access a database attribute in an after_initialize. As a solution, I've implemented Peter Marklund's idea for a skip instantiation option. In addition to resolving this issue, this patch is beneficial because it improves the performance of calls to #exists?. As Koz stated in the previous ticket, this saves AR from firing the relevant callbacks for the model.

    Any feedback is welcome.

  • Marek Kowalski

    Marek Kowalski October 24th, 2009 @ 12:40 PM

    1. In my opinion #exists? should instantiate the object. At least I would expect the after_find filter to be fired.
  • Rob Olson

    Rob Olson October 25th, 2009 @ 04:18 AM

    Marek,

    Why would you expect the after_find filter to be fired for exists? As you mentioned earlier, that was not the behavior in Rails 2.3.2.

    I think of #exists? as the equivalent of executing a SQL count (even though it is not for performance reasons) and intuitively would not expect it to call after_find.

  • Marek Kowalski

    Marek Kowalski October 25th, 2009 @ 09:54 AM

    Rob,

    In fact in Rails 2.3.2 the after_find filter was not fired but I found out about it just when I was debugging the issue with missing attributes. Why I think it should be fired? Well, we should consider for what purpose people use the after_find filter. In the application I work on we use it as the last security fence - it makes sure that the newly instantiated object belongs to the scope of interest of the current user. So not firing this filter when using #exists? theoretically opens the way for the true-negative effect: checking exists? returns true, because the record with given ID is present, however attempt to load the object will fail. Of course we have other security algorithms to double check it, but maybe some other people don't. Or maybe I'm just talking non sense... So for what purpose do you use the after_find filter ?

    Best,
    Marek

  • Marek Kowalski

    Marek Kowalski October 25th, 2009 @ 10:02 AM

    Rob,

    In fact in Rails 2.3.2 the after_find filter was not fired but I found out about it just when I was debugging the issue with missing attributes. Why I think it should be fired? Well, we should consider for what purpose people use the after_find filter. In the application I work on we use it as the last security fence - it makes sure that the newly instantiated object belongs to the scope of interest of the current user. So not firing this filter when using #exists? theoretically opens the way for the true-negative effect: checking exists? returns true, because the record with given ID is present, however attempt to load the object will fail. Of course we have other security algorithms to double check it, but maybe some other people don't. Or maybe I'm just talking non sense... So for what purpose do you use the after_find filter ?

    Best,
    Marek

  • Rob Olson

    Rob Olson October 26th, 2009 @ 03:26 AM

    Marek, thank you for sharing how you have employed after_find in your application. I do not know all of the details of your application but I typically handle that situation differently.

    One common way I ensure that the object I am finding belongs to the scope of interest of the current user is to do the find on an association proxy. Koz covered this in a blog post on therailsway.com.

    In that case the find will return nil which then tells me that the record does not exist. With this technique I can accomplish the same thing in 1 sql query that you are doing with 2.

    To be honest I have not found a good use for the after_find filter yet. However, since I did not know that it was available until recently, I would not have thought to use it before.

  • Marek Kowalski

    Marek Kowalski October 26th, 2009 @ 10:09 AM

    Rob, of course you are right, this is sensible approach. But you also have to take into consideration that some lame developer could forget about accessing the object through the association, just like: Model.find(params[:id]). This would be a major security breach, but after_find filter comes to the rescue. Rails code is not very idiot-resistant, so if you are working in the team where people come and go you should better optimize the security rather than the number of SQL queries.

  • Tom Lea

    Tom Lea October 26th, 2009 @ 03:20 PM

    @Rob Olson: Just a note, your failing test does not fail. after_initialize is not quite like other callbacks, it's slipped unless defined as an actual method (for performance reasons).

  • Rob Olson

    Rob Olson October 28th, 2009 @ 06:52 AM

    @Tom Lea: I have been unable to recreate after_initialize not running. On my machines it runs (and results in an error) the way I have specified it. Any ideas?

    Also, I realize that the test results in an error instead of a failure. To receive a failure instead the test could be written like this:

    def test_exists_on_model_with_after_initialize_method_should_not_blow_up
      assert_nothing_raised { assert Entrant.exists? }
    end
    
  • Rob Olson

    Rob Olson November 10th, 2009 @ 03:37 AM

    As Tom Lea pointed out, in the 2-3-stable branch after_initialize must be declared as an actual method. It was working fine for me before but after updating Rails passing a block to after_initialize stopped working.

    I've adjusted the patch for 2-3-stable. The old patch file (above) still works for Rails 3.0 and is preferred for the master branch since "def after_initialize" gives a deprecation warning in Rails 3.

  • Mark Dodwell

    Mark Dodwell December 6th, 2009 @ 01:16 AM

    I ran into this same bug, and side-stepped it by changing the way I was setting my attribute in #after_initialize:

    def after_initialize
      self.token ||= "foobar"
    end
    

    becomes:

    def after_initialize
      write_attribute(:token, "foobar") unless read_attribute(:token)
    end
    

    and that made the ActiveRecord::MissingAttributeError error go away. Might be of use to somebody?

  • Frederic Jean

    Frederic Jean December 24th, 2009 @ 06:38 PM

    • Tag changed from 2.3.4, activerecord, exception, patch to 2.3.4, 2.3.5, activerecord, exception, patch
  • CDD Developers

    CDD Developers February 19th, 2010 @ 06:52 AM

    Rob's patch works well on 2.3.5 for our production server. It would be nice if this could be committed for 2.3.6

  • Tim Connor

    Tim Connor May 23rd, 2010 @ 10:37 PM

    Did this NOT make it into 2.3.6?

  • Nikos Dimitrakopoulos

    Nikos Dimitrakopoulos June 8th, 2010 @ 08:34 PM

    And nor in 2.3.8... Could a committer please spend some integrating Rob's patch?

    Thanks

  • Yuri

    Yuri July 9th, 2010 @ 04:11 PM

    • Importance changed from “” to “”

    Still broken in 2.3.8. Annoying.

  • Geoffroy

    Geoffroy July 28th, 2010 @ 11:13 AM

    Still seems unresolved in 3.0.0rc

    the solution from Mark Dodwell works though

    def after_initialize
    write_attribute(:token, "foobar") unless read_attribute(:token) end

    maybe worth to include in the final?

  • Ernie Miller

    Ernie Miller July 28th, 2010 @ 09:28 PM

    • Tag changed from 2.3.4, 2.3.5, activerecord, exception, patch to 2.3.4, 2.3.5, 3.0.0.rc, activerecord, exception, patch

    Yup, just ran into this in 3.0.0rc as well. Only happens when doing a query that includes associations with corresponding conditions, triggering the old join code.

  • Brian Artiaco

    Brian Artiaco September 2nd, 2010 @ 07:07 PM

    I just spent half a day on this, before I finally found this ticket. While I'm sad that it's been almost exactly a year since this was reported, and it hasn't been fixed yet, here's my simple work around for my scenario:

    validates_uniqueness_of :email, :scope => :library_id
    
    def after_initialize
      self.status ||= "Invited"
    end
    

    As explained above, this will cause a 'MissingAttributeError' to be thrown, if there are records returned by the validates_uniqueness_of query. My simple solution is this:

    def after_initialize
      self.status ||= "Invited" if new_record?
    end
    

    While other people are having more complex issues, this should solve the simple case, until an actual solution is commited into rails.

    (Also added 2.3.8 to the list of tags, as that is what I'm working against, in preparation for upgrading to rails 3)

  • Tim Connor

    Tim Connor September 2nd, 2010 @ 07:37 PM

    Brian, no need for that new_record check, if you just use read_attribute and write_attribute. I don't think there is any more complex case that can't solve as a work-around.

  • Santiago Pastorino

    Santiago Pastorino February 2nd, 2011 @ 04:49 PM

    • State changed from “new” to “open”
    • Tag changed from 2.3.4, 2.3.5, 3.0.0.rc, activerecord, exception, patch to 234, 235, 300rc, activerecord, exception, patch

    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

    Santiago Pastorino February 2nd, 2011 @ 04:49 PM

    • State changed from “open” to “stale”
  • Victor Costan

    Victor Costan February 17th, 2011 @ 06:36 AM

    • State changed from “stale” to “open”

    The bug is still present in Rails 3.0.4. This blog post (not mine) shows a workaround: http://blog.edseek.com/archives/2009/04/16/missingattributeerror-fr...

    The error is confusing, and will waste a few hours of time for anyone doing after_initialize / after_find hooks.

    [state:open]

  • Rob Olson

    Rob Olson April 18th, 2011 @ 10:55 PM

    • Tag changed from 234, 235, 300rc, activerecord, exception, patch to 2-3-stable, activerecord, exception, patch

    This has finally been resolved in Rails 3.0.7 by #6127.

    It's still present in 2-3-stable though.

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>

Referenced by

Pages