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

时间:2013-06-26 22:59:25

标签: ruby-on-rails ruby activerecord

我有一个对象数组,我们称之为Indicator。我想在这个数组上运行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. 在ActiveRecord :: Relation上运行def self.subjects类型方法时,如何访问ActiveRecord :: Relation对象本身?
  3. 感谢。如果我需要澄清任何事情,请告诉我。

5 个答案:

答案 0 :(得分:149)

您可以将对象数组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, ....

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

答案 1 :(得分:43)

  

如何将对象数组转换为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是关系(与其定义的模型类相对)。

答案 2 :(得分:4)

嗯,在我的情况下,我需要将一个对象数组转换为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

答案 3 :(得分: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"

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

答案 4 :(得分: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周内生产并经过检查的未完成的元件。为了避免创建新的作用域,您可以过滤到一个数组,然后再转换回去。请记住,您仍然要对数据库进行查询,因为它是通过id搜索的,但仍然是查询。