21 Ruby Tricks You Should Be Using In Your Own Code
Writing for Ruby Inside, I get to see a lot of Ruby code. Most is good, but sometimes we forget some of Ruby's shortcuts and tricks and reinvent the wheel instead. In this post I present 21 different Ruby "tricks," from those that most experienced developers use every day to the more obscure. Whatever your level, a refresh may help you the next time you encounter certain coding scenarios.
Note to beginners: If you're still learning Ruby, check out my Beginning Ruby book.
2009 Update: This post was written in early 2008 and looking back on it, there are a couple of tricks that I wouldn't recommend anymore - or to which extra warnings need to be added. I've added paragraphs like this where necessary. Enjoy! :-)
1 - Extract regular expression matches quickly
A typical way to extract data from text using a regular expression is to use the match method. There is a shortcut, however, that can take the pain out of the process:
email = "Fred Bloggs <fred@bloggs.com>" email.match(/<(.*?)>/)[1] # => "fred@bloggs.com" email[/<(.*?)>/, 1] # => "fred@bloggs.com" email.match(/(x)/)[1] # => NoMethodError [:(] email[/(x)/, 1] # => nil email[/([bcd]).*?([fgh])/, 2] # => "g"
Ultimately, using the String#[]
approach is cleaner though it might seem more "magic" to you. It's also possible to use it without including an extra argument if you just want to match the entire regular expression. For example:
x = 'this is a test' x[/[aeiou].+?[aeiou]/] # => 'is i'
In this example, we match the first example of "a vowel, some other characters, then another vowel."
2 - Shortcut for Array#join
It's common knowledge that Array#*
, when supplied with a number, multiplies the size of the array by using duplicate elements:
[1, 2, 3] * 3 == [1, 2, 3, 1, 2, 3, 1, 2, 3]
It's not well known, however, that when given a string as an argument Array#*
does a join!
%w{this is a test} * ", " # => "this, is, a, test" h = { :name => "Fred", :age => 77 } h.map { |i| i * "=" } * "n" # => "age=77nname=Fred"
3 - Format decimal amounts quickly
Formatting floating point numbers into a form used for prices can be done with sprintf or, alternatively, with a formatting interpolation:
money = 9.5 "%.2f" % money # => "9.50"
4 - Interpolate text quickly
The formatting interpolation technique from #3 comes out again, this time to insert a string inside another:
"[%s]" % "same old drag" # => "[same old drag]"
You can use an array of elements to substitute in too:
x = %w{p hello p} "<%s>%s</%s>" % x # => "<p>hello</p>"
5 - Delete trees of files
Don't resort to using the shell to delete directories. Ruby comes with a handy file utilities library called FileUtils
that can do the hard work:
require 'fileutils' FileUtils.rm_r 'somedir'
Be careful how you use this one! There's a FileUtils.rm_rf
too..
6 - Exploding enumerables
* can be used to "explode" enumerables (arrays and hashes). "Exploding" is sort of an implicit on-the-fly conversion from an array to regular method arguments. We'll let the examples do the talking:
a = %w{a b} b = %w{c d} [a + b] # => [["a", "b", "c", "d"]] [*a + b] # => ["a", "b", "c", "d"]
a = { :name => "Fred", :age => 93 } [a] # => [{:name => "Fred", :age =>93}] [*a] # => [[:name, "Fred"], [:age, 93]]
a = %w{a b c d e f g h} b = [0, 5, 6] a.values_at(*b).inspect # => ["a", "f", "g"]
7 - Cut down on local variable definitions
Instead of defining a local variable with some initial content (often just an empty hash or array), you can instead define it "on the go" so you can perform operations on it at the same time:
(z ||= []) << 'test'
2009 Update: This is pretty rancid, to be honest. I've changed my mind; you shouldn't be doing this :)
8 - Using non-strings or symbols as hash keys
It's rare you see anyone use non-strings or symbols as hash keys. It's totally possible though, and sometimes handy (and, no, this isn't necessarily a great example!):
does = is = { true => 'Yes', false => 'No' } does[10 == 50] # => "No" is[10 > 5] # => "Yes"
9 - Use 'and' and 'or' to group operations for single liners
This is a trick that more confident Ruby developers use to tighten up their code and remove short multi-line if and unless statements:
queue = [] %w{hello x world}.each do |word| queue << word and puts "Added to queue" unless word.length < 2 end puts queue.inspect # Output: # Added to queue # Added to queue # ["hello", "world"]
2009 Update: Be careful here - this one can sting you in the butt if your first expression returns nil
even when it works. A key example of this is with the puts
method which returns nil
even after printing the supplied arguments.
10 - Do something only if the code is being implicitly run, not required
This is a very common pattern amongst experienced Ruby developers. If you're writing a Ruby script that could be used either as a library OR directly from the command line, you can use this trick to determine whether you're running the script directly or not:
if __FILE__ == $0 # Do something.. run tests, call a method, etc. We're direct. end
11 - Quick mass assignments
Mass assignment is something most Ruby developers learn early on, but it's amazing how little it's used relative to its terseness:
a, b, c, d = 1, 2, 3, 4
It can come in particularly useful for slurping method arguments that have been bundled into an array with *:
def my_method(*args) a, b, c, d = args end
If you want to get really smart (although this is more 'clever' than truly wise):
def initialize(args) args.keys.each { |name| instance_variable_set "@" + name.to_s, args[name] } end
12 - Use ranges instead of complex comparisons for numbers
No more if x > 1000 && x < 2000 nonsense. Instead:
year = 1972 puts case year when 1970..1979: "Seventies" when 1980..1989: "Eighties" when 1990..1999: "Nineties" end
13 - Use enumerations to cut down repetitive code
Rubyists are often keen to remove repetition - often espousing "DRY" (Don't Repeat Yourself). You can take this to extremes using Ruby's enumerators to perform similar operations multiple times. Consider requiring multiple files, for instance:
%w{rubygems daemons eventmachine}.each { |x| require x }
14 - The Ternary Operator
Another trick that's usually learned early on by Ruby developers but rarely in less experienced developers' code is the "ternary operator." The ternary operator is not a fix-all, but it can sometimes make things tighter, particularly in view templates.
puts x == 10 ? "x is ten" : "x is not ten" # Or.. an assignment based on the results of a ternary operation: LOG.sev_threshold = ENVIRONMENT == :development ? Logger::DEBUG : Logger::INFO
15 - Nested Ternary Operators
It can be asking for trouble but ternary operators can be nested within each other (after all, they only return objects, like everything else):
qty = 1 qty == 0 ? 'none' : qty == 1 ? 'one' : 'many' # Just to illustrate, in case of confusion: (qty == 0 ? 'none' : (qty == 1 ? 'one' : 'many'))
16 - Fight redundancy with Ruby's "logic" features
I commonly see methods using this sort of pattern:
def is_odd(x) # Wayyyy too long.. if x % 2 == 0 return false else return true end end
Perhaps we can use a ternary operator to improve things?
def is_odd(x) x % 2 == 0 ? false : true end
It's shorter, and I've seen that pattern a lot (sadly) but you should go one step further and rely on the true / false responses Ruby's comparison operators already give!
def is_odd(x) # Use the logical results provided to you by Ruby already.. x % 2 != 0 end
Sometimes, though, you want to explicitly convert implicit true/false scenarios into explicit true/false results:
class String def contains_digits? self[/\d/] ? true : false end end
If we hadn't done this, you'd get back either nil or the first matched digit rather than true or false.
17 - See the whole of an exception's backtrace
def do_division_by_zero; 5 / 0; end begin do_division_by_zero rescue => exception puts exception.backtrace end
18 - Allow both single items AND arrays to be enumerated against
# [*items] converts a single object into an array with that single object # of converts an array back into, well, an array again [*items].each do |item| # ... end
19 - Rescue blocks don't need to be tied to a 'begin'
def x begin # ... rescue # ... end end
def x # ... rescue # ... end
20 - Block comments
I tend to see this in more 'old-school' Ruby code. It's surprisingly under-used though, but looks a lot better than a giant row of pound signs in many cases:
puts "x" =begin this is a block comment You can put anything you like here! puts "y" =end puts "z"
2009 Update: Curiously, I've not seen any significant uptake of block comments in Ruby but.. I don't use them myself either anymore. I suspect with column editing and keyboard shortcuts in common text editors, the motivation here has lessened.
21 - Rescue to the rescue
You can use rescue in its single line form to return a value when other things on the line go awry:
h = { :age => 10 } h[:name].downcase # ERROR h[:name].downcase rescue "No name" # => "No name"
If you want to post your own list of Ruby tricks to your blog, send trackback here or leave a comment, and I'll link to all of them in a future post. Alternatively, feel free to post your own Ruby tricks as comments here, or critique or improve on those above.
As an aside, Ruby Inside is exactly two years old today. Thanks for your support! Intriguingly, the first post was another Ruby trick that I forgot to include above, so check that out too.
May 26, 2008 at 7:31 pm
Hey, great article! Will be using tons of these tips! You need to escape your right and left angled brackets in the first example string though.
May 26, 2008 at 7:47 pm
Awesome compilations, didn't know some bits of it myself!
May 26, 2008 at 8:05 pm
Another way to do #18 is with Array(items). Though IIRC ActiveRecord proxies don't play nice with that, since their method_missing magic calls the deprecated Object#to_a.
May 26, 2008 at 8:09 pm
Some of these I like a lot, but I think a few could actually be a sign of knowing Ruby too well. The rescue stuff, exception.backtrace, the use of ranges in case statements, I think the world would be a better place with more of that, but #7 is just asking for trouble.
May 26, 2008 at 8:13 pm
Gianni: Thanks! I've edited this piece about 10 times already due to the "tweaks" WordPress has made to it.. :) Got that one now.
May 26, 2008 at 8:19 pm
Hey Peter,
this looks great! I am still not seeing the full email-address string for the first example in safari, ff, or opera. The example becomes confusing quickly unless you know what it should be looking for.
May 26, 2008 at 8:31 pm
Wow, really nice indeed!
That is some sweet Ruby code right there, I need to suck it all up!!
Great post! Someone should make a screencast out of it...
...no not me, I am too lazy...
May 26, 2008 at 8:34 pm
Really, really awesome! Thanks for sharing those nice tips Peter, I'd like to see more of these posts, personally!
May 26, 2008 at 8:43 pm
There's a secret #22 in #17... being able to exclude Exception on a rescue. :)
begin
jump
rescue => exception
put exception.backtrace
end
is short for
begin
jump
rescue Exception => exception
put exception.backtrace
end
May 26, 2008 at 8:45 pm
Please refrain from #9. The given example is especially horrible. I'm using Ruby for a couple of years now and I would have interpreted it wrong.
If you really want to put it on one line use parens and semicolon:
(queue << word; puts "Added to queue") unless word.length < 2
And "puts queue.inspect" is highly unidiomatic, it does the same as "p queue".
Otherwise a nice list, thanks.
May 26, 2008 at 8:51 pm
Seems I had to fix something else too :) #1 should now be working okay. Thanks!
May 26, 2008 at 8:53 pm
Stefan: One place where #9 can come in useful is in Rails when you want to render something and return all based on a condition. Rather than do four lines (if / render / return / end), you can use that technique to get it on one line. (At least, you did at one point, whether things have changed now I don't know as I haven't used Rails 2.1 yet.)
May 26, 2008 at 8:54 pm
Some of these are good tricks, some are blindingly obvious to decent programmers, some are unnecessary, and some are actually bad (all IMHO, of course). They are certainly not all things we "should" be using in our own code.
1. Good tip, but the first line is wrong, I think.
2. As Array#join is so prevalent, I think it should be continued to be used in place of the utterly unmnemonic * .
4. The Ruby idiom for interpolating text is #{}. Using the C equivalent is just going to confuse people (even if just for a second).
7. What's wrong with z = ['test'] ?
8. If this is such a good tip, let's see a good example!
9. This, like a lot of these tips, is predicated on shorter code being better. Most of the time I would agree, but I see no point in using 'and' here instead of just putting a separate operation on a separate line. Besides, wouldn't 'or' work just as well? That's the sign of a bad construct, it seems to me.
16. It's sad that people use true and false in this way, but they do, so it's a good tip. Unfortunately.
18. Not sure if this is a good example. If 'items' is a single object, it shouldn't be called 'items.' I would hope it to be rare to want to enumerate over a single object anyway. Certainly, it should be rare, in which case, this code expands an array and then immediately collapses it again.
20. I think comments are a code smell.
21. Using exception handling in place of a control structure is generally frowned upon. I have to admit, though, that this is nice and neat, it avoids things like andand, and I do see it in "real" code. It's probably way slower than the more straightforward technique, but that's not a real issue.
Overall, an interesting article - interesting enough to take the time to disagree with some of it. :)
///ark
May 26, 2008 at 9:15 pm
Hi Peter,
good list! Most of those really can be seen "in the wild", especially among more experienced Rubyists. I suspect there will be some dissent over certain points, as there always is, and I imagine point number 7 to be the first to come under close scrutiny :) For example,
# generate the first 12 Fibonacci numbers
fib = [1, 1]
10.times { fib << fib[-1] + fib[-2] }
is (to me, at least) more immediately accessible than
# generate the first 12 Fibonacci numbers
10.times { (fib ||= [1, 1]) << fib[-1] + fib[-2] }
Also, I've come to realize that grouping local initializations at the beginning of the function helps me immensely later when debugging or writing tests. Probably a matter of taste. :)
Speaking of grouping things, this is how I test for valid arguments:
def send_letter(address, letter)
throw "Address must be a string" unless address.is_a?(String)
throw "Need a Letter instance" unless letter.is_a?(Letter)
throw "Can't send as a letter" unless letter.mass <= 16.oz
...
end
Which is fairly obvious to some, if not most people, but perhaps bears a mention.
Cheers,
Zoran
May 26, 2008 at 9:31 pm
#12 : Some other handy case usage
var = "hello"
case var
when /el/
puts "Regexp matched"
when String
puts "Class matched"
when 1,2,3
puts "Multiple arguments matching"
end
#21: This is generally a bad practice for two reasons :
1) Running ruby with --debug will show all those unnecessary exceptions
2) The backtrace gets built even if not used and it is expensive
May 26, 2008 at 9:34 pm
Mark:
1: WordPress didn't escape the brackets. Should be sorted now.
2: I mostly agree, although this is a very cute trick that I am sure to use in certain, off the cuff situations.
7: It's really for situations where you repeat something over and over. For example, appending something to an instance variable in a class. I tend to use it with class variables that store references to objects of that class. (@@objs ||= []) << self, for example.
18: There are some instances where external data define whether you have a single object or an array, and you /could/ check for that and fix it to always be an array, but it's easier just to use this trick. I believe one instance is when Rails converts params.. if there are multiples with the same name, you can get an array of strings, otherwise you just get the string.. if you want to iterate over whatever you get back, this trick sorts it out.
May 26, 2008 at 9:35 pm
Anyway, critiques are certainly encouraged and welcomed on this. If people can "correct" any bad usages, that will help everyone else who comes here to check out the tricks. That said, they are "tricks" and not all of them are 100% smell-free :-)
May 26, 2008 at 9:52 pm
Hi Peter --
It's good to see this stuff -- I've definitely been too immersed recently in discussions of changing Ruby, and not enough of what one can actually do with it! Like everyone else, I have my likes and dislikes, but I think overall it's a good list and every single one is worth knowing about and thinking about.
I'd just like to expand on #19 by pointing out the possibly obvious, which is that leaving out 'begin' only works in method definitions (where 'def' carries a kind of implicit begin). Also, there will of course be occasions where one wants a begin in a method, namely in cases where you don't want the whole method governed by the rescue clause.
David
May 26, 2008 at 10:17 pm
Re: 12
I wouldn't encourage people to get in the habit of using
- `when something: another_thing`
The colon is deprecated (and raises an exception in 1.9). Use either `then` or semi-colon:
- `when something then another_thing`
- `when something; another_thing`
May 26, 2008 at 10:25 pm
> x % 2 != 0
You can also do this: not (x % 2).zero?
Not sure how much better that is, but it's definitely an overlooked trick.
Code that doesn't use #16 is a personal pet peeve of mine.
Chris: no no no... "rescue => e" does NOT imply Exception, it implies StandardError. Big difference. Getting the two confused is another pet peeve of mine, as it makes code impossible to escape from via Ctrl-C since Interrupts get rescued. no fun.
May 26, 2008 at 10:55 pm
I can tell that this list was written by an ex-Perl programmer, especially #15. ;-)
It's too bad that Ruby doesn't have the wantarray method as in Perl. I've used it a few times for situations like #11, mass assignment and returning arrays.
May 26, 2008 at 10:58 pm
6 - Why "explode"? I believe it's called the "splat" operator. Makes more sense to me.
7 - Could be just: (z = []) < '-u', :group => '-g'}[kind] or raise ArgumentError.
17 - In Ruby 1.9, Array#to_s is an alias of #inspect so the backtrace wouldn't be displayed in a readable manner.
18 - Ok in Ruby 1.9. But in 1.8, be careful with strings: the splat operator calls #to_a that splits strings on new lines, which might not be the expected behaviour. [items].flatten is a safer alternative.
May 26, 2008 at 11:02 pm
Sorry about my comment above: my comment about point 7 has been garbaged and the one about point 8 dropped. Here they are:
7 - Could be just: (z = []).push "test".
8 - I like to use Hashes to make one line case statements like: flag = {:user => '-u', :group => '-g'}[kind] or raise ArgumentError.
May 26, 2008 at 11:38 pm
Roman: On your comment for number 7, what you are suggesting wouldn't want for what was intended. If you repeat that over and over, the array doesn't get bigger as z is reinitialized to [] each time, hence the ||= variation.
On your comment for #18.. very interesting! I didn't realize that. Unfortunately even Array("x\ny\nz") does that split too. Your flatten technique appears to be a good one here.
Your one line case statements using hashes.. that's a good trick! I might try that myself..
You should consider making your own list, Roman. I'd definitely link to it. You clearly have a deep experience of some of the pitfalls regarding Ruby trickery! :)
May 27, 2008 at 12:09 am
Thanks!
Some are great, but some are clever (or just plain obtuse) at the expense of readability. Elegance is not the same as terseness. Elegance is always good.
Terseness and its evil twin verbosity will always be in bitter war against each other, both in pursuit of elegance. The winner is not always clear.
C, C++, Perl, Smalltalk and others have all allowed its practitioners to fall into the "terse is good" trap. We should aim for succinct. Or, as Einstein may have said, "everything should be as simple as possible, but no simpler."
Tom
May 27, 2008 at 12:29 am
#15... no, no, a thousand times no! I dislike the use of the ternary operator in general, honestly, but if you _are_ going to use it, never, ever, _ever_ nest it! Down that path lies madness for whoever comes along behind you and has to maintain the code.
May 27, 2008 at 12:51 am
Great list. I don't know if "should be using" applies to all of these tricks, however.
I have a friend who is currently serving 27 years in prison for nesting ternary operators. Please don't do it!!
May 27, 2008 at 1:14 am
My, all this BRAINPOWER and it has not occurred to anyone to make a FUCKING PRINT FRIENDLY version available??!!??
May 27, 2008 at 1:46 am
"[items].flatten is a safer alternative"
Won't that break nested arrays? You can write a "flatten one level" method, but otherwise I think #18 is the best way. You should know if your code is expecting strings or not!
May 27, 2008 at 2:40 am
Nice List Peter.
As with many of the other comments, any issues I have with the items are derived from personal taste.
[item].flatten ftw.
and ( mylist ||= []) << "stuff" can cause some interesting trouble when used without considering scoping.
A note to Zoran tho, throw != raise. It's deceptive when coming from a java,c#,c++ background as throw seems more akin to goto.
Cheers
May 27, 2008 at 5:56 am
This is an interesting list, I learned a few things. Thank you.
However, the ternary operator is a pet peeve of mine. It seems to be loved by those who were taught, wrongly, that having fewer lines of code is better; that somehow vertical size is worse than horizontal size. if/else is concise enough, very readable, and everyone understands it.
May 27, 2008 at 6:07 am
you can just put a normal case statement on a line. the multiple 'when' is pretty ugly tho
May 27, 2008 at 6:18 am
I've been bitten badly by inline rescue a little too often I'm afraid. It catches too many types of exception, and you then have a hard time tracking down the spurious nil and what caused it because you've thrown away the exception information.
@Mark Wilden: I've used it for memoizing the calculation of candidates in a kakuro solver. Skeleton solution looked something like:
@@result_cache = Hash.new {|args| self[args] = calculate_possibles(*args)}
def self.possible_values(target_sum, places, candidates)
@@result_cache[[target_sum, places, candidates]]
end
def calculate_possible_values(target_sum, places, candidates)
...
end
I've also used it in a rails plugin that did some tricks with the params hash. Because the params hash is just a hash (a mistake IMHO) rather than a hashlike object, you can't monkeypatch it easily. The only way I could pass information around was to come up with some kind of hash key that I could use which would never clash with the 'real' hash keys. So I did something like:
module DistinguishedHashKey
end
...
params[DistinguishedHashKey] = ...
Admittedly that's not a _good_ solution, a better one would probably have involved submitting a patch introducing a RequestParams class to Rails, but life is short, time was marching on and I needed the problem solved then and there.
@Peter - one line case statements using hash. Those are especially useful when you start using lambas in the hash values. An _old_ perl trick that one...
Roman: The problem with [items].flatten is that it's too eager. Sure it fixes the string case, but it breaks [*[:foo, [:bar]]]. One fix:
begin
old, $/ = $/, ""
[*items]
ensure
$/ = old
end
Not even slightly pretty though.
May 27, 2008 at 6:27 am
I always liked those tricks - but then again if they get used alot ruby will become the next read only language after perl. The next step would be the obfuscated ruby contest.
Wordpress can be a little insisting on reformating, you could try the plugin wp-unformatted - http://alexking.org/projects/wordpress/readme?project=wp-unformatted.
May 27, 2008 at 6:35 am
Nice list, I already knew some of these and I use them frequently.
If you combine 6. and 11. you can do things like
> a, b, *c = 1, 2, 3, 4, 5, 6
a => 1
b => 2
c => [3, 4, 5, 6]
That's pretty useful when used with Array#split :-)
I relly like String#% because it makes more readable code than the common interpolation method ( #{} ). Compare :
" %s " % [tag, contents, tag]
" #{content} "
(Ok maybe it's not the best possible example :-/)
Thanks for the post Peter !
May 27, 2008 at 6:37 am
Hmm, tags get stripped :/
The last example was :
"<%s> %s </%s>" % [tag, contents, tag]
"<#{tag}> #{contents} </#{tag}>"
May 27, 2008 at 6:44 am
Todd Werth:
(IMHO) The ternary is understood by everyone too. I usually prefer it in assignments from simple conditionals.
Peter:
Sweet tricks. A lot of these I never knew, a few of them are good reminders.
May 27, 2008 at 7:37 am
like most programmers, i was never taught having few lines of vertical code is better than horizontal code, never mind whether it's wrong or correct. the use of ternary operators is certainly a personal preference, but why write in 5 lines can you can write in 1, that is readable and much more concise. and why not make use of those brackets to improve readability.
output = (x == 10)? "x is ten" : "x is not ten"
May 27, 2008 at 8:40 am
Ternary operators are evil. Nested ternary operators are really evil.
It is just so much more readable to use the words - remembering that conditionals in Ruby are just expressions and yield a value.
amount = if qty == 1 then "one" else "many" end
and it's even better if you use more than one line.
May 27, 2008 at 11:28 am
Thanks for sharing these useful tips! The 16th is wonderful!
May 27, 2008 at 12:39 pm
Everyone knows ternary operators. Use them only in simple cases and don't worry about it. Nothing wrong with x = (condition) ? bar : baz
Mark Wilden: We have all had to maintain uncommented masses of poorly-written code. Some of us respond by swearing that from now on, our code will have comments. Some of us respond by swearing that from now on, our code will be more readable.
Why not do both?
May 27, 2008 at 1:09 pm
Who are these people who can't read ternaries? Bad factoring makes code hard to maintain, not succinct syntax.
You forgot to mention !! in relation to number #16.
"abc"[/(d}/, 1] # => nil
!!"abc"[/(d}/, 1] # => false
May 27, 2008 at 1:52 pm
Hi,
Interesting reading. Unfortunatly, for me, (far to) much look Perllish (= obfuscated). Not really elgant readable code that Ruby is supposed to promote.
#2 Can't Ruby with all its string functions format a floating number in a lucid way like: money.to_s(3.2f) or in the Basic style money.to_s("0.00")? I can do it in .NET Basic!
# 14 Well used terniary operator is good. Even if easily missused. But so can everything.
# 16 This example could be implemented in Basic since it got functions.
(Well at least in HP9830 Basic since 1976). But few realize it!
I don't understand why the textbooks don't show it.
But the sigel line version returning sometbing else that the if test result requires the terniary operator.
May 27, 2008 at 2:03 pm
My basic rule of thumb: if I get to a line of code that requires me to stop and put mental parenthesis around it--someone is trying to be cute or show how clever they are.
Please don't nest ternary operators.
May 27, 2008 at 2:14 pm
Great article. I would really like to use tip #21 but according to all the comments I'm not sure I dare :)
I see many uses for this, though and I'm wondering if there might be a better solution if I want to do stuff like this:
is_available = user.presence.availability == Online
and I really don't care if either "user" or "presence" is nil?
I see myself do code such as:
is_available = (user && user.presence ? user.presence.availability == Online : Offline)
What I really would like is something to eval a string and return either the value in the ending object or nil if the object tree can't be followed till the end e.g.:
availability = find_availability("user.presence.last_year.lastest_city.available")
Does anyone see my point? But then again, maybe "rescue" is acceptable enough in the above example? :)
May 27, 2008 at 3:26 pm
If everyone knows ternary operators, why are they in a list of 21 ruby "tricks"? From years of mentoring junior programmers, I can tell you that no, not everyone does.
There are cases when ternary operators are useful and make code more readable, but those cases are rare.
Mike Cowden, I totally agree.
I do understand that these are age old arguments, and partially a matter of style. So this is just my ever so humble opinion.
May 27, 2008 at 3:37 pm
Fantastic list! There were actually a couple of points that were new to me, which doesn't happen much any more.
One nitpick: #18 would probably be better done as Array(items), as Object#to_ary is deprecated. Kernel.Array() is the canonical way to say "make sure this is an array".
May 27, 2008 at 3:54 pm
@mark - "Using exception handling in place of a control structure is generally frowned upon. I have to admit, though, that this is nice and neat, it avoids things like andand, and I do see it in “real” code. It’s probably way slower than the more straightforward technique, but that’s not a real issue."
Not true. Exceptions are encouraged and should be used when an exception happens in your code. Someone did some benchmarking on this subject and found that Ruby exceptions are reasonably fast; about as fast as an equivalent control structure.
The only problem I do see with the example is that you are not specifying which exception you want to rescue, which, if you are not careful, can hide problems deep in your code.
May 27, 2008 at 4:14 pm
Oh dear, someone suggested
!!
. Nil is perfectly falsy, pretty much everything else that isn'tfalse
is truthy. Why spend cycles to throw away information?May 27, 2008 at 4:16 pm
@Peter - " I’m wondering if there might be a better solution if I want to do stuff like this"
Based on your example, I find it's usually better to make the method available in the "base" object:
class User
def availability
presence ? presence.availability : Offline
end
end
If you're running into this problem often, you might want to rethink your design. Although there are certainly cases where the rescue is your best bet.
Evaling a string like you describe is pretty straightforward.
class String
def try(string, caller)
string.split('.').inject(caller) do |result,method|
result.send(:eval, method) if result
end
end
end
"thing(10).to_s.fail".try(self)
But, again, I'd reevaluate your design first.
May 27, 2008 at 5:07 pm
wow™
really nice!
May 27, 2008 at 6:48 pm
As for #9, it could be written in a more functional (i.e. no side effects) way, like this:
queue = %w{hello x world}.inject([]) do |queue, word|
(queue <= 2; queue
end
puts queue.inspect
though it requires getting used to the inject construct...
May 27, 2008 at 6:51 pm
The example is not very readable so I'a posting it again:
queue = %w{hello x world}.inject([]) do |queue, word|
(queue << word; puts "Added to queue") if word.length >= 2; queue
end
puts queue.inspect
May 27, 2008 at 10:14 pm
In the age where readable code is getting so much emphasis, and people are encouraged to use more than one language, I'm not convinced that any of the "shortcuts" are an improved coding style. Explicit code = maintainable code IMO. Ruby is already cryptic enough IMO, eliminating words in favor of symbols and one-liners isn't an advantage to anyone but the original coder's "tricky" ego.
May 27, 2008 at 11:06 pm
@John: That's one of the reason why exception handling for control is often bad - the swallowing.
If the code is truly responding to an exceptional circumstance, then exceptions are the way to go. This is because exceptions allow the code with the best understanding of what to do handle the situation, whether it's local or somewhere up the callstack. But if the code is merely providing a default, and the circumstance isn't truly exceptional I don't think exceptions are the most "intentional" (i.e. showing the intent of the code) way to do it.
@Alan: The issue of comments is a huge and eminently debatable. My summary comment is "All code that's tricky to understand should be commented. Code should not be tricky to understand." :)
@Piers: You probably know this, but the Rails params hash isn't actually just a Hash. It's a HashWithIndifferentAccess, allowing lookup by symbols or strings. :)
!! is an old C trick to convert an expression to 1 or 0 (true or false, in C). I agree it's not necessary in Ruby.
@Francois: The reason I don't like string formatting like the old C sprintf is that it makes the code less readable. To understand the output, you have to bop back and forth, rather than reading it inline.
@All: It's not the "ternary" (or even "terniary") operator. It's -a- ternary operator (the only one, as it happens). Its name is the "conditional operator," and yes, nesting them is evil.
///ark
May 27, 2008 at 11:08 pm
By far the most interesting thing I learned is that ; is an alternative to newline. (Who would have thought!) But the ability to do ternary 1-liners with multi-line blocks is pretty awesome.
"ruby" == "awesome" ? ((response = "i think so"); puts response) : (if true: puts "you know damn right" end)
May 28, 2008 at 8:08 am
@Mark: The thing is, HashWithIndifferentAccess is still sending a datastructure to do an object's job. Where's the information about which route matched to provide that set of params? Where's the information about how the URI was parsed into keys? I get depressed by the amount of useful information that Rails throws away during route recognition. Some of that stuff, it it were available, could allow for a radical slimming down of controllers to the point where they become nearly invisible.
But that's a soapbox I probably shouldn't be climbing onto here.
May 28, 2008 at 9:35 am
Just a little combining ...
queue = %w{hello x world}.map { |word| word.length >= 2 ? (puts "Added to queue"; word) : nil }.compact
p queue
May 28, 2008 at 1:34 pm
@Piers: You're absolutely right. I was just joking around, since I think HashWithIndifferentAccess is such a funny name. :)
@Frank: I have to say that adding nil to the queue then removing it later - just for code brevity - would not be my first choice.
///ark
May 28, 2008 at 1:50 pm
Sure.
p queue = %w{hello x world}.select { |word| word.length >= 2 }
May 28, 2008 at 8:29 pm
Yikes - some of that is scary. Just because you can do it doesn't mean you should! :-)
May 28, 2008 at 8:57 pm
I might be a little slow, but I don't get #7.
What is it supposed to replace? why is it better?
May 28, 2008 at 10:08 pm
K: It can be useful to remove the need to initialize variables. It's particularly useful for instance variables being used in one-liners.
Consider this somewhat arcane example of adding together "columns" within an array:
a = [ [1,2,3], [4,5], [6, 7, 8] ]
a.each { |y| y.each_with_index { |i, j| (@r ||= [])[j] = @r.fetch(j, 0) + i.to_i } }
The (@r ||= []) part means I didn't need to do a @r = [] on the previous line. Whether or not this "shortcut" is suitable for you or not is a matter of personal taste, however :)
May 29, 2008 at 8:21 am
@Peter Theill: There's a dynamic hash-tree snippet over at RubyForge.
class Node < Hash
attr_accessor :value
def method_missing(key)
self[key]==nil ? self[key]=Node.new : self[key]
end
end
tree = Node.new
p tree.branch.leaf.value = 'Hello Worldd!'
p tree.branch.leaf.value == 'Hello World!'
p tree.branch.leaf.xxx.value == 'Hello World!'
p tree.branch.value
Similarly you can create multidimensional hashes ( http://www.ruby-forum.com/topic/130324 ).
May 29, 2008 at 6:54 pm
Wow.
The basic stuff (use fileutils instead of shell, get exception backtrace) is useful, although I wouldn't call these things 'tricks.' The other things, (nested ternaries, cramming as many operations as possible into as few characters as possible), ...
Ruby is not perl; I'd like to keep it that way.
May 30, 2008 at 7:28 am
What's that saying about those who fail to learn the lessons of the past are doomed to repeat its mistakes?
Using "tricks" results in code with lower maintainability, because fewer people will understand it.
May 30, 2008 at 4:10 pm
#18 wording is misleading. it will really call to_splat, or to_a, and therefore a 'single object' wont necessarily stay a single object. a hash will be converted to an array and so forth
May 30, 2008 at 8:40 pm
13 is just wrong on so many levels.
May 30, 2008 at 10:08 pm
#68: Very common though.
May 31, 2008 at 12:24 am
Cool list, I learned some new ways to confuse my co-workers! :) I liked #2.
Some of these create useless garbage objects that must be collected in the common cases and are much less efficient than other techniques. Just be cause a Ruby program doesn't have to call free() doesn't mean that Ruby wont have to do it.
Ruby does not memoize constants, so it may look like a constant Range or String in your code, but it's just garbage. Consider naming them with CONSTANTS.
Sorry I have to say it: using Floats for monetary values is always a bad idea.
May 31, 2008 at 12:42 am
Kurt: I use integers based on the lowest denomination of the currency (cents, pence, etc.) I still use the same format though, once I've divided the price by 100 (or whatever). "%.2f" % (amount / 100)
Pingback: Churadesign
June 3, 2008 at 4:01 pm
Nice compilation!
As Mark Wilder already pointed out, using #{} instead of
sprintf would be more idiomatic. Your example was:
x = %w{p hello p}
"%s" % x
If we give tag and text names, it would look like:
tag, text = *%w{p hello}
"%s" % [tag, text, tag]
We may avoid the duplication by giving explicit positions
in the format string:
"%2$s" % [tag, text]
The #{} form is a lot more readable:
"#{text}"
June 3, 2008 at 8:04 pm
#68 This is one reason why _why's Camping is so few lines.
June 3, 2008 at 8:14 pm
Unfortunately the comment function does not display most of the code I entered. Peter, could you please correct that?
June 3, 2008 at 9:02 pm
Tammo: I see the same here as I did in the e-mail from the WordPress installation to me, so if anything has been removed, it was removed at the earliest stage.. sanitization perhaps. Ideally code would be put into a pastie and then linked here.
Pingback: vBharat.com » 21 Ruby Tricks You Should Be Using In Your Own Code