将对象数组转换为 ActiveRecord::Relation
Posted
技术标签:
【中文标题】将对象数组转换为 ActiveRecord::Relation【英文标题】:Converting an array of objects to ActiveRecord::Relation 【发布时间】:2013-06-24 06:55:04 【问题描述】:我有一个对象数组,我们称之为Indicator
。我想在这个数组上运行指标类方法(def self.subjects
品种、范围等)。我知道在一组对象上运行类方法的唯一方法是让它们成为 ActiveRecord::Relation。所以我最终诉诸于将to_indicators
方法添加到Array
。
def to_indicators
# TODO: Make this less terrible.
Indicator.where id: self.pluck(:id)
end
有时我会在类方法中链接很多这样的作用域来过滤结果。因此,即使我在 ActiveRecord::Relation 上调用方法,我也不知道如何访问该对象。我只能通过all
获得它的内容。但是all
是一个数组。那么我必须将该数组转换为 ActiveRecord::Relation。例如,这是其中一种方法的一部分:
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
我想这可以浓缩为两个问题。
-
如何?最好不要每次都发送
where
。
在 ActiveRecord::Relation 上运行 def self.subjects
类型方法时,如何访问该 ActiveRecord::Relation 对象本身?
谢谢。如果我需要澄清任何事情,请告诉我。
【问题讨论】:
如果您尝试将该数组转换回关系的唯一原因是因为您通过.all
获得它,只需使用.scoped
就像 Andrew Marshall 的回答所表明的那样(尽管在 rails 4 它会与.all
一起工作)。如果您发现自己需要将数组转换为关系,那么您在某处出错了......
【参考方案1】:
首先,这不是灵丹妙药。根据我的经验,我发现转换为关系有时比其他方法更容易。我尝试非常谨慎地使用这种方法,并且仅在替代方案更复杂的情况下使用。
这里说的是我的解决方案,我已经扩展了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
搜索,但仍然是查询。
【讨论】:
【参考方案2】: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
return Relation
对象,但在其后面绑定了数据库查询,您可以查看它,
users.to_sql
=> "SELECT `users`.* FROM `users` WHERE `users`.`id` IN (1, 3, 4, 5) ORDER BY created_at desc"
因此不可能从独立于 sql 查询的对象数组中返回 ActiveRecord::Relation
。
【讨论】:
【参考方案3】:好吧,就我而言,我需要将对象数组转换为 ActiveRecord::Relation 并使用特定列(例如 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 field function
【讨论】:
对于这个适用于 PostgreSQL 的版本,看看这个线程:***.com/questions/1309624/… ActiveRecord::StatementInvalid (PG::TooManyArguments: ERROR: cannot pass more than 100 arguments to a function) 此解决方法不能为函数提供超过 100 个参数,因此,它适用于总关系长度最大为 100,像 112 和 12 这样的 id 不会相互分离。 那么MyModel.where(id: ids)
?【参考方案4】:
您可以像这样将对象数组 arr
转换为 ActiveRecord::Relation(假设您知道对象是哪个类,您可能会这样做)
MyModel.where(id: arr.map(&:id))
你必须使用where
,它是一个有用的工具,你不应该不愿意使用。现在你有了一个将数组转换为关系的单行代码。
map(&:id)
会将您的对象数组转换为仅包含其 id 的数组。将数组传递给 where 子句将生成带有 IN
的 SQL 语句,如下所示:
SELECT .... WHERE `my_models`.id IN (2, 3, 4, 6, ....
请记住,数组的顺序将会丢失 - 但由于您的目标只是在这些对象的 集合 上运行一个类方法,我假设不会有问题的。
【讨论】:
干得好,正是我需要的。您可以将其用于模型上的任何属性。非常适合我。 既然可以where(id: arr.map(&:id))
,为什么还要自己构建文字SQL?严格来说,这不会将数组转换为关系,而是获取对象的新实例(一旦实现关系),这些 ID 的属性值可能与数组中已经在内存中的实例不同。
不过,这会丢失排序。
非常低效!您已将内存中已有的对象集合转换为将要执行数据库查询以访问的对象集合。我会考虑重构那些你想要遍历数组的类方法。
@james2m 你能想出一种更有效的方法将任何对象数组转换为活动记录关系吗?据我所知,在 Rails 中没有内置的方法或方法可以做到这一点。这是 Rails 中最实用的方法。【参考方案5】:
如何将对象数组转换为 ActiveRecord::Relation?最好不要每次都在哪里。
您无法将 Array 转换为 ActiveRecord::Relation,因为 Relation 只是 SQL 查询的构建器,其方法不对实际数据进行操作。
但是,如果你想要的是一个关系,那么:
对于 ActiveRecord 3.x,不要调用 all
而是调用 scoped
,这将返回一个关系,该关系表示 all
将在数组中提供给您的相同记录。
对于 ActiveRecord 4.x,只需调用 all
,它会返回一个关系。
在 ActiveRecord::Relation 上运行
def self.subjects
类型方法时,如何访问该 ActiveRecord::Relation 对象本身?
当在 Relation 对象上调用该方法时,self
是关系(与定义它的模型类相反)。
【讨论】:
请参阅下面的@Marco Prins 了解解决方案。 rails 5 中不推荐使用的 .push 方法怎么样 @GstjiSaini 我不确定您所指的确切方法,请提供文档或源链接。不过,如果它被弃用,这不是一个非常可行的解决方案,因为它可能很快就会消失。 这就是为什么你可以先class User; def self.somewhere; where(location: "somewhere"); end; end
,然后User.limit(5).somewhere
这是唯一真正启发 OP 的答案。这个问题揭示了对 ActiveRecord 如何工作的有缺陷的了解。 @Justin 只是加深了人们对为什么不停地传递对象数组只是为了映射它们并构建另一个不必要的查询是不好的缺乏理解。以上是关于将对象数组转换为 ActiveRecord::Relation的主要内容,如果未能解决你的问题,请参考以下文章