(or Sign up) :

Super Easy Collect For ActiveRecord Arrays

Dec 09

Show_hide

Disclaimer: Yes I'm fully aware the 99.9% of the people who read this site will not only have no idea what I'm talking about, but wouldn't care even if they did. But for the .1% who do, this is really, really cool! :-)

Recently at work I've been writing a Ruby On Rails application for our Network group to monitor and track our environment. The fellow I've been working with has written a bunch of really amazing Perl scripts that scour the network and collect lots of information about all of the devices. I'm writing the Rails front end to work with that data, and in the process teaching him Ruby (and Rails), so once we're done he can take it over and maintain the app. It was while showing him some things in IRB that I discovered an area for improvement that would really make working with a collection of ActiveRecord objects easier and the code far more beautiful.

Here's how it worked:

>> locations = Location.find(:all)

>> just_the_names_and_ids = locations.collect{|loc| [loc.id, loc.name]}
=> [[1, "New York Zone"], [2, "Los Angeles Zone"], [4, "Boston Zone"],
[5, "San Francisco Zone"], [6, "Development Center"]]

That's really groovy, and something that every Rubyist does in his/her sleep. The syntax for collect, being a block can be a bit confusing for a new programmer though. Then I remembered the ActiveRecord finder syntax.

Location.find_by_city_and_function("Los Angeles", "Production")

Why can't we do the same sort of thing with collect? Now you can!

>> locations = Location.find(:all)

>> just_the_names_and_ids = locations.collect_id_and_name
=> [[1, "New York Zone"], [2, "Los Angeles Zone"], [4, "Boston Zone"],
[5, "San Francisco Zone"], [6, "Development Center"]]

>> just_the_names = locations.collect_name
=> [["New York Zone"], ["Los Angeles Zone"], ["Boston Zone"],
["San Francisco Zone"], ["Development Center"]]

>> names_and_cities = locations.collect_name_and_city
=> [["New York Zone", "New York"], ["Los Angeles Zone", "Los Angeles"],
["Boston Zone", "Boston"], ["San Francisco Zone", "San Francisco"],
["Development Center", "Chicago"]]

I love it! It's so much more readable and "Rails like" that I'm really surprised that it wasn't something built in. But since it's so easy to extend Ruby (and Rails) it takes just a tiny bit of code to add that functionality in. Just paste the code below into your environment.rb and you're good to go.

class Array

def method_missing(method_id, *arguments)
if match = /collect_([_a-zA-Z]\w*)/.match(method_id.to_s)
attributes = match.captures.last.split('_and_')
self.collect{|array| attributes.collect{|attr| array[attr.to_sym]}}
else
super
end
end
end

This could easily be made into a plugin, or stuck into a .rb file in the lib directory and required. It's up to you. Hopefully someone out there will find this handy. I know I have.

Caveats: This won't work with included associations. Example:

Device.find(:all, :include => :location).collect_name_and_city

That will return a collection with the device names, but city will be nil because it was an included association. It would be easy to extend the functionality to support that, but I couldn't think of a clean syntax to allow it. So I figure it's best to keep it as it is.

Finally, this will work on Array's of hashes too, and not just ActiveRecord objects.

>> pets = [{:name => "Browser", :kind => "Dog"}, 

{:name => "Fluffy", :kind => "Cat"},
{:name => "Noriko", :kind => "Japanese Actress"}]

>> pets.collect_name_and_kind
=> [["Browser", "Dog"], ["Fluffy", "Cat"], ["Noriko", "Japanese Actress"]]

2 responses to Super Easy Collect For ActiveRecord Arrays

  1. Peter 2 days later said:

    Insane cool trick!!
    I wish every Ruby Coder had such a cute header !!

    happy_coding

  2. John Gibbons 3 days later said:

    heh, thanks Peter. :-)

Post your comment below

Name:

Website:

Email:

Your comment: