I have an array of objects, let\'s call it an Indicator
. I want to run Indicator class methods (those of the def self.subjects
variety, scopes, etc
You can convert an array of objects arr
to an ActiveRecord::Relation like this (assuming you know which class the objects are, which you probably do)
MyModel.where(id: arr.map(&:id))
You have to use where
though, it's a useful tool which you shouldn't be reluctant to use. And now you have a one-liner converting an array to a relation.
map(&:id)
will turn your array of objects to an array containing only their id's. And passing an array to a where clause will generate a SQL statement with IN
that looks something like:
SELECT .... WHERE `my_models`.id IN (2, 3, 4, 6, ....
Keep in mind that the ordering of the array will be lost - But since your objective is only to run a class method on the collection of these objects, I assume it won't be a problem.
ActiveRecord::Relation
binds database query which retrieves data from database.
Suppose to make sense, We have array with objects of same class, then with which query we suppose to bind them?
When I run,
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
Here in above, users
return Relation
object but binds database query behind it and you can view it,
users.to_sql
=> "SELECT `users`.* FROM `users` WHERE `users`.`id` IN (1, 3, 4, 5) ORDER BY created_at desc"
So it is not possible to return ActiveRecord::Relation
from array of objects which is independent of sql query.
How can I convert an Array of objects to an ActiveRecord::Relation? Preferably without doing a where each time.
You cannot convert an Array to an ActiveRecord::Relation since a Relation is just a builder for a SQL query and its methods do not operate on actual data.
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.
When running a
def self.subjects
type method on an ActiveRecord::Relation, how do I access that ActiveRecord::Relation object itself?
When the method is called on a Relation object, self
is the relation (as opposed to the model class it’s defined in).
Well, in my case, I need to converting an array of objects to ActiveRecord::Relation as well as sorting them with a specific column(id for instance). Since I'm using MySQL, the field function could be helpful.
MyModel.where('id in (?)',ids).order("field(id,#{ids.join(",")})")
The SQL looks like:
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
First of all, this is NOT a silver bullet. Out of my experience, I found that converting to relation is sometimes easier than alternatives. I try to use this approach very sparingly and only in cases where the alternative would be more complex.
That being said here is my solution, I've extended Array
class
# 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
A usage example would be array.to_activerecord_relation.update_all(status: 'finished')
. Now where do I use it?
Sometimes you need to filter out ActiveRecord::Relation
for example take out not completed elements. In those cases best is to use scope elements.not_finished
and you would still keep ActiveRecord::Relation
.
But sometimes that condition is more complex. Take out all elements that are not finished, and that has been produced in the last 4 weeks and have been inspected. To avoid creating new scopes you can filter to an array and then convert back. Keep in mind that you still do a query to DB, quick since it searches by id
but still a query.