14
votes

As outlined here:

http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

inverse_of appears to tell Rails to Cache the in memory associations and minimize Database Queries. Their example is:

 class Dungeon < ActiveRecord::Base
   has_many :traps, :inverse_of => :dungeon
   has_one :evil_wizard, :inverse_of => :dungeon
 end

 class Trap < ActiveRecord::Base
   belongs_to :dungeon, :inverse_of => :traps
 end

Which they immediatly follow with:

 for `belongs_to` associations `has_many` inverse associations are ignored.

So I have several questions.

  1. Are inverse associations ignored on has_many for a belongs_to? If so, how does their example make sense? Shouldn't it just not do anything?
  2. As far as I can tell (assuming it does anything) All this allows to do is something like:

    dungeon.traps.first.dungeon
    

    with the final call to .dungeon NOT generating an entire new query, but merely reaching for the in memory association. Assuming that is correct, why would I ever NOT want that behavior? Why wouldn't I just stick inverse_of: on every association?

2

2 Answers

6
votes

I started writing about rails inflector and how when an association isn't a straight inflection of a model you use inverse_of to indicate what it is. But then I scrolled to the section you mention and this is how I see it. Say you have something like:

# let's pick a dungeon
d = Dungeon.first

# say you find also find a trap that belongs to this particular d
t = Trap.find(...)

# then t.dungeon is the exact same object as d
d == t.dungeon

Of course dungeon.traps.first.dungeon doesn't really make sense and I doubt that's why this exists. Personally I don't see where and how I would use this but the example they give seems to fill a use case. It goes like this:

# you have an attribute level on dungeon
d.level # => 5

# now say you have a comparison after a modification to level
d.level = 10

# now without inverse_of the following thing occurs
d.level         # => 10
t.dungeon.level # => 5

# d was updated and since t.dungeon is a whole different object 
# it doesn't pick up the change and is out of sync but using invers_of you get
d.level         # => 10
t.dungeon.level # => 10

# because d and t.dungeon are the very same object

Hope that clarifies things.

4
votes

Great news! In Rails 4.1 basic associations* will automatically set up inverse_of.

*More convenience means more edge cases... automatic inverse_of only works for associations that do not specify any of the following options:

  • :through
  • :foreign_key
  • :conditions
  • :polymorphic

Resources:

http://edgeguides.rubyonrails.org/4_1_release_notes.html http://wangjohn.github.io/activerecord/rails/associations/2013/08/14/automatic-inverse-of.html