class CreateOrders < ActiveRecord::Migration[5.2]
def change
create_table :orders do |t|
t.timestamps
end
end
end
class CreateItems < ActiveRecord::Migration[5.2]
def change
create_table :items do |t|
t.timestamps
end
end
end
class CreateAssemblies < ActiveRecord::Migration[5.2]
def change
create_table :assemblies do |t|
t.timestamps
end
end
end
class CreateOrderItems < ActiveRecord::Migration[5.2]
def change
create_table :order_items do |t|
t.references :order, foreign_key: true
t.references :item, foreign_key: true
t.references :assembly, foreign_key: true
t.timestamps
end
end
end
class CreateAssemblyItems < ActiveRecord::Migration[5.2]
def change
create_table :assembly_items do |t|
t.references :assembly, foreign_key: true
t.references :item, foreign_key: true
t.timestamps
end
end
end
class Assembly < ApplicationRecord
has_many :orders, through: :order_items
has_many :assembly_items
has_many :assemblies, through: :assembly_items
has_many :items, through: :assembly_items
end
class AssemblyItem < ApplicationRecord
belongs_to :assembly
belongs_to :item
end
class Item < ApplicationRecord
has_many :orders, through: :order_items
has_many :assemblies, through: :assembly_items
end
class Order < ApplicationRecord
has_many :order_items
has_many :assemblies, through: :order_items
has_many :items, through: :assemblies
end
class OrderItem < ApplicationRecord
belongs_to :order
belongs_to :item
belongs_to :assembly
end
Running via Spring preloader in process 23837
Loading development environment (Rails 5.2.2)
irb(main):001:0> o = Order.first
Order Load (0.3ms) SELECT "orders".* FROM "orders" ORDER BY "orders"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> #<Order id: 1, created_at: "2019-02-25 17:26:00", updated_at: "2019-02-25 17:26:00">
irb(main):002:0> a = Assembly.first
Assembly Load (0.3ms) SELECT "assemblies".* FROM "assemblies" ORDER BY "assemblies"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> #<Assembly id: 1, created_at: "2019-02-25 17:26:35", updated_at: "2019-02-25 17:26:35">
irb(main):003:0> i = Item.first
Item Load (0.4ms) SELECT "items".* FROM "items" ORDER BY "items"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> #<Item id: 1, created_at: "2019-02-25 17:26:27", updated_at: "2019-02-25 17:26:27">
irb(main):004:0> o.assemblies
Assembly Load (0.7ms) SELECT "assemblies".* FROM "assemblies" INNER JOIN "order_items" ON "assemblies"."id" = "order_items"."assembly_id" WHERE "order_items"."order_id" = $1 LIMIT $2 [["order_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Assembly id: 1, created_at: "2019-02-25 17:26:35", updated_at: "2019-02-25 17:26:35">]>
irb(main):005:0> o.items
Item Load (1.4ms) SELECT "items".* FROM "items" INNER JOIN "assembly_items" ON "items"."id" = "assembly_items"."item_id" INNER JOIN "assemblies" ON "assembly_items"."assembly_id" = "assemblies"."id" INNER JOIN "order_items" ON "assemblies"."id" = "order_items"."assembly_id" WHERE "order_items"."order_id" = $1 LIMIT $2 [["order_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Item id: 1, created_at: "2019-02-25 17:26:27", updated_at: "2019-02-25 17:26:27">]>
irb(main):006:0> a.orders
Order Load (0.3ms) SELECT "orders".* FROM "orders" INNER JOIN "order_items" ON "orders"."id" = "order_items"."order_id" WHERE "order_items"."assembly_id" = $1 LIMIT $2 [["assembly_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Order id: 1, created_at: "2019-02-25 17:26:00", updated_at: "2019-02-25 17:26:00">]>
irb(main):007:0> a.assemblies
Assembly Load (0.3ms) SELECT "assemblies".* FROM "assemblies" INNER JOIN "assembly_items" ON "assemblies"."id" = "assembly_items"."assembly_id" WHERE "assembly_items"."assembly_id" = $1 LIMIT $2 [["assembly_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Assembly id: 1, created_at: "2019-02-25 17:26:35", updated_at: "2019-02-25 17:26:35">]>
irb(main):008:0> a.items
Item Load (0.4ms) SELECT "items".* FROM "items" INNER JOIN "assembly_items" ON "items"."id" = "assembly_items"."item_id" WHERE "assembly_items"."assembly_id" = $1 LIMIT $2 [["assembly_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Item id: 1, created_at: "2019-02-25 17:26:27", updated_at: "2019-02-25 17:26:27">]>
irb(main):009:0> i.orders
Order Load (0.4ms) SELECT "orders".* FROM "orders" INNER JOIN "order_items" ON "orders"."id" = "order_items"."order_id" WHERE "order_items"."item_id" = $1 LIMIT $2 [["item_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Order id: 1, created_at: "2019-02-25 17:26:00", updated_at: "2019-02-25 17:26:00">]>
irb(main):010:0> i.assemblies
Assembly Load (0.4ms) SELECT "assemblies".* FROM "assemblies" INNER JOIN "assembly_items" ON "assemblies"."id" = "assembly_items"."assembly_id" WHERE "assembly_items"."item_id" = $1 LIMIT $2 [["item_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Assembly id: 1, created_at: "2019-02-25 17:26:35", updated_at: "2019-02-25 17:26:35">]>
irb(main):011:0>
Notice that all of these relations (presumably) work and operate as expeted.
However, when we try to use them in the way you described.
irb(main):011:0> o.items
Item Load (0.9ms) SELECT "items".* FROM "items" INNER JOIN "assembly_items" ON "items"."id" = "assembly_items"."item_id" INNER JOIN "assemblies" ON "assembly_items"."assembly_id" = "assemblies"."id" INNER JOIN "order_items" ON "assemblies"."id" = "order_items"."assembly_id" WHERE "order_items"."order_id" = $1 LIMIT $2 [["order_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Item id: 1, created_at: "2019-02-25 17:26:27", updated_at: "2019-02-25 17:26:27">]>
irb(main):012:0> assemblies = o.assemblies
Assembly Load (0.4ms) SELECT "assemblies".* FROM "assemblies" INNER JOIN "order_items" ON "assemblies"."id" = "order_items"."assembly_id" WHERE "order_items"."order_id" = $1 LIMIT $2 [["order_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Assembly id: 1, created_at: "2019-02-25 17:26:35", updated_at: "2019-02-25 17:26:35">]>
irb(main):013:0> assemblies.items
Traceback (most recent call last):
1: from (irb):13
Assembly Load (0.6ms) SELECT "assemblies".* FROM "assemblies" INNER JOIN "order_items" ON "assemblies"."id" = "order_items"."assembly_id" WHERE "order_items"."order_id" = $1 LIMIT $2 [["order_id", 1], ["LIMIT", 11]]
NoMethodError (undefined method `items' for #<Assembly::ActiveRecord_Associations_CollectionProxy:0x00007f921ec785a0>)
irb(main):014:0>
We get that error because assemblies isn't actually a single thing, so we can't call items on it directly.
irb(main):017:0> assemblies.map { |assembly|
irb(main):018:1* assembly.items
irb(main):019:1> }
Item Load (0.5ms) SELECT "items".* FROM "items" INNER JOIN "assembly_items" ON "items"."id" = "assembly_items"."item_id" WHERE "assembly_items"."assembly_id" = $1 LIMIT $2 [["assembly_id", 1], ["LIMIT", 11]]
=> [#<ActiveRecord::Associations::CollectionProxy [#<Item id: 1, created_at: "2019-02-25 17:26:27", updated_at: "2019-02-25 17:26:27">]>]
irb(main):020:0>
In that way, we can iterate over each of the assemblies and return a value. Much like your later comment suggest where it's assemblies.each. While my models do not have a name, we can pretend we wanted a specific data structure.
irb(main):017:0> assemblies.map { |assembly|
irb(main):018:1* assembly.items
irb(main):019:1> }
Item Load (0.5ms) SELECT "items".* FROM "items" INNER JOIN "assembly_items" ON "items"."id" = "assembly_items"."item_id" WHERE "assembly_items"."assembly_id" = $1 LIMIT $2 [["assembly_id", 1], ["LIMIT", 11]]
=> [#<ActiveRecord::Associations::CollectionProxy [#<Item id: 1, created_at: "2019-02-25 17:26:27", updated_at: "2019-02-25 17:26:27">]>]
irb(main):020:0>
So, you could alternatively, define a method that does the above on order, however, while I may not have the exact API you desired, if I were to create another assembly, item, order_item, and assembly_item, I can achieve...
irb(main):058:0> o.items
Item Load (0.8ms) SELECT "items".* FROM "items" INNER JOIN "assembly_items" ON "items"."id" = "assembly_items"."item_id" INNER JOIN "assemblies" ON "assembly_items"."assembly_id" = "assemblies"."id" INNER JOIN "order_items" ON "assemblies"."id" = "order_items"."assembly_id" WHERE "order_items"."order_id" = $1 LIMIT $2 [["order_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Item id: 1, created_at: "2019-02-25 17:26:27", updated_at: "2019-02-25 17:26:27">, #<Item id: 2, created_at: "2019-02-25 18:03:06", updated_at: "2019-02-25 18:03:06">]>
irb(main):059:0>
However, you can also achieve what you're after by using a scope.
If you add the scope:
scope :items, -> { AssemblyItem.where(assembly_id: self.ids).map { |ai|
ai.item
}}
to your Assembly model, you should be able to do the following...
irb(main):031:0> o = Order.first
Order Load (0.3ms) SELECT "orders".* FROM "orders" ORDER BY "orders"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> #<Order id: 1, created_at: "2019-02-25 17:26:00", updated_at: "2019-02-25 17:26:00">
irb(main):032:0> o.assemblies.items
(0.3ms) SELECT "assemblies"."id" FROM "assemblies" INNER JOIN "order_items" ON "assemblies"."id" = "order_items"."assembly_id" WHERE "order_items"."order_id" = $1 [["order_id", 1]]
AssemblyItem Load (0.3ms) SELECT "assembly_items".* FROM "assembly_items" WHERE "assembly_items"."assembly_id" IN ($1, $2) [["assembly_id", 1], ["assembly_id", 2]]
Item Load (3.3ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
Item Load (0.3ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 LIMIT $2 [["id", 2], ["LIMIT", 1]]
=> [#<Item id: 1, created_at: "2019-02-25 17:26:27", updated_at: "2019-02-25 17:26:27">, #<Item id: 2, created_at: "2019-02-25 18:03:06", updated_at: "2019-02-25 18:03:06">]
irb(main):033:0>
Comments