Rails-如何在哪里使用自定义属性?

时间:2018-06-25 16:40:37

标签: ruby-on-rails

我有一个Order模型,其中有datetime列 start 和int列 arriving_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吗?但是我不知道如何在我的案子中使用它,甚至无法解决我遇到的问题。

您是否有解决此问题的想法或“最佳实践”? 谢谢!

1 个答案:

答案 0 :(得分:1)

您有不同的选择来实现此行为。

  1. 添加其他finish_time列,并在每次更新/创建时间值时对其进行更新。这可以在rails中(使用before_validationafter_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应用程序(例如,当其他源/脚本也访问您的数据库时),但是具有将业务逻辑划分到很多地方(红代码,Postgres代码)的缺点。

  2. 您的第二个选择是为查询添加虚拟列。

    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查询,该查询是order表,带有附加的finish_time列。在第二行中,我们使用finishing_orders SQL作为FROM子句(“聪明地”别名为orders,因此rails很高兴)。这样,我们可以像finish_time一样查询普通列。

    SQL是为相对较旧的postgresql版本编写的(我想它适用于9.3+)。如果您使用make_interval而不是与interval '1 second'相乘,则SQL可能会更具可读性(但我需要更新的Postgresql版本,我认为是9.4 +)。