假设您正在为滑雪板租赁商店实施rails app。
给定的滑雪板可以处于以下三种状态之一:
公司需要能够查看
的租赁历史记录租赁历史记录需要包含时间数据(例如,Sally从2009年12月1日到2009年12月3日租用滑雪板0123)。
您将如何设计模型?你有一个4列(id,state,customer,store)的滑雪板表,并且每次状态发生变化时,都会将此表中的行和时间戳复制到snowboard_history表中吗?
谢谢!
(注意:我实际上并没有尝试实施租赁店;这只是我能想到的最简单的类比。)
答案 0 :(得分:9)
我会使用一对插件来完成工作。哪个会使用四个型号。滑雪板,商店,用户和审核。
acts_as_state_machine和acts_as_audited
AASM简化了状态转换。审计创建了您想要的历史记录。
Store和User的代码很简单,acts_as_audited将处理审计模型。
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
假设您的客户是控制器操作期间的current_user,当状态发生变化时,您只需要这样做。
获取滑雪板历史记录:
@snowboard.audits
获取客户的租赁历史记录:
@customer.full_history
您可能想要创建一个帮助方法,将客户的历史记录塑造成更有用的东西。也许像他这样:
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
答案 1 :(得分:2)
首先,我会为Snowboard,Customer和Store生成单独的模型。
script/generate model Snowboard name:string price:integer ...
script/generate model Customer name:string ...
script/generate model Store name:string ...
(rails会自动生成id
和created_at
,modified_at
日期)
为了保留历史记录,我不会复制这些表格中的行/值,除非有必要(例如,如果您想跟踪客户租用它的价格)。
相反,我会创建SnowboardEvent模型(如果您愿意,可以将其称为SnowboardHistory
,但就创建新历史而言,使用您所描述的类似属性会感到奇怪:
ev_type
(即0为RETURN,1为维护,2为RENT ......)snowboard_id
(非空)customer_id
store_id
例如,
script/generate model SnowboardEvent ev_type:integer snowboard_id:integer \
customer_id:integer store_id:integer
然后我会设置SnowboardEvent
,Snowboard
,Customer
和Store
之间的所有关系。滑雪板可以具有current_state
,current_store
等功能,实现为
class Snowboard < ActiveRecord::Base
has_many :snowboard_events
validates_presence_of :name
def initialize(store)
ev = SnowboardEvent.new(
{:ev_type => RETURN,
:store_id => store.id,
:snowboard_id = id,
:customer_id => nil})
ev.save
end
def current_state
ev = snowboard_events.last
ev.ev_type
end
def current_store
ev = snowboard_events.last
if ev.ev_type == RETURN
return ev.store_id
end
nil
end
def rent(customer)
last = snowboard_events.last
if last.ev_type == RETURN
ev = SnowboardEvent.new(
{:ev_type => RENT,
:snowboard_id => id,
:customer_id => customer.id
:store_id => nil })
ev.save
end
end
def return_to(store)
last = snowboard_events.last
if last.ev_type != RETURN
# Force customer to be same as last one
ev = SnowboardEvent.new(
{:ev_type => RETURN,
:snowboard_id => id,
:customer_id => last.customer.id
:store_id => store.id})
ev.save
end
end
end
客户将拥有相同的has_many :snowboard_events
。
检查滑雪板或客户历史记录只需使用Snowboard.snowboard_events
或Customer.snowboard_events
循环播放记录。 “时态数据”将是这些事件的created_at
属性。我不认为使用Observer是必要的或相关的。
注意:上面的代码没有经过测试,也不是完美的,只是为了得到这个想法:)