There are several noteworthy things happening in this snippet:
- Array destructuring using
*
pluck
Arel.sql
- Single record
Destructuring (*)
Pluck is defined as
pluck(*column_names)
which means it takes a variable amount of arguments.
If you'd call it with
pluck([:age, :name])
the method would see it as an array of arrays ([[:age, :name]]
)
so you have to "destructure" the array firs:
pluck(*[:age, :name])
or call it with single values in the first place:
pluck(:age, :name)
(which might not be possible if you receive the values as array, as in your case)
See this Thoughtbot post for more details
pluck
pluck
does two things:
- it modifies the
select
clause of the generated SQL statement
- and it bypasses creation of the ActiveRecord Model and returns the data only.
So the following lines return the same data
User.all.map { |user| [user.age, user.name] }
User.all.select(:age, :name).map { |user| [user.age, user.name] }
User.pluck(:age, :name)
The first is the least efficient: it selects ALL columns and creates the models just to discard them when calling map
The second is a bit more efficient in that it only selects the required columns. Still creates the models though.
pluck
is without the overhead of selecting all columns and creating the models. So it usually is faster and more memory efficient.
On the SQL level it boils down to the difference between
select * from users
and
select age, name from users
So IMHO pluck
is especially useful for APIs or when exporting to CSVs and similar.
Arel.sql
Rails wants you to wrap unsafe SQL in Arel.sql
. It is a security measure (that i personally find annoying and useless) and lets you know that you have a potentially unsafe operation that Arel/ActiveRecord can not check for you.
Single record
You can not call it on a single record (and it would also not make much sense, since the single record is already the result of a DB query that was executed and there a Model which has been constructed).
If you want something similar for a single record that you have already loaded from the DB:
user.attributes.values_at('age', 'name')
or you can limit the query to just return results for one record
User.where(id: 123).pluck(:age, :name) # only one record since PK
or
User.where(some conditions).limit(1).pluck(:age, :name)
Note that pluck
is somewhat inconsistent in its return values:
If you only pluck one attribute, then it returns an array of values
User.pluck(:name)
=> ["Carl", "Mike"]
but when you pluck multiple attributes it returns an array of arrays of values
User.pluck(:name, :age) => [["Carl", 21], ["Mike", 35]]