Question

I'm basically trying to do something like this:

class A < ActiveRecord::Base
    has_many :bs
end

class B < ActiveRecord::Base
    belongs_to :a
    has_many :cs
end

class C < ActiveRecord::Base
    belongs_to :b
    has_many :ds
end

class D < ActiveRecord::Base
    ...
# and so on with class E, F, G, ...


# get all `C` for all `B` of `A` does not work
A.first.bs.cs
--> undefined method `cs' for #<ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_B:0xxxxx>

My naive approach is to monkey patch array with a new function and us it like this:

class Array
    def get (m)
        self.map{|o| o.send(m)}.delete_if(&:empty?).flatten
    end
end

# works:
A.first.bs.get(:cs)

# works too:
A.all.get(:bs).get(:cs)

# works:
A.all.get(:bs).get(:cs).get(:ds).get(:es)

Are there any pitfalls I do not see at the moment? Monkey patching Array with a function like this smells for me a little bit - is there any cleaner approach?

I simply want to chain those has_many-associations without much hassle in my code. Maybe there's already a gem for it I've not found yet?

Was it helpful?

Solution

First of all, object returned by your bs method is not an array - it is much more complicated AssociationProxy object, which wraps an array called internally target. Your approach leads to extreme case of N+1 problem.

The correct approach is to introduce has_many :through association:

class A < ActiveRecord::Base
  has_many :bs
  has_many :cs, through: :bs 
end

Then you can just call:

A.cs

Without any changes in db.

If you had more nested association (let's say C has_many :ds), you can get them through another has_many :through association:

class A < ActiveRecord::Base
  has_many :bs
  has_many :cs, through: :bs
  has_many :ds, through: :cs
end
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top