M. Cypher
M. Cypher

Reputation: 7066

Strange issue with self-referential belongs_to

I'm having a very irritating issue with a self-referential belongs_to association in Rails 3:

class Locale < ActiveRecord::Base
  belongs_to :parent_locale, :class_name => 'Locale', :foreign_key => 'parent_locale_id'
end

In console:

locale = Locale.find(2)
locale.parent_locale = Locale.find(3)
locale.save
#----> Association is saved correctly!
locale.parent_locale_id
 => 3
locale.parent_locale
#----> Association is returned correctly!
# Now let's retrieve the record again, and see if it still works...
locale = Locale.find(2)
locale.parent_locale_id
 => 3
locale.parent_locale
 => nil

What on earth could be the issue here? Any suggestions?

Edit: This does not work either:

belongs_to :parent_locale, :class_name => 'Locale', :foreign_key => 'parent_locale_id', :inverse_of => :child_locales    
has_many :child_locales, :class_name => 'Locale', :foreign_key => 'parent_locale_id', :inverse_of => :parent_locale

Edit: I enabled SQL query logging in the console and noticed what happens when I try to retrieve the parent_locale:

locale.parent_locale
Phrase Load (0.4ms)  SELECT `phrases`.* FROM `phrases` WHERE `phrases`.`key` = 'parent_locale_id' LIMIT 1
 => nil 

Whoa, what is this? It turns out that Locale has the following method:

def [](key)
  if phrase = Phrase.find_by_key(key)
    t = self.translations.find_by_phrase_id(phrase.id)
    t.text if t
  end
end

Still, how do I make sure this method is not triggered, but the association instead? Frankly, I don't even know why this method is called, as I'm not treating the locale as an array anywhere. Also, there are other associations on this class that do work.

Upvotes: 1

Views: 742

Answers (3)

M. Cypher
M. Cypher

Reputation: 7066

I solved the problem. The culprit was that the [] method was overwritten in the model, and Rails seems to use it to access the model's foreign key column.

I had to change this..

def [](key)
  if phrase = Phrase.find_by_key(key)
    t = self.translations.find_by_phrase_id(phrase.id)
    t.text if t
  end
end

to this:

def [](key)
  if phrase = Phrase.find_by_key(key)
    t = self.translations.find_by_phrase_id(phrase.id)
    t.text if t
  else
    super(key)
  end
end

Sorry, you guys could not have guessed this of course.

Upvotes: 1

Dan McClain
Dan McClain

Reputation: 11921

The problem is that the second find may be hitting ActiveRecord's cache, which is retrieving the "unedited", cached record, instead of retrieving the updated one from the database.

Also, your association is missing the other end, whether it be has_many or has_one, which is probably preventing the record from being retrieved when referencing parent_locale

Edit The two ends of the relation should not use the same foreign key, belongs_to should not use foreign_key

Upvotes: 0

davidb
davidb

Reputation: 8954

belongs_to :parent_locale, :class_name => 'Locale', :foreign_key => 'parent_locale_id'
has_many :child_locales, :class_name => 'Locale', :foreign_key => 'parent_locale_id'

Upvotes: 1

Related Questions