Welcome to Giant Robots Smashing Into Other Giant Robots — a weblog about development, business, design and technology — written by thoughtbot.

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 ‘items_orders’, and my three models looked something like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
class Item < ActiveRecord::Base
  has_and_belongs_to_many :orders
end

class Order < ActiveRecord::Base
  has_and_belongs_to_many :items
end

class ItemsOrders < ActiveRecord::Base
  belongs_to :item
  belongs_to :order
  validates_presence_of :quantity
end
From my ItemsOrders join model, I can access the quantity for each item. From script/console:
1
2
3
4
5
6
7
8
9
10
>> 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:
1
2
3
4
5
6
7
<h1>Order summary</h1>
<% @order.items.each do |each| -%>
  <p>
    <%= each.name %> <%= each.price %> 
    <%= each.quantity %>, Total: <%= each.quantity * each.price %>
  </p>
<% end -%>

About this entry

 

thoughtbot is hiring

We are hiring web developers and web designers in both Boston and New York, NY.

What are we up to?

We built Shoulda, an eclectic set of additions to Test::Unit; Paperclip to manage uploaded files without hassle; Jester, a REST/ActiveResource client library written in Javascript, and Squirrel, an enhancement for ActiveRecord's find syntax; — amongst some other projects.


Chad (President) and Jon (CTO) co-authored a technical book titled Pro Active Record: Databases with Ruby and Rails, which explores the ins and outs of the ActiveRecord ruby library. You can buy it today at Amazon.com.

About thoughtbot, inc.

We are a small web application development consulting business, with offices in Boston, MA and New York, NY. If you're looking to find a team for your next web development project or your new web application — get in touch.