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, usersreturn 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.subjectstype 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.