Man, the Ruby Inside Advent Calendar 2006 is pretty cool. That guy from Err the Blog even made a guest post about Piston. Too bad there isn’t a Rails-specific advent calendar…
Well, hold that thought. We do everything faster in Rails, right? Ten times faster? How about twenty-five times faster? Let’s give it a shot.
Following, my friends, is a Rubyisms in Rails Advent Calendar, starring ActiveSupport. Twenty-five little Christmasy nuggets condensed into one blog post. Twenty-five days in one. That’s how we roll.
ActiveSupport is, of course, home to much Ruby-fu in Rails. All Rails’ cool extensions to Ruby live in ActiveSupport. Here, then, is my Christmasy gift to you: a guide of sorts. Enjoy.
Oh, wait. I need to say: I culled these methods from the Edge version of Rails. Sorry, 1.1.6ers. You’re just going have to wait until 1.2 for all these goodies. Carry on.
Seven Satisfying Stringy Shortcuts [ String Methods ]
Day 1: at (String)
Ever find yourself wanting the first letter of a string and accidently getting back the code of that character? Yeah. Never again.
>> "Finally, something useful!".at(6) => "y"
Day 2: from and to (String)
Like slice for arrays, kinda. Give either method a number and you’ll get the rest of the string from or to that character.
>> "Chris the Person".from(6) => "the Person" >> "Chris the Person".to(4) => "Chris"
Of course, we could just as easily use…
Day 3: first and last (String)
Two of my favorite array methods, Rails (of course) adds them onto String.
>> "Christmas Time".first => "C" >> "Christmas Time".first(5) => "Chris" >> "Christmas Time".last => "e" >> "Christmas Time".last(4) => "Time"
Day 4: each_char (String)
Treat a string like an array of characters with this little number. Multi-byte safe, too.
>> "Snow".each_char { |i| print i.upcase } SNOW
Day 5: starts_with? and ends_with? (String)
Sure, it’d be real easy to do this check with a regular expression, but Ruby is all about reading nicely. And these methods sure read nicely.
>> "Peanut Butter".starts_with? 'Peanut' => true >> "Peanut Butter".ends_with? 'Nutter' => false
Day 6: to_time and to_date (String)
A little bit of love reminiscent of to_a for strings.
>> "1985-03-13".to_time => Wed Mar 13 00:00:00 UTC 1985 >> "1985-03-13".to_date => #<Date: 4892275/2,0,2299161>
Day 7: transformations! (String)
Oh, so many. Are you still using Inflector.pluralize(string)? Forget it. Try some of these methods on for size:
pluralize, singularize, camelize, titleize, underscore, dasherize, demodulize, tableize, classify, humanize, foreign_key, and constantize
>> "reindeer".pluralize => "reindeers" >> "elves".singularize => "elf" >> "christmas_carol".camelize => "ChristmasCarol" >> "christmas_carol".camelize(:lower) => "christmasCarol" >> "holiday_cheer".titleize => "Holiday Cheer" >> "AdventCalendar-2006".underscore => "advent_calendar_2006" >> "santa_Claus".dasherize => "santa-Claus" >> "Holiday::December::Christmas".demodulize => "Christmas" >> "SnowStorm".tableize => "snow_storms" >> "snow_storms".classify => "SnowStorm" >> "present_id".humanize => "Present" >> "Present".foreign_key => "present_id" >> "Cheer".constantize NameError: uninitialized constant Cheer >> "Christmas".constantize => Christmas
An Array of Atypical Alterations [ Array Methods ]
Day 8: to_sentence (Array)
Use to join elements of an array into a more English-friendly representation.
>> %w[Chris Mark Steven].to_sentence => "Chris, Mark, and Steven"
You can pass it two options: :connector and skip_last_comma. They work like a’this:
>> %w[Soap Mocha Chocolate].to_sentence(:connector => '&') => "Soap, Mocha, & Chocolate" >> %w[Ratatat Interpol Beirut].to_sentence(:skip_last_comma => true) => "Ratatat, Interpol and Beirut"
Day 9: to_param (Array)
This guy, in case you’re unawares, is the method Rails calls on any object when trying to figure out how the object should be represented in a URL.
For instance, on an ActiveRecord::Base object:
>> user = User.find(:first) => #<User:0x2905504 ... > >> helper.url_for :controller => 'users', :action => 'show', :id => user => "/users/1"
Now let’s say we override the to_param instance method on User to return name:
>> user = User.find(:first) => #<User:0x28fe31c ... > >> helper.url_for :controller => 'users', :action => 'show', :id => user => "/users/chris"
Cool. That said, the Array version of to_param joins elements with a /.
>> helper.url_for :controller => 'users', :action => 'show', :id => %w[one two three] => "/users/one%2Ftwo%2Fthree"
(That’s escaped, of course.) This is so you can easily send and deal with catch-all routes. This thread on RailsForum goes into catch-all routes. You may or may not find them useful.
Day 10: to_s (Array)
Sure, you know Array has a to_s, but did you know Rails overrides it to accept a format (:db) parameter? That’s right! Check it out:
>> array_of_posts = Post.find(:all, :limit => 3) => [#<Post:0x28c1ef8 ... >, #<Post:0x28c1de0 ... >, #<Post:0x28c1d54 ... >] >> array_of_posts.to_s(:db) => "1,2,3" >> [].to_s => "" >> [].to_s(:db) => "null"
This really only works with ActiveRecord objects, as Rails tries to call #id on the elements of the array. Unlike Time, this to_s currently doesn’t provide a hook for you to add more formats. One day? One day.
Day 11: to_xml (Array)
Another more-or-less ActiveRecord specific extension, you may call to_xml on an array when all the elements of the array respond to to_xml. Like, you know, an array of ActiveRecord objects.
>> puts array_of_posts.to_xml => "<?xml version=\"1.0\" encoding=\"UTF-8\"?> <posts> <post> ... tons of xml ... </post> </posts>
Pass in :skip_instruct to omit the <?xml version… line. You can also provide an :indent option (default: 2), a :builder option (you probably want to stick with the default of Builder::XmlMarkup), a :root option (defaults to the lowercase plural name of the class, posts in this case), and a children option (singular lowercase class—post for me).
Day 12: in_groups_of (Array)
The docs have this one covered pretty well: “Iterate over an array in groups of a certain size, padding any remaining slots with specified value (nil by default) unless it is false.“
And, three examples. This is almost cheating. But here:
>> %w[1 2 3 4 5 6 7].in_groups_of(3) { |g| p g } ["1", "2", "3"] ["4", "5", "6"] ["7", nil, nil] >> %w[1 2 3].in_groups_of(2, ' ') { |g| p g } ["1", "2"] ["3", " "] >> %w[1 2 3].in_groups_of(2, false) { |g| p g } ["1", "2"] ["3"] >> %w[1 2 3 4 5 6 7].in_groups_of(3) => [["1", "2", "3"], ["4", "5", "6"], ["7", nil, nil]]
Day 13: split (Array)
Cuts up an array into smaller arrays in the same way String.split cuts up a string into arrays. Can take a parameter (a value to cut on) or a block (where the result is what’s used for the cuttin’).
>> %w[Tom Jerry and Mickey and Pluto].split('and') => [["Tom", "Jerry"], ["Mickey"], ["Pluto"]] >> %w[Chris Mark Adam Tommy Martin Oliver].split { |name| name.first == 'M' } => [["Chris"], ["Adam", "Tommy"], ["Oliver"]]
Some Call Them ‘Home Fries’ [ Hash Methods ]
Day 14: stringify_keys and symbolize_keys (Hash)
These two simple methods help keep us developers lazy. Descriptive names. Both methods return a new hash but also have bang equivalents (like symbolize_keys!) which modify the receiver in place.
>> { 'days' => 25, 'spirit' => 'giving', 'wallet' => 'empty' }.symbolize_keys => {:wallet=>"empty", :spirit=>"giving", :days=>25} >> { 'system' => 'wii', :valid_ages => 5..90 }.stringify_keys => {"valid_ages"=>5..90, "system"=>"wii"}
Day 15: assert_valid_keys (Hash)
Ever try and slip your own keys into an ActiveRecord#find or somesuch? Rails isn’t having any of it, thanks to assert_valid_keys. Use this bad boy when sprinkling vinegar over your APIs.
>> about_me = { :height => 71, :weight => 160, :likes => 'monster trucks' => {:height=>71, :likes=>"monster trucks", :weight=>160} >> about_me.assert_valid_keys(:height, :weight, :age) ArgumentError: Unknown key(s): likes
Day 16: reverse_merge (Hash)
It’s like Hash#merge, but backwards! Also we gots reverse_merge! and reverse_update, which modify in place. Mostly useful to mass-set hash keys that haven’t already been set.
>> colors = { :foreground => 'red', :background => 'black' } => {:background=>"black", :foreground=>"red"} >> colors.merge(:background => 'green') => {:background=>"green", :foreground=>"red"} # doesn't override our set colors but would add in :background # if we didn't already have it: >> colors.reverse_merge(:background => 'green') => {:background=>"black", :foreground=>"red"}
Day 17: diff (Hash)
Kind of weird, but kind of useful, Hash#diff shows you what parts of hash A have been changed or removed in the transition to hash B.
Here, I’ll setup my name, then change my middle name, then totally throw away my middle name. Both times (altered and thrown away) I can see exactly what changed.
>> name = { :first => 'Chris', :last => 'Wanstrath', :middle => 'Jebediah' } => {:first=>"Chris", :last=>"Wanstrath", :middle=>"Jebediah"} >> name.diff(:first => 'Chris', :last => 'Wanstrath', :middle => 'Jonesy') => {:middle=>"Jebediah"} >> name.diff(:first => 'Chris', :last => 'Wanstrath') => {:middle=>"Jebediah"}
(My middle name isn’t really Jebediah, but that’d sure be a nice Christmas gift if anyone is in the giving mood.)
Day 18: to_xml and from_xml (Hash)
Okay, to_xml already got a mention with Array, but there’s a catch here: from_xml. Use it to turn XML documents into Ruby-friendly hashes (cough activeresource cough). Here’s me playing with it. And hey, look! It even respects arrays. Very cool.
>> Hash.from_xml '<posts><post><id>1</id></post></posts>' => {"posts"=>{"post"=>{"id"=>"1"}}} >> Hash.from_xml '<posts><post><id>1</id></post><post><id>2</id></post></posts>' => {"posts"=>{"post"=>[{"id"=>"1"}, {"id"=>"2"}]}}
Numerically Elvish Helpers [ Numeric (Integer, Float, etc) Methods ]
Day 19: bytes (Numeric)
Get ya’ bytes, get ya’ bytes! Maybe you’ve seen 2.days or 15.minutes before. Same deal, but in all the flavors of computer food: bytes. (They all come in plural and singular versions, btw.)
bytes, kilobytes, megabytes, gigabytes, terabytes, petabytes, and exabytes
>> 100.bytes => 100 >> 5.kilobytes => 5120 >> 10.megabytes => 10485760 >> 100.gigabytes => 107374182400 >> 2.terabytes => 2199023255552 >> 1.petabytes => 1125899906842624 >> 2.exabytes => 2305843009213693952
Day 20: days and months and years, oh my (Numeric)
Okay I know I just referenced this so you must have heard of it, but here’s the complete list of 5.days-style number helpers (all come in singular and plural as well):
seconds, minutes, hours, days, weeks, fortnights, months, years, ago / until, since / from_now
>> 15.seconds => 15 >> 2.minutes => 120 >> 30.hours => 108000 >> 1.days => 86400 >> 2.weeks => 1209600 >> 4.fortnights => 4838400 >> 2.months => 5184000 >> 17.years => 536479200 >> 2.days.ago => Sat Dec 16 00:34:49 -0800 2006 >> 2.days.ago(Time.now - 3.days) => Wed Dec 13 00:34:55 -0800 2006 >> 4.weeks.since("1985-03-13".to_time) => Wed Apr 10 00:00:00 UTC 1985
A Quicky Inty [ Integer Methods ]
Day 21: ordinalize (Integer)
Like pluralize and the like for string, ordinalize makes a number seem more official:
>> 5.ordinalize => "5th"
Day 22: even? and odd? (and multple_of?) (Integer)
Why aren’t these in Ruby? So awesome and simple. Actually, why isn’t all this stuff in Ruby? I digress, I guess.
>> 2.even? => true >> 2.odd? => false >> 99.multiple_of? 60 => false >> 25.multiple_of? 5 => true
Enumerating Your Emotions [ Enumerable (Hash, Array, etc.) Methods ]
Day 23: group_by (Enumerable)
Much like the SQL GROUP BY supra-command, this handy method allows you to group elements within an enumerable by any arbitrary way you desire. Here I’ll group an array of hashes by their preferred color.
>> magical_people = [ { :name => "Santa Claus", :color => "Red" }, { :name => "Mrs Claus", :color => "Red" }, { :name => "Twinkle the Elf", :color => "Green "} ] => [{:name=>"Santa Claus", :color=>"Red"}, {:name=>"Mrs Claus", :color=>"Red"}, {:name=>"Twinkle the Elf", :color=>"green "}] >> magical_people.group_by { |person| person[:color] } => {"Green "=>[{:name=>"Twinkle the Elf", :color=>"green "}], "Red"=>[{:name=>"Santa Claus", :color=>"Red"}, {:name=>"Mrs Claus", :color=>"Red"}]}
Day 24: index_by (Enumerable)
Man, how many times have you wanted to take an array and turn it into a hash keyed by some sort of attribute? Like maybe the ID of the element or its name? All the friggin’ time, right? Again, never again.
>> beatles = [{ :first => 'John', :last => 'Lennon' }, { :first => 'Paul', :last => 'McCartney' }, { :first => 'Evan', :last => 'Weaver' }, { :first => 'Ringo', :last => 'Starr' }] => [{:first=>"John", :last=>"Lennon"}, {:first=>"Paul", :last=>"McCartney"}, {:first=>"Evan", :last=>"Weaver"}, {:first=>"Ringo", :last=>"Starr"}] >> beatles.index_by { |beatle| beatle[:first] } => {"Evan"=>{:first=>"Evan", :last=>"Weaver"}, "Paul"=>{:first=>"Paul", :last=>"McCartney"}, "John"=>{:first=>"John", :last=>"Lennon"}, "Ringo"=>{:first=>"Ringo", :last=>"Starr"}}
Day 25: sum (Enumerable)
It’s just simple. Hit it up straight or pass it a block. Really, a nice shortcut for inject.
>> [1,2,3,4,5].sum => 15 >> Recipe.find(:all).sum { |recipe| recipe.total_time.to_i } => 1777
(Oh, I’m gonna keep going. I can’t just stop at 25. Maybe I could have retroactively started in mid-November or something? Anyway: a few more.)
Metaliciously Delicious Enrichments [ Metaprogramming Methods ]
Day 26: alias_method_chain
If you haven’t yet dug into Rails’ internals, here’s a bit of a peak: Rails makes heavy use of the pattern encapsulated in alias_method_chain. Many methods you see (like, say, render) are actually layers upon layers of other methods, all affecting behavior in some way or another. Sounds scary, doesn’t it? Nay! For sooth, you can harness this power for your own plugins and power tools.
Let’s say you wanted to add logging to the link_to method, for some reason. It’d be simple with alias_method_chain
def link_to_with_special_logging(*args) logger.debug "link_to called with args: #{args.inspect}" if logger link_to_without_special_logging(*args) end alias_method_chain :link_to, :special_logging
Now link_to will be renamed link_to_without_special_logging and link_to_with_special_logging will be renamed link_to by Rails. This lets me re-define a method which lovingly wraps an existing method, adding functionality. In your logs you may see _with_filters and _without_benchmarks sprinkled throughout. This is why. Cool.
Day 27: alias_attribute
Ever find yourself writing something like def name; title end in your Rails models? Maybe alias :name :title? I’ll do you one better: alias_attribute. It gives you an aliased getter method as well as a setter and a query method. Clean, too.
class User < ActiveRecord::Base alias_attribute :user_id, :id end >> user = User.find(:first) => #<User:0x12622fc ... > >> user.id => 1 >> user.user_id => 1 >> user.user_id? => true
Day 28: attr_accessor_with_default
A recent addition to Rails, this method wraps up the pattern of defining an attr_accessor on a class and having to set a default value upon instantiation. It puts everything in one place.
class Homework attr_accessor_with_default :sucks, true end >> assignment = Homework.new => #<Homework:0x2082f94> >> assignment.sucks => true
Day 29: class_inheritable_reader & class_inheritable_writer & friends
There’s a whole handful of these. What they do is setup a class variable which subclasses receive on instantiation, but do not share. So if class Parent has variable Name set to Wrecker, class Child will have Name set to Wrecker until you change it in Child. The change will not affect Parent.
class_inheritable_reader, class_inheritable_writer, class_inheritable_array_writer, class_inheritable_hash_writer, class_inheritable_accessor, class_inheritable_array, class_inheritable_hash
class Momma class_inheritable_hash :looks self.looks = { :hair => 'blonde', :eyes => 'blue' } end class Kid < Momma end => {:hair=>"blonde", :eyes=>"blue"} >> Momma.looks => {:hair=>"blonde", :eyes=>"blue"} >> Kid.looks => {:hair=>"blonde", :eyes=>"blue"} >> Kid.looks.update :eyes => "brown" => {:hair=>"blonde", :eyes=>"brown"} >> Kid.looks => {:hair=>"blonde", :eyes=>"brown"} >> Momma.looks => {:hair=>"blonde", :eyes=>"blue"}
The Leftovers: Two Friends Without Friends [ Misc Methods ]
Day 30: Range#to_s(:db)
Like the other special to_s methods, this one really only works with dates. But damn is it handy.
>> (7.days.ago..1.day.ago).to_s(:db) => "BETWEEN '2006-12-11 02:06:50' AND '2006-12-17 02:06:50'"
Day 31: Time Calculations
There’s a bunch of time calculations, but some of them (like Time.now.months_ago(2)) you can access more sexily through their numeric-based counterparts (Day 20 above). So, I’m just going to go through the useful ones.
For posterity’s sake, here’s the rdoc.
>> Time.days_in_month(2) => 28 >> Time.now.seconds_since_midnight => 8709.840965 # last_year, next_year, last_month, next_month >> Time.now.last_year => Sun Dec 18 02:25:59 -0800 2005 >> Time.now.next_month => Thu Jan 18 02:26:41 -0800 2007 # beginning_of_day, end_of_day, beginning_of_month, end_of_month # beginning_of_quarter, beginning_of_year >> Time.now.beginning_of_day => Mon Dec 18 00:00:00 -0800 2006 # yesterday, tomorrow, next_week(day = :monday) >> Time.now.tomorrow => Tue Dec 19 02:28:01 -0800 2006 >> Time.now.next_week(:friday) => Fri Dec 29 00:00:00 -0800 2006 # valid symbol keys for #change: # year, month, mday, hour, min, sec, usec >> Time.now => Mon Dec 18 02:33:17 -0800 2006 >> Time.now.change(:hour => 1) => Mon Dec 18 01:00:00 -0800 2006 >> Time.now.in(5.days) => Sat Dec 23 02:34:59 -0800 2006
Days I Wanted to Write But Someone Else Already Did, Better
Day 32: with_options
DRY up your routes, mostly. technoweenie leads by example
map.with_options :controller => 'mephisto' do |m| m.article ':year/:month/:day/:permalink', :action => 'show' m.article ':year/:month/:day/', :action => 'daily' m.search 'search/:q',:action => 'search', :q => nil m.tags '*tags', :action => 'list' end
Day 33: blank?
Such a handsome generic method. _why writes about the blank? concept in this post. The Rails semantics, however, are a bit different:
>> 0.blank? => false >> " ".blank? => true >> [].blank? => true >> {}.blank? => true >> nil.blank? => true
Day 34: returning
Stolen from ancient languages of yorn, returning is a favorite with an inject aftertaste. Both Jamis and I have written about it in the past. Check.
Day 35: Time#to_s(:format)
Throw out your crufty, functional Time helper methods and move to the new school: Bruce explains how to take advantage of Time#to_s.
>> Time.now.to_s(:time) => "01:50" >> Time.now.to_s(:short) => "Dec 18, 2006" >> Time.now.to_s(:db) => "2006-12-18 01:50:42"
Oh, and here’s a little gift: a small hack which lets you define date formats as procs.
Day 36: mattr_accessor and cattr_accessor
Basically, attr_accessor for modules and classes. As usual, more at Redhanded. Caution: beware. Class variables are not all they appear to be.
Day 37: delegate
The delegate method works a bit like alias_attribute in that it allows you to point a method somewhere else. Brian’s got the scoop along with some tasty OOP tips.
Day 38: Symbol#to_proc
The infamous Symbol#to_proc, it’s really quite simple. And, whadda ya know, PragDave has a quite simple and concise explanation for you.
>> %w[apple dell hp].map { |company| company.first } => ["a", "d", "h"] >> %w[apple dell hp].map(&:first) => ["a", "d", "h"]
Day 39: Proc#bind
Borrowed in spirit from Prototype, this guy can change the value of self for… well, you’re just gonna have to experience it for yourself. Chronicled in A Block Costume and Counting at the Cloak N Bind.
Days You Wrote in the Future
We now approach the most important day of advent: the 40th day. It is a little known fact that before becoming imbued with the powers of immortality and gift giving, Santa Claus spent 40 days and 40 nights in the desert building toys out of sand and cactus juice. These toys were then given to children all over the world on the very first Christmas. The holiday was a success and so, after a round of funding from angel investors, Christmas as we know it took shape.
Any Rubyisms in Rails I missed? Hit me with your favorite.
See you next year.
Wow! Great post. I learned few things from here. Thanks a lot! Marry Christmas ;)
Thank you !!
Fantastic, picked up a few tricks :)
Awesome stuff, nice work Chris.
Teacher says, everytime a bell rings, an angel gets its wings!!!!
Wow! Wonderful post! I knew about only a handful of these. Really handy! Thanks!
A little misleading there in the intro. Almost all of these are in 1.1.6. I tested up to day 25 and only days 10, 24, and 25 seem to be new in edge. And I know for a fact that many of the ones past 25 are in 1.1.6 as well.
Good tips all the same. I just wanted to make sure people knew that they were not left in the dust by using something as “ancient” as 1.1.6. ;-)
Very nice!
Merry xmas!
This article is a sweet Xmas present.
I note that Evan Weaver is now one of the Beatles. He is a talented boy, now isn’t he!
Very nice list. Good to have all in one place.
Worth noting that just about all your examples related to strings break because of Ruby’s crap Unicode support. At least they’re talking (and talking) about it.
But hey, maybe the few dozen languages that can get by (badly) on ASCII alone are enough to keep Rails in business in the long run.
Then again, maybe not.
Patrick, all the string methods mentioned are automatically unicode safe as of Rails 1.2. See the multibyte announcement on http://weblog.rubyonrails.org/2006/11/23/rails-1-2-release-candidate-1
Sweet christmas present for me. Thanks Chris, and wish ya a great Christmas.
Very good list! Maybe can we create a sort of tips repository for rails developers ??
Wow. Thanks for all these rubyisms. They’re gonna help a bunch when I go back and cleanup the mess of code I’ve made.
Nice post! Happy new year!
Wow! Thanks for all the nice tips. Definitely a good christmas present.
Thanks for the comment, DHH, going to check it out.
I’m obsessively grumpy about Unicode because it matters a lot to a lot of people who are cut off from computing in their own language… I hope Ruby support is fixed natively soon.
In the meantime the chars accessor is a great tool, Julik, especially, deserves a lot of credit for making it available. He’s a Unicode wiz.
And I should add that this is a nice post. ☺
argh… all this Date methods are not i18n capable ?.... How can you build a webapp supporting different locales (e.g. english, german and french) which is quite common for switzerland (hey we have four official languages :-) and english is not offical …) ?
pascal
loves it
this pretty much rules, thanks!
Excellent post, as always. One small thing: Contrary to what one may expect, “end_of_day” does not actually exist in the Core Extensions, which is kinda sad since it’s my favorite time of day. But, hey, it’s just so damn easy to roll your own:
Nice. One thing I’d like to point out. “string_string”.camelize produces pascal case not camel case. Which is a bit odd. Camel case is really .camelize(:lower) This should probably be fixed for clarity sake.
reindeers!?
Really Great!!!
It’s been a few months, but I keep re-visiting this article. Great job Chris.
Chime in.