Andrew Schwartz
Andrew Schwartz

Reputation: 4657

has_one :through association incorrectly returns nil for a new_record

I have the following models. Yes, the design is slightly convoluted, because we're revamping our architecture in stages; nonetheless, I think the principles here should work:

class Translation < ActiveRecord::Base
  belongs_to :translation_service_option
  belongs_to :service, inverse_of: :translation, class_name: "TranslationService"
  # I have also tried belongs_to :translation_service, inverse_of: :translation, foreign_key: :service_id
end

class TranslationService < Service
  # Service < ActiveRecord::Base
  has_one :translation, inverse_of: :service
  # I have also tried inverse_of: :translation_service in conjunction with above
  has_one :translation_service_option, through: :translation
end

I am trying to build a before_save callback on TranslationService that needs to access translation_service_option, however, this is returning nil for a new record:

t = Translation.last
#=> #<Translation id: 154, translation_service_option_id: 5, [...]
t.service = TranslationService.new
#=> #<TranslationService id: nil [...]
t.translation_service_option
#=> #<TranslationServiceOption id: 5 [...]
t.service.translation_service_option
#=> nil
t.service.translation
#=> #<Translation id: 154 [...]
t.service.translation.translation_service_option
#=> #<TranslationServiceOption id: 5

Of course, once I save the object, it works fine, but the issue is I need access to it in the before_save (or before_create) callback:

t.service.save
t.translation_service_option
#=> #<TranslationServiceOption [...]

So the problem is that t.service returns nil for its translation_service_option, even though t.service.translation has a valid translation_service_option.

I get that t.service doesn't have an id yet, it does know about it's translation object. So why can't it also know about it's translation_service_option object? Obviously I could just use self.translation.try(:translation_service_option) in my callback, but that starts to get messy as the callback uses methods that are used more generally, and so just for this one quirk I find myself needing to replace all uses of translation_service_option with translation.try(:translation_service_option), which just seems too un-Railsy and negates the purpose of setting up the has_one :through association in the first place.

Is there a way I can set up my associations to correctly assign t.service.translation_service_option on initialize?

Upvotes: 2

Views: 579

Answers (0)

Related Questions