Let's say you're implementing rails app for a snowboard rental store.
A given snowboard can be in one of 3 states:
The company needs to be able to view a rental history for
The rental history needs to include temporal data (e.g. Sally rented snowboard 0123 from Dec. 1, 2009 to Dec. 3 2009).
How would you design your model? Would you have a snowboard table with 4 columns (id, state, customer, store), and copy rows from this table, along with a timestamp, to a snowboard_history table every time the state changes?
Thanks!
(Note: I'm not actually trying to implement a rental store; this was just the simplest analogue I could think of.)
I would use a pair of plugins to get the job done. Which would use four models. Snowboard, Store, User and Audit.
acts_as_state_machine and acts_as_audited
AASM simplifies the state transitions. While auditing creates the history you want.
The code for Store and User is trivial and acts_as_audited will handle the audits model.
class Snowboard < ActiveRecord::Base
include AASM
belongs_to :store
aasm_initial_state :unread
acts_as_audited :only => :state
aasm_state :maintenance
aasm_state :available
aasm_state :rented
aasm_event :send_for_repairs do
transitions :to => :maintenance, :from => [:available]
end
aasm_event :return_from_repairs do
transitions :to => :available, :from => [:maintenance]
end
aasm_event :rent_to_customer do
transitions :to => :rented, :from => [:available]
end
aasm_event :returned_by_customer do
transitions :to => :available, :from => [:rented]
end
end
class User < ActiveRecord::Base
has_many :full_history, :class_name => 'Audit', :as => :user,
:conditions => {:auditable_type => "Snowboard"}
end
Assuming your customer is the current_user during the controller action when state changes that's all you need.
To get a snowboard history:
@snowboard.audits
To get a customer's rental history:
@customer.full_history
You might want to create a helper method to shape a customer's history into something more useful. Maybe something like his:
def rental_history
history = []
outstanding_rentals = {}
full_history.each do |item|
id = item.auditable_id
if rented_at = outstanding_rentals.keys.delete(id)
history << {
:snowboard_id => id,
:rental_start => rented_at,
:rental_end => item.created_at
}
else
outstanding_rentals[:id] = item.created_at
end
end
history << oustanding_rentals.collect{|key, value| {:snowboard_id => key,
:rental_start => value}
end
end