Rails has_and_belongs_to_many conveniences

The other day, I was developing an ordering system. In this system, an order has many items and each item has a price. I also needed to store how many of each item was being purchased. So I used my old friend habtm, and noticed that a simple join table wouldn’t be enough, I would need to store a quantity for each item sold. From the Rails docs:

Deprecated: Any additional fields added to the join table will be placed as attributes when pulling records out through has_and_belongs_to_many associations. Records returned from join tables with additional attributes will be marked as ReadOnly (because we can‘t save changes to the additional attrbutes). It‘s strongly recommended that you upgrade any associations with attributes to a real join model.

So I used a join model, but kept to the Rails naming convention for join tables and script/generated a model called ‘itemsorders’, and my three models looked something like this:

class Item < ActiveRecord::Base
  hasandbelongsto_many :orders

class Order < ActiveRecord::Base hasandbelongstomany :items end

class ItemsOrders < ActiveRecord::Base belongsto :item belongsto :order validatespresenceof :quantity end

From my ItemsOrders join model, I can access the quantity for each item. From script/console:

item = Item.create :price => 100, :name => 'Giant robot' order = Order.create itemsorders = ItemsOrders.create :item => item, :order => order, :quantity => 10 itemsorders = ItemsOrders.create :item => item, :order => order, :quantity => 5

order.items.collect{|each| each.quantity} => ["10", "5"]

item.quantity NoMethodError: undefined method `quantity' for #Item:0xb777aa30

You’ll notice that item itself does not understand quantity, but when accessed through an order with a habtm association it does. This allows me more access to order specific information, so later in the view I can do things like this:

Order summary

<% @order.items.each do |each| -%>

<%= each.name %> <%= each.price %> <%= each.quantity %>, Total: <%= each.quantity * each.price %>

<% end -%>