rails override default getter for a relationship (belongs_to)

Ruby on-RailsActiverecordAssociationsDefault Value

Ruby 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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionbradView Question on Stackoverflow
Solution 1 - Ruby on-RailsRandy SimonView Answer on Stackoverflow
Solution 2 - Ruby on-RailsalxibraView Answer on Stackoverflow
Solution 3 - Ruby on-RailsCory FoyView Answer on Stackoverflow
Solution 4 - Ruby on-RailsLulzonView Answer on Stackoverflow