---
Title: Enumurable#pluck and String#to_proc for Ruby
Author: Sami Samhuri
Date: 10th May, 2007
Timestamp: 1178838840
Tags: ruby, extensions
---
I wanted a method analogous to Prototype's pluck and invoke in Rails for building lists for options_for_select. Yes, I know about options_from_collection_for_select.
I wanted something more general that I can use anywhere - not just in Rails - so I wrote one. In a second I'll introduce Enumerable#pluck, but first we need some other methods to help implement it nicely.
First you need Symbol#to_proc, which shouldn't need an introduction. If you're using Rails you have this already.
class Symbol
# Turns a symbol into a proc.
#
# Example:
# # The same as people.map { |p| p.birthdate }
# people.map(&:birthdate)
#
def to_proc
Proc.new {|thing, *args| thing.send(self, *args)}
end
end
String#to_proc, which is nearly identical to the Array#to_proc method I previously wrote about.
class String
# Turns a string into a proc.
#
# Example:
# # The same as people.map { |p| p.birthdate.year }
# people.map(&'birthdate.year')
#
def to_proc
Proc.new do |*args|
split('.').inject(args.shift) do |thing, msg|
thing = thing.send(msg.to_sym, *args)
end
end
end
end
Enumerable#to_proc which returns a proc that passes its parameter through each of its members and collects their results. It's easier to explain by example.
module Enumerable
# Effectively treats itself as a list of transformations, and returns a proc
# which maps values to a list of the results of applying each transformation
# in that list to the value.
#
# Example:
# # The same as people.map { |p| [p.birthdate, p.email] }
# people.map(&[:birthdate, :email])
#
def to_proc
@procs ||= map(&:to_proc)
Proc.new do |thing, *args|
@procs.map do |proc|
proc.call(thing, *args)
end
end
end
endEnumerable#pluck for Ruby in all its glory.
module Enumerable
# Use this to pluck values from objects, especially useful for ActiveRecord models.
# This is analogous to Prototype's Enumerable.pluck method but more powerful.
#
# You can pluck values simply, like so:
# >> people.pluck(:last_name) #=> ['Samhuri', 'Jones', ...]
#
# But with Symbol#to_proc defined this is effectively the same as:
# >> people.map(&:last_name) #=> ['Samhuri', 'Jones', ...]
#
# Where pluck's power becomes evident is when you want to do something like:
# >> people.pluck(:name, :address, :phone)
# #=> [['Johnny Canuck', '123 Maple Lane', '416-555-124'], ...]
#
# Instead of:
# >> people.map { |p| [p.name, p.address, p.phone] }
#
# # map each person to: [person.country.code, person.id]
# >> people.pluck('country.code', :id)
# #=> [['US', 1], ['CA', 2], ...]
#
def pluck(*args)
# Thanks to Symbol#to_proc, Enumerable#to_proc and String#to_proc this Just Works(tm)
map(&args)
end
end#to_proc methods so as to work with a standard Ruby while only patching 1 module.
module Enumerable
# A version of pluck which doesn't require any to_proc methods.
def pluck(*args)
procs = args.map do |msgs|
# always operate on lists of messages
if String === msgs
msgs = msgs.split('.').map {|a| a.to_sym} # allow 'country.code'
elsif !(Enumerable === msgs)
msgs = [msgs]
end
Proc.new do |orig|
msgs.inject(orig) { |thing, msg| thing = thing.send(msg) }
end
end
if procs.size == 1
map(&procs.first)
else
map do |thing|
procs.map { |proc| proc.call(thing) }
end
end
end
endEnumerable#to_proc by saving the results of to_proc in @procs.*