How to Pluck Like a Rails Pro
The pluck
method was first introduced in Rails 3.2. A quick duckduckgo search or a glance at the docs will explain how to use it and the performance benefits that come along with doing so.
While pluck
is not exactly the new kid on the Rails block these days, it is still new to someone out there and even the experienced pluck
er might pick up a less-obvious trick towards the end.
Before there was pluck
Before there was pluck
, ActiveRecord’s select
was the goto method to query individual columns.
[1] pry(main)> User.select(:first_name)
User Load (0.2ms) SELECT `users`.`first_name` FROM `users`
=> [#,
#,
#,
#]
As select
would return an array of User
objects, you would often find yourself needing to write additional code to get the desired result.
<a href=”http://guides.rubyonrails.org/active_record_querying.html#pluck
“></a>
[2] pry(main)> User.select(:first_name).map(&:first_name)
User Load (0.4ms) SELECT `users`.`first_name` FROM `users`
# => ["Eric", "Victor", "Daniel", "Jonathan"]
Pluck, a quick refresher
Like its cousin select
, pluck
allows you to query columns in a similar manner but without the added performance costs of object construction and without the need for additional code to get an array of values.
In its simplest form, pluck
allows you to query a single column from a single table.[1]
[3] pry(main)> User.pluck(:first_name)
(0.2ms) SELECT `users`.`first_name` FROM `users`
# => ["Eric", "Victor", "Daniel", "Jonathan"]
It is also chainable[2], meaning that you can use it with other scopes or any other method that returns an ActiveRecord::Relation
(e.g., where
).
[4] pry(main)> User.distinct.active.pluck(:first_name)
(0.3ms) SELECT `users`.`first_name` FROM `users` where `users`.`active` = 1
# => ["Eric", "Victor", "Daniel"]
And somewhere along the way pluck
gained the ability to query multiple columns from a single table. (Rails 4 only)
[5] pry(main)> User.active.pluck(:first_name, :last_name)
(0.3ms) SELECT `users`.`first_name`, `users`.`last_name` FROM `users`
# => [["Eric", "Milford"], ["Victor", "Sirotek"], ["Daniel", "Morrison"]]
You can also query columns across tables.
[6] pry(main)> User.active.joins(:roles).pluck(:first_name, :last_name, :title)
(0.4ms) SELECT `users`.`first_name`, `users`.`last_name`, `roles`.`title` FROM `users` INNER JOIN `user_roles` ON `user_roles`.`user_id` = `users`.`id` INNER JOIN `roles` ON `roles`.`id` = `user_roles`.`role_id`
# => [["Eric", "Milford", "Developer"], ["Victor", "Sirotek", "Designer"], ["Daniel", "Morrison", "Big Boss Man"]]
But did you know?
A lesser known and currently undocumented feature is the ability to pass valid SQL strings directly to pluck
. In fact, the query across multiple tables above would be better written in this manner.[3]
Along comes the need to query multiple columns with a specific format (e.g., fullname). You might start with code that utilizes pluck
to query the needed columns but that falls back to map + join to handle the formatting.
[7] pry(main)> User.active.pluck(:first_name, :last_name).map { |names| names.join(" ") }
(0.3ms) SELECT `users`.`first_name`, `users`.`last_name` FROM `users`
=> ["Eric Milford", "Victor Sirotek", "Daniel Morrison"]
It gets the job done and still capitalizes on the more-performant pluck
to query the columns. But now well-equipped with expert-level knowledge of pluck
you know that there is a better way.
[8] pry(main)> User.pluck(<<-PLUCK)
[8] pry(main)* CONCAT_WS(" ", users.first_name, users.last_name)
[8] pry(main)* PLUCK
(0.4ms) SELECT CONCAT_WS(" ", users.first_name, users.last_name) FROM `users`
# => ["Eric Milford", "Victor Sirotek", "Daniel Morrison"]
Summary
pluck
is a handy and more-performant way to query columns than using select
or mapping full objects. When used effectively, pluck
can really contribute to the performance of your application. And the ability to leverage underlying database features like CONCAT_WS make it all the more handy.
[1] To ensure a unique set of values is returned, use distinct e.g., User.distinct.pluck(:first_name)
[2] Note that pluck
must come at the end of the chain. Not doing so will result in a NoMethodError
, as pluck
returns an array, not a chainable ActiveRecord::Relation
.
[3] While not absolutely necessary, the pluck
query across multiple tables would be more safely written as pluck("users.first_name, users.last_name, roles.title")
to correctly scope columns to their respective table.
Comments
Thanks for this nifty refresher, I literally joined google+ yesterday and already you have given me an insight into how I might be able to deal with a view that generates fairly slowly. Thanks!
Thanks Eric!
Great explanation.
Thanks!
Awesome explanation! Thanks for this
I was working on a complex query and was pulling my hair thinking how to pull some data with the least cost in one query and ended up doing something probably shity but I didn’t give up looking and … there we go! :) That’s what I was looking for! Thank you so much.
Thanks! It’s a very cool trick to learn.
Here is an another way for getting uniq values:
User.pluck('DISTINCT first_name')
very nice article there. thanks.
Thanks for this nice helping tutorial. Keep it up sir.