Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Fun with Object-Relational Mapping

The way object oriented code models data and the relational model we’ve been discussing here are different. With active record we have what is a good compromise. We can use database rows just like they are Ruby objects, as long as we remember to tell them to save back to the database when we’ve finished.

What is Active Record?

Martin Fowler is a well known thinker in the enterprise software space and works for Thoughtworks. Many years ago he wrote the classic book Patterns of Enterprise Application Architecture that looks at how you build enterprise level systems. The book contains many useful patterns for this and Active Record is persented for wrapping and using databases. A good many of Fowler’s patterns ended up in Rails because its originators used his work as an inspiration. He has many other books about things like how to model financial systems and all sorts that are well worth getting hold of if you want to think about enterprise systems more broadly.

Active Record allows you to declaratively model the the relationships between the entities in a system while easily being able to query the attributes of any relation itself without having to write loads of SQL yourself.

Plain old Ruby Objects (PORO)

Let’s think about Customer/Order/OrderLine and just do it in Ruby:

class Customer  
  attr_accessor :name, :orders  
  
  def initialize(name, orders = [])  
    self.name = name  
    self.orders = orders  
  end  
end  
  
class Order  
  attr_accessor :date, :order_lines  
  
  def initialize(date, order_lines = [])  
    self.date = date  
    self.order_lines = order_lines  
  end  
end  
  
class OrderLine  
  attr_accessor :product_name, :quantity, :unit_price  
  
  def initialize( product_name, quantity, unit_price)  
    self.product_name = product_name  
    self.quantity = quantity  
    self.unit_price = unit_price  
  end  
end

Let’s play with this a little:

irb(main):035> require 'date'
irb(main):036> customer = Customer.new("cust")
irb(main):037> order  = Order.new(Date.today)
irb(main):038> customer.orders << order
irb(main):039> order_line = OrderLine.new("Prod 1", 1, 200)
irb(main):040> order.order_lines << order_line
=> [#<OrderLine:0x0000000122f7fe40 @product_name="Prod 1", @quantity=1, @unit_price=200>]
irb(main):041> customer
=> 
#<Customer:0x0000000122fd0020
 @name="cust",
 @orders=[#<Order:0x0000000122f7ff08 @date=#<Date: 2026-08-04 ((2461257j,0s,0n),+0s,2299161j)>, @order_lines=[#<OrderLine:0x0000000122f7fe40 @product_name="Prod 1", @quantity=1, @unit_price=200>]>]>
irb(main):042> 

Ignoring the complication of creating and referencing a separate product class because it complicates things.

Looking at the code above and ignoring the obvious need we might have to save and reload the data if using it in anger you can see the differences quite easily:

  • The objects don’t have a database ID, so rendering routes like /customers/1 would mean we need to add something like an ID to make building an app possible
  • There are no queries as such, just elements in arrays or the actual object itself
  • Queries would have to be hand written using the methods active record reuses, for example methods like select and find from the Hash and Array classes.
  • We don’t have any of the active record helpers like belongs_to, or any validation, and have to rely on what we can write ourselves by hand.

ORMs like Active Record allow us to use the enormous power of databases to create applications that can persist and find data while still having our nice, easy to use objects, that work in the web space. We don’t have to write SQL to do the simple stuff. The Ruby Active Record framework is designed to do the 95% simple things easily, and then let you just write some SQL for the stuff that’s hard to express using object notation.

There’s also some niceties like queries using attributes that are nil are automatically set up to use the is null operator when they get to the database.

Some folks argue that AR does too much, people drop business logic in the models where it does not belong and they become bloated and break the single responsibility principle. As usual there are no hard and fast rules. If something starts to hurt your process then think about it, otherwise leave it alone. Using patterns like services to put business processes in is a first step when this becomes too hard. The Orthogonal Architecture splits everything at the expense of being harder to set up initially, if you really want to layer things for some version of properly.