This project is archived and is in readonly mode.

#4384 ✓resolved
Greg Hazel

belongs_to with :primary_key fails to preload

Reported by Greg Hazel | April 13th, 2010 @ 08:46 AM

Using Rails 2.3.5

with something like this:

class Foo < ActiveRecord::Base
  belongs_to :bar, :foreign_key => 'some_id', :primary_key => 'guid'
end

create_table "foos", :force => true do |t|
  t.string   "some_id"
end

create_table "bars", :force => true do |t|
  t.string   "guid",         :limit => 36,                  :null => false
end

These queries are fine:

>> Foo.find(567158)
  Foo Load (0.1ms)   SELECT * FROM foos WHERE (foos.id = 567158)
=> #<Foo id: 567158, some_id: "9917de13-99a5-45b9-a032-45d8c0ac4a81">
>> Foo.find(567158).bar
  Bar Load (0.4ms)   SELECT * FROM bars WHERE (bars.guid = '9917de13-99a5-45b9-a032-45d8c0ac4a81') LIMIT 1
=> #<Bar id: 3823066, guid: "9917de13-99a5-45b9-a032-45d8c0ac4a81">

But this query fails:

>> Foo.find(567158, :include => bar)
  Foo Load (0.2ms)   SELECT * FROM foos WHERE (foos.id = 567158)
  Bar Load (0.1ms)   SELECT * FROM bars WHERE (bars.id = 9917)
NoMethodError: undefined method each' for nil:NilClass

    from /usr/local/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/association_preload.rb:154:in `set_association_single_records'
    from /usr/local/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/association_preload.rb:147:in `each'
    from /usr/local/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/association_preload.rb:147:in `set_association_single_records'
    from /usr/local/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/association_preload.rb:344:in `preload_belongs_to_association'
    from /usr/local/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/association_preload.rb:318:in `each'
    from /usr/local/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/association_preload.rb:318:in `preload_belongs_to_association'
    from /usr/local/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/association_preload.rb:120:in `send'
    from /usr/local/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/association_preload.rb:120:in `preload_one_association'
    from /usr/local/ruby/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/ordered_hash.rb:97:in `each'
    from /usr/local/ruby/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/ordered_hash.rb:97:in `each'
    from /usr/local/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/association_preload.rb:114:in `preload_one_association'
    from /usr/local/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/association_preload.rb:91:in `preload_associations'
    from /usr/local/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/association_preload.rb:90:in `preload_associations'
    from /usr/local/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/association_preload.rb:90:in `each'
    from /usr/local/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/association_preload.rb:90:in `preload_associations'
    from /usr/local/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:1550:in `find_every'
    from /usr/local/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:1583:in `find_one'
    from /usr/local/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:1569:in `find_from_ids'
    from /usr/local/ruby/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:616:in `find'
    from (irb):2</code>



Notice that it's looking up by id=9917, which is the wrong column and the wrong value. You do get 9917 if you run '9917de13-99a5-45b9-a032-45d8c0ac4a81'.to_i, so the error here seems obvious.

I believe the bug is in preload_belongs_to_association where it uses "primary_key" which in this case is "id", instead of reflection.options[:primary_key], which would be 'guid'.

This patch seems to work for me, but as I'm not familiar with the code I would not call it complete: http://pastie.org/917089

Edit: boy, the markup language on this ticket system is bad.

Comments and changes to this ticket

  • David Trasbo

    David Trasbo April 15th, 2010 @ 08:09 PM

    • Assigned user set to “Ryan Bigg”

    Rails is not supposed to support foreign key columns of other types than integer. This ticket can be marked as invalid.

  • Ryan Bigg

    Ryan Bigg April 15th, 2010 @ 10:57 PM

    • State changed from “new” to “wontfix”

    I agree, this seems like an edge-case better suited to a plugin.

  • Greg Hazel

    Greg Hazel April 16th, 2010 @ 12:31 AM

    What? This would also be broken if the key were an integer.

  • Ryan Bigg

    Ryan Bigg April 16th, 2010 @ 12:47 AM

    • State changed from “wontfix” to “open”

    Leaving it open for discussion. OP has indicated he does not want to submit a patch.

  • Greg Hazel

    Greg Hazel April 16th, 2010 @ 10:53 AM

    • Title changed from “belongs_to with non-integer :foreign_key fails to preload” to “belongs_to with :primary_key fails to preload”
  • Greg Hazel
  • Greg Hazel

    Greg Hazel April 16th, 2010 @ 11:17 AM

    And, guys, if "Rails is not supposed to support foreign key columns of other types than integer", why are they used in the ActiveRecord unit tests themselves?

    class Reply < Topic
      ...
      belongs_to :topic_with_primary_key, :class_name => "Topic", :primary_key => "title", :foreign_key => "parent_title", :counter_cache => "replies_count"
      ...
    end
    
    ...
    
    create_table :topics, :force => true do |t|
      t.string   :title
      ...
      t.string   :parent_title
      ...
    end
    
  • Jeff Kreeftmeijer

    Jeff Kreeftmeijer April 16th, 2010 @ 11:26 AM

    I did a little test on 2.3.5 with the Foo and Bar models you described (I only changed the some_id and guid fields to be integers). I'm not getting any NilClass errors, it seems to work perfectly:

    >> Foo.find(2)
    => #<Foo id: 2, some_id: 123, created_at: "2010-04-16 10:18:32", updated_at: "2010-04-16 10:18:32">
    >> Foo.find(2).bar
    => #<Bar id: 1, guid: 123, created_at: "2010-04-16 10:19:58", updated_at: "2010-04-16 10:19:58">
    >> Foo.find(2, :include => :bar)
    => #<Foo id: 2, some_id: 123, created_at: "2010-04-16 10:18:32", updated_at: "2010-04-16 10:18:32">
    
  • Greg Hazel

    Greg Hazel April 16th, 2010 @ 11:36 AM

    Jeff: Is id the primary_key for Bar? Can you show the sql queries it is submitting? According to line 324 and 335 of associtation_preload.rb in Rails 2.3.5 it should be submitting:

    primary_key = klass.primary_key

    "#{table_name}.#{connection.quote_column_name(primary_key)} #{in_or_equals_for_ids(ids)}"

    which in your case would be:

    Bar.id = 123

    So, I'm not sure why you can't reproduce it exactly, but the bug certainly existed for integer columns, and was fixed by http://github.com/rails/rails/commit/63026541b209cc11ffd74cf3ca04b8...

  • Jeff Kreeftmeijer

    Jeff Kreeftmeijer April 16th, 2010 @ 11:58 AM

    Foo:

    class Foo < ActiveRecord::Base
      belongs_to :bar, :foreign_key => 'some_id', :primary_key => 'guid'
    end
    

    FoosController:

    class FoosController < ApplicationController
      def index
        @foo = Foo.find(1, :include => :bar)
      end
    end
    

    foos/index.html.erb:

    <%= @foo.bar.to_yaml %>
    

    queries:

    Processing FoosController#index (for 127.0.0.1 at 2010-04-16 12:50:15) [GET]
      Foo Load (0.2ms)   SELECT * FROM "foos" WHERE ("foos"."id" = 1) 
      Bar Load (0.2ms)   SELECT * FROM "bars" WHERE ("bars"."id" = 123) 
    Rendering foos/index
      Bar Load (0.3ms)   SELECT * FROM "bars" WHERE ("bars"."guid" = 123) LIMIT 1
    Completed in 16ms (View: 4, DB: 1) | 200 OK [http://localhost/foos]
    

    So I think you're right. It's not properly preloading in 2.3.5 when using integers if you ask me...

  • Jeff Kreeftmeijer
  • Ryan Bigg

    Ryan Bigg April 19th, 2010 @ 10:14 PM

    • State changed from “open” to “resolved”
  • Jamison Wilde

    Jamison Wilde November 12th, 2010 @ 10:40 PM

    • Importance changed from “” to “Low”

    There is a additional bug exposed with this fix, in that if you use intern'd column names as your primary key, the comparison then fails.

    :primary_key => :my_column fails while :primary_key => 'my_column' succeeds.

    The patch is to use to_s:
    options[:primary_key].to_s

    Obviously the workaround is to not use intern'd column names.

    Putting this here since this is the first place people will find in a search engine presumably.

  • Jamison Wilde

    Jamison Wilde November 12th, 2010 @ 11:09 PM

    Also, some more background:
    This fails silently in Ruby 1.8.

    In Ruby 1.9 it fails loudly with:

    NoMethodError: undefined method type' for nil:NilClass<br/>

    from /home/slain/.rvm/gems/ruby-1.9.2-p0/gems/activesupport-2.3.8/lib/active_support/whiny_nil.rb:52:in `method_missing'
    

    Since the columns.detect fails and Ruby 1.9 no longer supports type as a method on NilClass.

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>

People watching this ticket

Pages