【问题标题】:Setting & getting virtual attributes in Rails model在 Rails 模型中设置和获取虚拟属性
【发布时间】:2012-08-22 20:26:14
【问题描述】:

我正在寻找一种 Rails-y 方法来处理以下问题:

Event 模型中的两个 datetime 属性:

start_at: datetime
end_at:   datetime

我想使用 3 个字段在表单中访问它们:

event_date
start_time
end_time

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

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")

 => #<Event id: 3, start_at: "2012-08-30 01:00:00", end_at: "2012-08-30 04:00:00",  created_at: "2012-08-22 19:51:53", updated_at: "2012-08-22 19:51:53"> 

直到直接设置实际属性(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
 => #<Event id: 3, start_at: "2012-08-30 01:00:00", end_at: "2012-08-30 06:00:00", created_at: "2012-08-22 19:51:53", updated_at: "2012-08-22 19:51:53"> 
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
 => #<Event id: 3, start_at: "2012-08-30 01:00:00", end_at: "2012-08-30 04:00:00", created_at: "2012-08-22 19:51:53", updated_at: "2012-08-22 20:02:15"> 
1.9.3p194 :010 > 

我的假设是我还需要自定义“实际”属性的设置器,但我不确定如何在不破坏默认行为的情况下做到这一点。想法?也许有更多“Rails-y”“callback-y”方式来处理这个问题?

【问题讨论】:

  • 好问题!它并没有就此结束。通常,当您在表单上放置虚拟属性时,您还希望验证错误消息在真实属性和虚拟属性之间保持同步。此外,为无效输入保留标准表单行为,当重新显示表单并填充无效值时,会使所有这些回调逻辑非常复杂。我很想看到一些处理所有这些的好模式。

标签: ruby-on-rails ruby-on-rails-3 accessor virtual-attribute


【解决方案1】:

这是我的看法。我没有用 ActiveRecord 测试过,但是我离开了 cmets。希望这会有所帮助。

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 有一个明确的模式。可以通过元编程对其进行抽象,但我认为这会使示例不清楚。

【讨论】:

    【解决方案2】:

    我根本不会“缓存”“虚拟”属性,尤其是如果您不需要虚拟属性是“可设置的”,只需要“可获取”,这就是您的示例的样子。

      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
    

    一旦您开始缓存,您就必须担心缓存值无效——您基本上遇到了“缓存值无效”的问题。有几种方法可以使您的原始设计工作 - 但我认为在那里进行的计算不足以证明您正在做的记忆/缓存增加的复杂性。只需按需提供 em,您无需担心缓存值无效。

    如果您真的想按照最初的建议去做,这可能会让您开始:Callback for changed ActiveRecord attributes?(不确定自从编写了 stackoverflow 以来 Rails 是否发生了变化)

    【讨论】:

    • 这使我可以访问表单中的event_datestart_timeend_time。但是这些是表单输入值,因此,从您的回答中可以看出,无法对这些虚拟属性进行更改并将它们应用于与记录一起保存的 actual 属性。我错过了什么?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-11
    • 2023-03-17
    • 1970-01-01
    • 2019-02-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多