Take the 2-minute tour ×

I have Model X and Model Y which are associated through a has_and_belongs_to_many macro. There's a table in the database that retains data on the many-to-many relationships between Model X and Model Y.

I also have Model A, which is associated through a has_many macro with Model X. I want to be able to run a very simple command to reach all Model Y objects associated with Model A. So, in other words, let's say I have the following objects:

objectA.kind_of? ModelA = true
objectX.kind_of? ModelX = true
objectY.kind_of? ModelY = true

I want to be able to run objectA.objectYs and have returned to me [objectY].

What must I put in my model definitions to be able to do this?

(I have tried placing in Model A: (1) has_many :modelY; and (2) has_many :modelY, :through => :modelX. Neither is right.)

share|improve this question
add comment

1 Answer

up vote 1 down vote accepted

Caveat: normally one wouldn't use such perverse model names.

Assuming the models look like this:

class ModelA < ActiveRecord::Base
  has_many :model_xes

class ModelX < ActiveRecord::Base
  belongs_to :model_a
  has_and_belongs_to_many :model_ies, :class_name => 'ModelY', 
    :join_table => 'model_x_model_ies'

class ModelY < ActiveRecord::Base
  has_and_belongs_to_many :model_xes, :class_name => 'ModelX',
    :join_table => 'model_x_model_ies'

We can create a scope to get the ModelY's for all the ModelX's belonging to a Model A:

class ModelY < ActiveRecord::Base
  scope :find_by_a, lambda { |a| joins(:model_xes).\

then a simple method to call the scope on an instance of ModelA:

class ModelA < ActiveRecord::Base
  def ys

Test like so:

require 'spec_helper'

describe ModelA do
  before(:each) do
    @a = ModelA.create(:name=>"a")
    2.times { @a.model_xes.create(:name=>"x") }

  it "relates model_x" do
    @a.model_xes.count.should == 2

  it "relates model y" do
    x = @a.model_xes.first
    x.model_ies.count.should == 1

  it "relates model y through model x" do
    @a.model_xes.each do |x|
      2.times { x.model_ies.create(:name=>"y") }
    ys = @a.ys
    ys.count.should == 4
    ys.all? { |y| y.name == "y" }.should be_true

Note that HABTM has fallen out of favor, so you're encouraged to use has_many :through instead.

share|improve this answer
My strong thanks for your extremely helpful answer. –  Morris A. Singer Feb 10 '11 at 3:33
add comment

Your Answer


