Rails has_and_belongs_to_many conveniences

Eric Torrey

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 items_orders, and my three models looked something like this:

class Item < ActiveRecord::Base
  has_and_belongs_to_many :orders

class Order < ActiveRecord::Base
  has_and_belongs_to_many :items

class ItemsOrders < ActiveRecord::Base
  belongs_to :item
  belongs_to :order
  validates_presence_of :quantity

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
>> items_orders = ItemsOrders.create :item => item,
  :order => order, :quantity => 10
>> items_orders = 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:

<h1>Order summary</h1>
<% @order.items.each do |each| -%>

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

<% end -%>