Converting an array of objects to ActiveRecord::Relation

我有一个对象数组,我们称之为 Indicator。我想在这个数组上运行 Indicator 类方法(def self.subjects变体、范围等的方法)。我所知道的在一组对象上运行类方法的唯一方法是使它们成为 ActiveRecord: : Relations。因此,我最终求助于向 Array添加一个 to_indicators方法。

def to_indicators
# TODO: Make this less terrible.
Indicator.where id: self.pluck(:id)
end

有时候,我会在类方法中链接相当多的这些作用域来过滤结果。因此,即使我调用 ActiveRecord: : Relations 上的方法,我也不知道如何访问该对象。我只能通过 all得到它的内容。但是 all是一个数组。然后我必须把这个数组转换成 ActiveRecord: : 关系。例如,这是其中一种方法的一部分:

all.to_indicators.applicable_for_bank(id).each do |indicator|
total += indicator.residual_risk_for(id)
indicator_count += 1 if indicator.completed_by?(id)
end

我想这可以归结为两个问题。

  1. 如何将一个对象数组转换为 ActiveRecord: : Relations? 最好不要每次都执行 where
  2. When running a def self.subjects type method on an ActiveRecord::Relation, how do I access that ActiveRecord::Relation object itself?

谢谢。如果我需要澄清什么,让我知道。

96667 次浏览

如何将一个对象数组转换为 ActiveRecord: : Relations? 最好不要每次都执行 where。

无法将 Array 转换为 ActiveRecord: : Relations,因为 Relations 只是 SQL 查询的构建器,其方法不对实际数据进行操作。

However, if what you want is a relation then:

  • for ActiveRecord 3.x, don’t call all and instead call scoped, which will give back a Relation which represents the same records that all would give you in an Array.

  • for ActiveRecord 4.x, simply call all, which returns a Relation.

在 ActiveRecord: : Relations 上运行 def self.subjects类型方法时,如何访问 ActiveRecord: : Relations 对象本身?

当方法被调用到一个关系对象时,self就是关系(与它所定义的模型类相反)。

您可以将对象数组 arr转换为 ActiveRecord: : 关系,如下所示(假设您知道对象是哪个类,您可能知道)

MyModel.where(id: arr.map(&:id))

你必须使用 where,虽然,它是一个有用的工具,你不应该不愿意使用。现在您有了一行程序将数组转换为关系。

map(&:id)将把对象数组转换为只包含其 id 的数组。将一个数组传递给 where 子句将生成一个带有 IN的 SQL 语句,该语句类似于:

SELECT .... WHERE `my_models`.id IN (2, 3, 4, 6, ....

请记住,数组的顺序将丢失 -但是由于您的目标只是在这些对象的 收藏品上运行类方法,所以我认为这不会是一个问题。

那么,在我的情况下,我需要 将对象数组转换为 ActiveRecord: : 关系以及 sorting他们与一个特定的列(例如 id)。因为我使用的是 MySQL,所以 场函数可能会有所帮助。

MyModel.where('id in (?)',ids).order("field(id,#{ids.join(",")})")

SQL 看起来像:

SELECT ... FROM ... WHERE (id in (11,5,6,7,8,9,10))
ORDER BY field(id,11,5,6,7,8,9,10)

MySQL 字段函数

ActiveRecord::Relation绑定从数据库中检索数据的数据库查询。

假设有意义,我们有数组的对象是相同的类,那么我们应该用哪个查询来绑定它们?

我跑步的时候,

users = User.where(id: [1,3,4,5])
User Load (0.6ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` IN (1, 3, 4, 5)  ORDER BY created_at desc

在上面这里,users返回 Relation对象,但绑定后面的数据库查询,您可以查看它,

users.to_sql
=> "SELECT `users`.* FROM `users` WHERE `users`.`id` IN (1, 3, 4, 5)  ORDER BY created_at desc"

因此,不可能从与 sql 查询无关的对象数组返回 ActiveRecord::Relation

首先,这不是什么灵丹妙药。根据我的经验,我发现转换为关系有时比选择更容易。我试图非常谨慎地使用这种方法,并且只在替代方案更为复杂的情况下使用。

这就是我的解决方案,我已经扩展了 Array

# lib/core_ext/array.rb


class Array


def to_activerecord_relation
return ApplicationRecord.none if self.empty?


clazzes = self.collect(&:class).uniq
raise 'Array cannot be converted to ActiveRecord::Relation since it does not have same elements' if clazzes.size > 1


clazz = clazzes.first
raise 'Element class is not ApplicationRecord and as such cannot be converted' unless clazz.ancestors.include? ApplicationRecord


clazz.where(id: self.collect(&:id))
end
end

一个使用的例子是 array.to_activerecord_relation.update_all(status: 'finished')。现在我在哪里使用它?

有时候你需要过滤掉 ActiveRecord::Relation,例如去掉不完整的元素。在这种情况下,最好的方法是使用范围 elements.not_finished,而且仍然保留 ActiveRecord::Relation

但有时这种情况更为复杂。取出所有未完成的元素,并已在过去4周生产,并已检查。为了避免创建新的作用域,您可以筛选到一个数组,然后再转换回来。请记住,您仍然对 DB 执行查询,因为它通过 id进行搜索,但仍然是一个查询。