在Rails模型中设置和获取虚拟属性

我正在寻找一种方法来解决以下问题:

Event模型中的两个datetime属性:

 start_at: datetime end_at: datetime 

我想使用3个字段以表格forms访问它们:

 event_date start_time end_time 

我遇到的问题是如何将实际虚拟属性保持在“同步”,以便可以通过表单和/或直接通过end_atend_at更新模型。

 class Event < ActiveRecord::Base attr_accessible :end_at, :start_at, :start_time, :end_time, :event_date attr_accessor :start_time, :end_time, :event_date after_initialize :get_datetimes # convert db format into accessors before_validation :set_datetimes # convert accessors into db format def get_datetimes if start_at && end_at self.event_date ||= start_at.to_date.to_s(:db) # yyyy-mm-dd self.start_time ||= "#{'%02d' % start_at.hour}:#{'%02d' % start_at.min}" self.end_time ||= "#{'%02d' % end_at.hour}:#{'%02d' % end_at.min}" end end def set_datetimes self.start_at = "#{event_date} #{start_time}:00" self.end_at = "#{event_date} #{end_time}:00" end end 

哪个有效:

 1.9.3p194 :004 > e = Event.create(event_date: "2012-08-29", start_time: "18:00", end_time: "21:00") => # 

直到直接设置实际属性(在validation时end_at设置回end_time ):

 1.9.3p194 :006 > e.end_at = "2012-08-30 06:00:00 UTC +00:00" => "2012-08-30 06:00:00 UTC +00:00" 1.9.3p194 :007 > e => # 1.9.3p194 :008 > e.save (0.1ms) BEGIN (0.4ms) UPDATE "events" SET "end_at" = '2012-08-30 04:00:00.000000', "start_at" = '2012-08-30 01:00:00.000000', "updated_at" = '2012-08-22 20:02:15.554913' WHERE "events"."id" = 3 (2.5ms) COMMIT => true 1.9.3p194 :009 > e => # 1.9.3p194 :010 > 

我的假设是我还需要自定义“实际”属性的setter,但我不知道如何做到这一点w / out搞砸了默认行为。 思考? 也许有更多的“Rails-y”“回调-y”方式来处理这个问题?

这是我的看法。 我没有用ActiveRecord测试它,但我留下了评论。 希望这可以帮助。

 class Event < ActiveRecord::Base attr_accessible :end_at, :start_at, :start_time, :end_time, :event_date attr_accessor :start_time, :end_time, :event_date def start_time @start_time || time_attr_from_datetime(start_at) end def start_time=(start_time_value) @start_time = start_time_value set_start_at end def end_time @end_time || time_attr_from_datetime(end_at) end def end_time=(end_time_value) @end_time = @end_time_value set_end_at end def event_date @event_date || start_at.to_date.to_s(:db) end def event_date=(event_date_value) @event_date = event_date_value set_start_at set_end_at end def start_at=(start_at_value) write_attribute(:start_at, start_at_value) # Maybe you need to do write_attribute(:start_at, DateTime.parse(start_at_value)) here ??? @start_time = time_attr_from_datetime(start_at) end def end_at=(end_at_value) write_attribute(:end_at, end_at_value) # Maybe you need to do write_attribute(:end_at, DateTime.parse(end_at_value)) here ??? @end_time = time_attr_from_datetime(end_at) end private def set_start_at self.start_at = DateTime.parse("#{event_date} #{start_time}:00") end def set_end_at self.end_at = DateTime.parse("#{event_date} #{end_time}:00") end def time_attr_from_datetime(datetime) "#{'%02d' % datetime.hour}:#{'%02d' % datetime.min}" end end 

编辑:获取和设置start_time和end_time有一个明确的模式。 它可以用元编程抽象一点,但我认为这会使示例不清楚。

我根本不会’缓存”虚拟’属性,特别是如果你不需要你的虚拟属性是“可设置的”,只有“可以”,这就是你的例子。

  def event_date start_at.to_date.to_s(:db) # yyyy-mm-dd end def start_time "#{'%02d' % start_at.hour}:#{'%02d' % start_at.min}" end def end_time "#{'%02d' % end_at.hour}:#{'%02d' % end_at.min}" end 

一旦开始缓存,您必须担心使缓存的值无效 – 您基本上有一个’无效的缓存值’问题。 有几种方法可以使您的原始设计工作 – 但我认为在那里进行的计算并不足以certificate您正在进行的记忆/缓存增加的复杂性。 只需按需提供em,您无需担心使缓存的值无效。

如果你真的想要做你最初提出的建议,这可能会让你开始: 回调更改的ActiveRecord属性? (不确定自从写入stackoverflow后Rails是否已更改)