Learning new things about Ruby on Rails every day
I have been working with Rails for over 2 years now, and still i manage to discover new stuff while crawling around the source code.
Seeing as i have missed some really cool things, i decided to share some of those small discoveries with everyone else.
OrderedHash
I have seen a lot of discussion and help requests about ruby Hashes and maintaining its key order.
Well i just stumbled upon the implementation of an ordered hash today.
It’s namespaced inside ActiveSupport
module, and it basically works just like a regular Hash
, but with the additional feature of maintaining the order of keys accord to the insertion order.
hash.store(:first, 1)
hash[:second] = 2
So it pretty much works like a regular hash, although you have to use the namespaced constructor to create one.
The reason behind it is that it would’nt shadow the other implementations of OrderedHash
(for example Ruby 1.9 already has feature this built in, which also means that ActiveSupport::OrderedHash
then references to ::Hash
).
Enumerable.group_by
I had the need to group an array of association objects by one field – that mean by abusing inject and building a new hash of items by looping through the whole collection.
And just today i accidentally stumbled upon Enumerable.group_by
method. It does exactly what you would expect – it groups set elements by method/attribute.
This feature is actually already implemented in Ruby 1.8.7, but Rails core extension removes that method and replaces it with it’s own.
The reason behind it is that in Ruby’s own implementation, the results are not ordered, as in Rails’ implementation is built on top of ActiveSupport::OrderedHash
.
has_many :order_items
end
# our example order_items would contain items from vendors named Eckö, Nike, Abercrombie
order_items = order.order_items.all(:order => :vendor_name)
order_items.group_by(:&vendor_name)
=> #<OrderedHash {"Abercrombie" => [#<OrderItem ..>, #<OrderItem..>], "Eckö" => [#<OrderItem...>], "Nike" => [#<OrderItem>]}>
In this example i ask for all order items, order them by vendor name and then group them up by the same column. This results in a ordered hash, where the key is the vendor name (in the order i want) with value being an array of all matching order items with that vendor.
AssociationCollection.any?
You probably have seen, or even used Array/Hash
methods any?
or empty?
You probably also have discovered that an association acts a lot like an array, with having both of those methods.
My personal expectation, when seeing these methods, was that when calling association.any?
, it would fetch all the record from the database and then calls Array.any?
on it.
As i usually do not want to load the whole association to check if there are any records in it, i then opted for doing association.count > 0
(which does a SELECT COUNT(*)
etc query on the database).
And once again, crawling around the source i discovered that Rails is actually pretty clever and implements both mentioned methods on top of count queries.
=> "SELECT COUNT(*) FROM order_items WHERE order_id = 1"
# Snippet from AssociationCollection
def empty?
size.zero?
end
def any?
if block_given?
method_missing(:any?) { |*block_args| yield(*block_args) }
else
!empty?
end
end
So AssociationCollection.any?
is implemented on top of AssociationCollection.empty?
, which is implemented on top of AssociationCollection.size
.
And by the way, AssociationCollection.size
does not hit the database if the collection is already loaded, if not, it does a simple count query.
I hope this stuff will be useful for someone at some point and i will also try and share any neat discoveries with you in the future.
P.S Shoperb, our new no-hassle e-commerce solution, is shaping up really well.
4 Comments
The DrinkRails blog linked to your post.
Thanks a lot, it’s good to know this stuff interests other fellow developers out there also.
Didn’t know about the group_by method. This would definitely come in handy. Thanks!