I have a
class Item
include Mongoid::Document
field name, type: String
end
and a
class Pack
include Mongoid::Document
end
Each Pack can include different Items in different quantities.
I initially wanted to do
class Pack
include Mongoid::Document
field items, type: Array
end
At first, I tried
a = Pack.new
a.items = []
a.items << {item: Item.first, quantity: 4}
a.save
and I got:
NoMethodError: undefined method `__bson_dump__' for #<Item:0x007faf1a56d670>
Then I tried:
a = Pack.new
a.items = []
a.items << {item_id: Item.first.id, quantity: 4}
a.save
But now I can't do something like
a.items[0].item.name
Next, I tried
class Item
include Mongoid::Document
field name, type: String
belongs_to :item_quantity
end
class ItemQuantity
include Mongoid::Document
has_one :item
belongs_to :pack
field quantity, type: Integer
end
class Pack
include Mongoid::Document
has_many :item_quantity
end
Which works but feels like a kludge
How do I best do this?
Your last iteration is close to the way I've solved similar problems in the past. I'll try to adapt it to your context. The main thing I did differently was not storing the quantity in the "join" collection, so for each of the multiple "copies" of an item in a pack, a new doc gets created in that collection.
class Item
include Mongoid::Document
has_many :item_instances
end
class ItemInstance
include Mongoid::Document
belongs_to :item
belongs_to :pack
end
class Pack
include Mongoid::Document
has_many :item_instances
end
With this you can do something like:
pack = Pack.new
item = Item.first
pack.item_instances.build(item_id: item.id)
I've used this for a print shop management app where the classes were Invoice, Job, Product. A job was sort of an "instance" of a product, and an invoice had many jobs, which could possibly all be the same product, so a straight many to many mapping wouldn't work.
I'd also rethink your schema and make sure things are named meaningfully. Just by renaming your classes from Item, ItemInstance, Pack to Product, Item, Pack respectively, it makes things a bit clearer:
pack = Pack.new
product = Product.first
pack.items.build(product_id: product.id)
Finally, if you create some simple convenience methods in the Pack class, that can go a long way towards creating a more useable interface:
class Pack
def new_item(product)
self.items.build(product_id: product.id)
end
def products
self.items.map { |i| i.product }
end
end
So you can do:
pack.add_item(product)
# Same as using pack.items.build(product_id: product.id)
pack.products
# Returns an array (including duplicates) of all the products included in the pack.
Hope that helps. This doesn't come up often, and I'm sure there are other solutions, but that's the basic pattern I use when trying to solve the "one object has more than one of another object" problem.
Add your own methods relevant to how you'll be using the class and it should be pretty usable. Also, don't forget to set up indexing on the "join" collection. And see this issue on Mongoid's Github page, requesting multiple entries in the arrays used for many-to-many relations.
If that gets added, it'll be the way to go in the future if all you need is a simple relation. If you to store more details, like I did when a print job for a product gets created (timestamp, title etc.), you'll still have to do something like the above.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With