ActiveRecord - Retrieve attributes from another model - relation does not exist

Hi there,

I’m learning the active model, and I’m trying to retrieve an
association.

My models are:

class Composition < ActiveRecord::Base
has_and_belongs_to_many :materials
belongs_to :product
end

class Material < ActiveRecord::Base
has_and_belongs_to_many :compositions
has_many :products, through: :composition
end

class Product < ActiveRecord::Base
has_many :compositions
has_many :materials, through: :composition
accepts_nested_attributes_for :materials
end

My schema is:

create_table “compositions”, force: :cascade do |t|
t.integer “product_id”
t.integer “material_id”
t.integer “material_quantity”
t.datetime “created_at”, null: false
t.datetime “updated_at”, null: false
end

create_table “materials”, force: :cascade do |t|
t.string “name”
t.decimal “unit_cost”
t.string “unit_measure”
t.datetime “created_at”, null: false
t.datetime “updated_at”, null: false
end

create_table “products”, force: :cascade do |t|
t.string “name”
t.string “description”
t.datetime “created_at”, null: false
t.datetime “updated_at”, null: false
end

On my CompositionsController index method, I would like to retrieve all
raw
materials for a product id.

What i have now is:

def index
@product = Product.find(params[:product_id])
@compositions = @product.compositions
end

How do I retrieve materials attributes from @compositions?

When I try to use
@compositions = @product.compositions.includes(:materials)
It gives me the error:

PG::UndefinedTable: ERROR: relation “compositions_materials” does not
exist
LINE 5: WHERE a.attrelid = ‘“compositions_materials”’…
^
: SELECT a.attname, format_type(a.atttypid, a.atttypmod),
pg_get_expr(d.adbin, d.adrelid), a.attnotnull,
a.atttypid, a.atttypmod
FROM pg_attribute a LEFT JOIN pg_attrdef d
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
WHERE a.attrelid = ‘“compositions_materials”’::regclass
AND a.attnum > 0 AND NOT a.attisdropped
ORDER BY a.attnum

Any hints?

Thanks in advance,
Leandro

Hi Leandro,

The way it is set up right now, you would actually have in the
Composition
model a belongs_to :material. And the Material would have a has_many
:compositions.

Verify that you have the foreign key set in your Composition model, so
you
would never be able to do something like: composition.materials.

Since you have the has_many through association you could simply call.
@product.materials and it will return what you want (It will do the
INNER
JOIN through the compositions table).

The error you’re getting is because when you’re using a has_and_belongs
to
many, Rails expect to have a relationship table named after the two
models
in the plural. But in this case your compositions table is already the
relationship table.

Best regards,
Marco Almeida

On Mon, Mar 30, 2015 at 3:20 PM, Leandro França <

Now I see that you have the quantity inside your compositions table and
you
probably need that.

You can use the code you have in your controller normally with the
changes
mentioned in my last e-mail.

Marco Almeida

+45 31 65 28 84

On Mon, Mar 30, 2015 at 3:53 PM, Marco Antonio A. <

Hi Leandro,

You can reach the materials through the compositions, and you should
worry
about doing extra queries in the DB since you have used the includes
method. You should iterate through them to get the list of material
names.

Say we have:

@compositions = @p.compositions.include(:materials)
@material_names = @compositions.map { |c| c.materials.map(&:name) }

@material_names will return you an array of arrays with the material
names.

Since you have the has many through setup, you can call
@product.materials.map(&:name) directly and you will get a flattened
array
with the materials’ names.

Best regards,
Marco

On Mon, Mar 30, 2015 at 4:33 PM, Leandro França <

Thanks again Marco,

I ended up adding a references to my previous query, and it seems its
working fine now:

@compositions = @product.compositions.includes(:material).references(:
materials)

Regards,
Leandro

Hey Marco,

Thanks a lot for you quick response.

So, I tried to change my models to use only has_many and belongs_to,
instead of has_and_belongs_to_many.
I also added the foreign key to material_id:

class Composition < ActiveRecord::Base
belongs_to :material, :class_name => “Material”, :foreign_key =>
‘material_id’
belongs_to :product
end

class Material < ActiveRecord::Base
has_many :compositions
has_many :products, through: :composition
end

class Product < ActiveRecord::Base
has_many :compositions
has_many :materials, through: :composition
end

On rails console, I can run fine:

@p = Product.first
@p.compositions
@p.compositions.includes(:material)

But this last one returns me only the items I already have on
composition,
not the actual material attributes.

@p.compositions.includes(:material)
Composition Load (0.5ms) SELECT “compositions”.* FROM “compositions”
WHERE “compositions”.“product_id” = $1 [[“product_id”, 1]]
Material Load (0.7ms) SELECT “materials”.* FROM “materials” WHERE
“materials”.“id” IN (1, 2)
=> #<ActiveRecord::AssociationRelation [#<Composition id: 5, product_id:
1,
material_id: 1, material_quantity: 3, created_at: “2015-03-30 05:20:27”,
updated_at: “2015-03-30 05:20:27”>, #<Composition id: 6, product_id: 1,
material_id: 2, material_quantity: 2, created_at: “2015-03-30 05:20:27”,
updated_at: “2015-03-30 05:20:27”>]>

I would like to also get the material names from Material model.
How do I get them using this approach?

Thanks again,
Leandro