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 plucker 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.

Photo of Eric Milford

Eric’s career in developing well-architected web applications spans over a decade. He’s worked for established companies and small startups.

Comments:


Post a Comment

(optional)
(optional — will be included as a link.)
  1. 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!

    dahenderson@csumb.edu
    David Henderson
    January 13, 2017 at 21:43 PM
  2. Thanks Eric!
    Great explanation.

    July 11, 2017 at 1:29 AM
  3. Thanks!

    wtds.trabalho@gmail.com
    Wellington Torrejais da Silva
    November 07, 2017 at 13:37 PM
  4. Awesome explanation! Thanks for this

    Chuks
    January 10, 2018 at 15:06 PM