Rails 6.1:通过has_many关系创建包含.first或.last的作用域



在Rails 6.1中,假设有两个模型:

  • PeriodicJob: has_many :executions
  • Execution,state字段为succeededfailed

我想运行查询:

给我最后一次(最高ID)执行状态为succeed的所有PeriodicJobs

一个可能的解决方案是使用带有子查询的原始SQL,正如在另一个Stackoverflow问题中指出的那样:在最后一个has_many关联中搜索具有特定值的实例

然而,对于这样一个简单的英语问题,这似乎过于复杂的代码。考虑到Rails的强大功能,我希望看到这样的内容:
PeriodicJob.joins(:executions).where(cool_trick_im_yet_unaware_of_to_get_last_ordered_by_id_execution: { state: 'succeeded' }

这样的东西在Rails中存在吗?它将如何应用到这个例子中?

优化读取的一种方法是设置一个单独的外键列和关联作为快捷键。到最近一次执行:

class AddLatestExecutionToProducts < ActiveRecord::Migration[6.0]
def change
add_reference :latest_execution, :execution
end
end
class PeriodicJob < ApplicationRecord
has_many :executions, 
after_add: :set_latest_execution
belongs_to :latest_execution, 
optional: true,
class_name: 'Execution'
private
def set_latest_execution(execution)
update_attribute(:latest_execution_id, execution.id)
end
end

这允许您执行PeriodicJob.eager_load(:latest_execution)并避免N+1查询和从执行表中加载所有记录。如果每个周期作业有很多执行,这一点尤其重要。

成本是每次创建一个执行时都需要一个额外的写查询。

如果您想将此限制为仅包含最近的成功/失败,您可以添加两列:

class AddLatestExecutionToProducts < ActiveRecord::Migration[6.0]
def change
add_reference :latest_successful_execution, :execution
add_reference :latest_failed_execution, :execution
end
end
class Execution ​< ApplicationRecord
enum state: {
​succeeded: 'succeeded',
​failed:    'failed'
​ }
end
class PeriodicJob < ApplicationRecord
has_many :executions, 
after_add: :set_latest_execution
belongs_to :latest_successful_execution, 
optional: true,
class_name: 'Execution'
belongs_to :latest_failed_execution, 
optional: true,
class_name: 'Execution'
private
def set_latest_execution(execution)
if execution.succeeded?
update_attribute(:latest_successful_execution_id, execution.id)
else
update_attribute(:latest_failed_execution_id, execution.id)
end
end
end

如果使用的是常规数字id,则可以为关系添加条件。这将在模型上设置一个自定义关系,该关系将返回成功的最新执行。有一个宏将限制'集合'为一个。

has_one :current_succeeded_execution, -> { where(state: "succeeded").reorder(id: :desc) }, class_name: 'Execution'

如果你使用uuid,我会在这些模型上设置一个默认作用域,以按asc创建的顺序排序,以确保第一个总是最老的,即第一个创建的。然后你可以重新排序以获取最新的一个

has_one :current_succeeded_execution, -> { where(state: "succeeded").reorder(created_at: :desc) }, class_name: 'Execution'

相关内容

最新更新