rails override default getter for a relationship (belongs_to)
Ruby on-RailsActiverecordAssociationsDefault ValueRuby on-Rails Problem Overview
So I know how to override the default getters for attributes of an ActiveRecord object using
def custom_getter
return self[:custom_getter] || some_default_value
end
I'm trying to achieve the same thing however for a belongs to association. For instance.
class Foo < AR
belongs_to :bar
def bar
return self[:bar] || Bar.last
end
end
class Bar < AR
has_one :foo
end
When I say:
f = Foo.last
I'd like to have the method f.bar
return the last Bar, rather than nil if that association doesn't exist yet.
This doesn't work however. The reason is that self[:bar] is always undefined. It's actually self[:bar_id].
I can do something naive like:
def bar
if self[:bar_id]
return Bar.find(self[:bar_id])
else
return Bar.last
end
end
However this will always make a db call, even if Bar has already been fetched, which is certainly not ideal.
Does anyone have an insight as to how I might have a relationship such that the belongs_to attribute is only loaded once and has a default value if not set.
Ruby on-Rails Solutions
Solution 1 - Ruby on-Rails
alias_method is your friend here.
alias_method :original_bar, :bar
def bar
self.original_bar || Bar.last
end
The way this works is that you alias the default "bar" method as "original bar" and then implement your own version of "bar". If the call to original_bar returns nil then you return the last Bar instance instead.
Solution 2 - Ruby on-Rails
i found that using "super" is the best way
def bar
super || Bar.last
end
I hope this helps you :D
Solution 3 - Ruby on-Rails
Randy's answer is spot on, but there's an easier way to write it, using alias_method_chain:
def bar_with_default_find
self.bar_without_default_find || Bar.last
end
alias_method_chain :bar, :default_find
That creates two methods - bar_with_default_find
and bar_without_default_find
and aliases bar
to the with
method. That way you can explicitly call one or the other, or just leave the defaults as is.
Solution 4 - Ruby on-Rails
Building upon other answers here, you may also want to handle assignment operations as well.
Alias method:
alias_method :original_bar, :bar
alias_method :original_bar=, :bar=
def bar
self.original_bar ||= Bar.last
end
Super method:
def bar
super || bar = Bar.last
end
Where this was useful for me was when using Bar.find_or_initialize_by
which meant the record wasn't always persisted, and also any non-persisted changes would reflect on the parent record as well.