将对象数组转换为ActiveRecord :: Relation


104

我有一个对象数组,我们称它为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

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

  1. 如何将对象数组转换为ActiveRecord :: Relation?最好不要where每次都做。
  2. def self.subjectsActiveRecord :: Relation上运行类型方法时,如何访问该ActiveRecord :: Relation对象本身?

谢谢。如果我需要澄清任何事情,请告诉我。


3
如果您尝试将该数组转换回关系的唯一原因是通过.all,则可以使用它,.scoped就像Andrew Marshall的答案所示(尽管在rails 4中可以使用.all)。如果您发现自己需要将数组转换为关系,那么您在某个地方出错了
nzifnab 2013年

Answers:


46

如何将对象数组转换为ActiveRecord :: Relation?最好每次不做一次。

您不能将数组转换为ActiveRecord :: Relation,因为Relation只是SQL查询的构建器,并且其方法不能对实际数据进行操作。

但是,如果您想要的是关系,则:

  • 对于ActiveRecord 3.x,不要调用all,而是调用scoped,这会返回一个Relation,它表示all将在数组中提供给您的记录。

  • 对于ActiveRecord 4.x,只需调用,即可all返回一个Relation。

def self.subjectsActiveRecord :: Relation上运行类型方法时,如何访问该ActiveRecord :: Relation对象本身?

当在Relation对象上调用该方法时,self该关系就是该关系(与其定义在其中的模型类相对)。


1
有关解决方案,请参见下面的@Marco Prins。
贾斯汀

在rails 5中不推荐使用.push方法吗
Jaswinder

@GstjiSaini我不确定您所指的确切方法,请提供文档或源链接。但是,如果过时了,那不是一个非常可行的解决方案,因为它可能很快就会消失。
安德鲁·马歇尔

那就是为什么你可以这样做class User; def self.somewhere; where(location: "somewhere"); end; end,那么User.limit(5).somewhere
Dorian

这是真正启发OP的唯一答案。这个问题揭示了关于ActiveRecord如何工作的有缺陷的知识。@Justin只是在加深对为什么为什么要不断传递对象数组以映射它们并构建另一个不必要的查询的原因感到缺乏了解。
james2m

162

您可以arr像这样将对象数组转换为ActiveRecord :: Relation(假设您知道对象是哪个类,您可能会做什么)

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

where但是,您必须使用它,它是一个有用的工具,您不应不愿意使用它。现在,您有了将数组转换为关系的单行代码。

map(&:id)会将您的对象数组转换为仅包含其ID的数组。并将数组传递给where子句将生成一条SQL语句,IN其内容类似于:

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

请记住,数组的顺序将丢失 -但由于您的目标只是在这些对象的集合上运行类方法,因此我认为这不会成为问题。


3
做得好,正是我所需要的。您可以将其用于模型上的任何属性。非常适合我。
nfriend21 2014年

7
为什么要自己构建文字SQL where(id: arr.map(&:id))?严格来说,这不会将数组转换为关系,而是获取对象的新实例(一旦实现关系),这些实例的ID可能具有与数组中已存在的内存实例不同的属性值。
安德鲁·马歇尔

7
但是,这会失去顺序。
Velizar Hristov 2015年

1
@VelizarHristov那是因为它现在是一个关系,只能按列进行排序,而不能按您希望的任何方式进行排序。关系处理大型数据集的速度更快,并且需要权衡取舍。
Marco Prins

8
效率很低!您已经将已经在内存中的对象集合变成了要进行数据库查询访问的对象。我将看一下重构要在数组上迭代的那些类方法。
james2m

5

好吧,以我为例,我需要将对象数组转换为ActiveRecord :: Relation并使用特定的列(例如,id)它们进行排序。由于我使用的是MySQL,因此field函数可能会有所帮助。

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字段功能


对于可与PostgreSQL一起使用的版本,请看以下线程:stackoverflow.com/questions/1309624/…–
扶手椅

0

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"

因此,不可能ActiveRecord::Relation从独立于sql查询的对象数组中返回。


0

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

话虽如此,这是我的解决方案,我扩展了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查询但仍然是查询,因此查询很快。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.