Thursday, September 12, 2013

Pluck Your Colon, or, Concisifying Your Ruby on Rails Code

   Recently I encountered some Ruby code that looked like:
ids = holder.things.collect { |thing| thing.id }
(I prefer to say map rather than collect, but they're really the same thing.  Which one you use is largely a matter of taste, influenced by what languages you've used in the past, and your laziness in typing.)

   There are two small successive improvements that can be made to this.  First, when you have any code of the form:
bunch_of_things.collect { |thing| thing.some_method }
(and remember, retrieving a data-member of an object is a method!) you can shorten that to:
bunch_of_things.collect(&:some_method)
   This uses the & shorthand for Ruby's to_proc method.  Long story short, the : makes a Symbol, and the & calls to_proc on that.  collect will send that to each item in turn, making it behave just like a block explicitly calling it on each item.  (I won't go into the nitty-gritty details here of how that works; if you care, investigate Ruby's yield keyword.)

   For example, if you have a block of numbers and you want to get their even-ness, you can do:
[1, 2, 3, 5, 8].map(&:even?)
# => [false, true, false, false, true]
  You can also use the &: trick with block-taking methods other than collect/map, such as inject/reduce:
[1, 2, 3, 4, 5].inject(&:+)
# => 15
though of course inject will want a method that takes an argument.  (Why this is so, is left as an argument for the reader.)

   Sometimes you can omit the &.  I'm not sure exactly what the rule is, or even if there is one.  At the cost of one more character, you may as well just always use it.

  Back to our original code, though, there's another trick we can use to simplify this.

  ActiveRecord provides a method called pluck... and we were indeed using ActiveRecord.  pluck sets the SQL SELECT statement to retrieve only the columns you want.  The result is an array of the values ready to be used by your program.  (If you give it more than one column to pluck, the values are themselves arrays.  However, in this case, as in the vast majority, we were only interested in one column.)  Not only does this often make the results easier to deal with, it can also help deal with a large dataset by saving i/o between the database and your application, memory on both ends, etc.

   So, rather than go through the hoops of retrieving the things associated with holder, and then looping through them to extract the id column, this could be written more simply as:
ids = holder.things.pluck(:id)
   What are some of your favorite Ruby (or Rails) idioms for making common code more concise (short but still clear)?

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.