ruby-on-rails - Rails - 如何在哪里使用自定义属性?
问题描述
我有 Order 模型,其中我有 datetime 列start和 int 列到达_dur 、drop_off_dur等。它们是从开始开始的持续时间(以秒为单位)
然后在我的模型中我有
class Order < ApplicationRecord
def finish_time
self.start + self.arriving_duration + self.drop_off_duration
end
# other def something_time ... end
end
我希望能够做到这一点:
Order.where(finish_time: Time.now..(Time.now+2.hours) )
但是我当然不能,因为没有finish_time这样的专栏。我怎样才能达到这样的结果?
我在 SA 上阅读了 4 种可能的解决方案:
- 急切加载所有订单并使用过滤器选择它 - 如果有更多订单,这将无法正常工作
- 每次我需要时都有参数化范围,但这意味着太多的代码重复
- 每次都有 sql 函数并使用 select() 将其绑定到模型 - 这只是痛苦
- 以某种方式使用http://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html#method-i-attribute?但我不知道如何将它用于我的案例,或者它是否能解决我遇到的问题。
你有任何想法或一些“最佳实践”如何解决这个问题吗?谢谢!
解决方案
您有不同的选择来实现此行为。
添加一个附加
finish_time
列并在您更新/创建时间值时更新它。这可以在 rails 中完成(使用before_validation
或after_save
回调)或作为 psql 触发器。class Order < ApplicationRecord before_validation :update_finish_time private def update_finish_time self.finish_time = start_time + arriving_duration.seconds + drop_off_duration.seconds end end
finish_time
当您在整个应用程序的许多地方都需要时,这尤其有用。它的缺点是您需要使用额外的代码来管理该列,并且它存储您实际已经拥有的数据。好处是,如果您有很多订单并需要对其进行搜索,您可以轻松地在该列上创建索引。一个选项可能是将完成时间更新实现为 postgresql 触发器而不是在 rails 中。这样做的好处是独立于您的 Rails 应用程序(例如,当其他源/脚本也访问您的数据库时),但缺点是将您的业务逻辑拆分到许多地方(ruby 代码、postgres 代码)。
您的第二个选项是为您的查询添加一个虚拟列。
def orders_within_the_next_2_hours finishing_orders = Order.select("*, (start_time + (arriving_duration + drop_off_duration) * interval '1 second') AS finish_time") Order.from("(#{finishing_orders.to_sql}) AS orders").where(finish_time: Time.now..(Time.now+2.hours) ) end
上面的代码创建了带有附加列
finishing_order
的表的 SQL 查询。在第二行中,我们将该SQL 用作 FROM 子句(“聪明地”别名为所以 rails 很高兴)。这样我们就可以像普通列一样查询。order
finish_time
finishing_orders
orders
finish_time
SQL 是为相对较旧的 postgresql 版本编写的(我猜它适用于 9.3+)。如果您使用
make_interval
而不是与 SQL 相乘,interval '1 second'
可能会更具可读性(但需要更新的 postgresql 版本,我认为 9.4+)。