Search code examples
ruby-on-railsactiverecordcollectionsmany-to-manyhas-many-through

Cannot access nested many-to-many has_many :through associations from parent object


EDIT: This may help get to the point of my question...

Is there a way to do "cascading" through: associations? For example, if we go with the bones song: "The foot bone is connected to the ankle bone, the ankle bone's connected to the leg bone, the leg bone's connected to the hip bone..." I don't want to say that the foot has_many hipbones, because that's not entirely accurate. Nor do I want to say the foot has_many hipbones through the legbone (because it also needs to pass through the ankle). No instead, the foot has_many hips through the ankle which is through the leg. The foot is connected to an ankle, and that foot_ankle assembly is then connected to a leg, and then that entire foot_ankle_leg assembly is finally attached to a hip. So a foot can have many hips, but the foot doesn't belong to a hip in isolation, the association exists only as part of a particular foot_ankle_leg assembly.

To represent something like this, am I correct in setting up those inbetween through tables to "carry" the foot/ankle/leg connections up to the hip? (ie The a_b_c_d_e table represents something akin to a "final" foot_ankle_leg_hip assembly)


ORIGINAL QUESTION: Have several models that come together with various intervening many-to-many :through tables. HABTM is not used since these through tables contain additional attributes.

Here's an image of how it fits together, green boxes are the many-to-many join tables. For brevity's sake, renamed to letters

Database chart

Here's how the structure is coded

class A < ApplicationRecord
  has_many :a_bs
  has_many :bs, through: :a_b
...
end

class B < ApplicationRecord
  has_many :a_bs,
  has_many :as, through: :a_b
...
end

class AB < ApplicationRecord
  belongs_to :a
  belongs_to :b
  has_many :a_b_c_ds
  has_many :c_ds, through: :a_b_c_d
...
end

class C < ApplicationRecord
  has_many :c_ds
  has_many :ds, through: :c_d
...
end

class D < ApplicationRecord
  has_many :c_ds
  has_many :cs, through: :c_d
...
end

class CD < ApplicationRecord
  belongs_to :c
  belongs_to :d
  has_many :a_b_c_ds
  has_many :a_bs, through: :a_b_c_d
...
end

class ABCD < ApplicationRecord
  belongs_to :a_b
  belongs_to :c_d
  has_many :a_b_c_d_es
  has_many :es, through: :a_b_c_d_e
...
end

class E < ApplicationRecord
  has_many :a_b_c_d_es
  has_many :a_b_c_ds, through: :a_b_c_d_e
...
end

class ABCDE < ApplicationRecord
  belongs_to :a_b_c_d
  belongs_to :e
...
end

Whenever I try to access nested children from the parent object in the console, something like A.first.a_b_c_ds, it returns

#<ABCD::ActiveRecord_Associations_CollectionProxy:0x26ca578>.

Is this what I should be seeing? Do I need to interact with that CollectionProxy directly rather than see the "usual" records output? If so, that's a new thing I'll need to learn about :)

In the read-out, I also notice that it's attempting to find the parent's id in the through table rather than the "child" id that's associated.

ABCD Load (0.3ms)  SELECT "a_b_c_ds".* FROM "a_b_c_d" WHERE "a_b_c_d"."a_id" = ?  [["a_id", 1]]

Now, obviously, the a_id won't be in table ABCD. But ab_id is in there, which is associated with a_id. And if I'm reading the rails-guide correctly, rails should be smart enough to make that distinction if I set it up properly.

Any idea where I'm taking a wrong turn?

The class names aren't necessarily alphabetical. For example, Wrapping, Package, Object, WrappingPackage, WrappingPacakgeObject. But since I'm using named many-to-many through: tables, it's my understanding the table names shouldn't matter. That it only comes into play when using has_many_and_belongs_to join tables. But is that where I'm off?

Thanks for your help! Let me know if you need more snippets!


Solution

  • Each belongs_to or has_many call creates an association method in all instances of the class.

    So when you do:

    class A
        has_many bs
    end
    
    class B
        has_many cs
    end
    

    then you basically added method bs to all A's instances. There is no method cs in A and there is no other magic involved.