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 enjoys crafting well-architected web applications. With a career spanning nearly ten years, he has worked with established companies and small startups. An avid and passionate learner, Eric continues to grow as a developer and improve upon his craft.

Aside from coding, Eric enjoys singing and playing guitar, as well as performing improv comedy in his home of NYC. Having previously cooked professionally in fine dining, he is also learning to bake bread as an apprentice at SCRATCHBread in Brooklyn, NY.

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