Good Ruby Times
In the last few weeks I’ve had the chance to glance into several different codebases, some written years ago by devs in their Rails-infancy (rainfancy?).
Doing the travelling codes-man like that is a golden opportunity to see what people’s stumbling blocks are and how a few easy tricks can improve the code substantially.
What follows is not your new cutting edge coffe making kitchen sink script-fu, but a rather dull list of everyday Ruby that I learned people need to learn.
One-stop requires
This is a short one: requires go into the environment.rb.
Really, don’t use it elsewhere. I’ve seen people reason along the lines of “I keep the ‘require hpricot’ in my xe.com currency rates scraping class as to keep things local and closely knit”. There is some truth to that argument and if a gem is only used by a rake task, maybe it even saves us a second during app startup and some RAM. Still, load order issues, poor readability and maintainability makes it a real pain to deal with down the road.
Trust me: don’t. Put it in environment.rb with a comment describing its purpose in your project.
Whenever possible use the “config.gem”. Advantages:
- allows you to specify the lib name even if the gem name is different (e.g. for github gems)
- allows you to require sub libs
Not used very often perhaps but put to good use by e.g. the right_aws gem where you can choose to load just the SQS part with:
config.gem 'right_aws', :lib => 'sqs/right_sqs'or maybe you just wanted the SDB part?
config.gem 'right_aws', :lib => 'sdb/right_sdb_interface'I personally believe this particular feature is more confusing than helpful, but it’s good to know it’s there.
- allows you to specify the library version
Towards the end of the development cycle, before rolling out to production, gems should generally be frozen. An alternative is to define exact versions in the config. I generally advise doing both as having the environment.rb as the one-stop source for gem requirements is a boon for everybody involved, capistrano scripts or new devs on your team.
- all in one place, easy to find out requirements
Singleton methods (creation)
Ruby provides three (common) ways to define a singleton method:
-
Explicitly name the class
class RapidInterventionGroupTeam < ActiveRecord::Base
def RapidInterventionGroupTeam.load_group_allocations_and_teams(project_id, curr_user)
# Code
end
end
PROs: hmm, dunno. Makes it easy to know what class you're looking at if the source code file is very long?
CONs: long, ugly
-
Use self
class RapidInterventionGroupTeam < ActiveRecord::Base
def self.load_group_allocations_and_teams(project_id, curr_user)
# Code
end
end
PROs: easy to spot here-comes-a-singleton-marker. Some people like to use the 'self' keyword. I call them self-ish people. Sorta short.
CONs: I think it's ugly. For more than two singletons that repeating 'self' annoys me.
-
Use the metaclass
class RapidInterventionGroupTeam < ActiveRecord::Base
# ==========================
# = Class methods go here! =
# ==========================
class << self
def load_group_allocations_and_teams(project_id, cur_user)
# Code
enddef team_allocations
# Code
enddef group_allocations
# Code
end
end# ============================
# = Instance methods go here =
# ============================
def beef_cow_and_fowl
# Code
end
end
PROs: shortest. Encourages devs to gather singletons in one spot, and perhaps they will notice when it gets out of hand and start creating mixins. Maybe. Also allows devs to show off their Ruby metaclass knowledge at bars. Maybe.
CONs: that class < < self is funky and if funky doesn't rub well with you (or your boss) then I guess it's no good.
I personally favor c) whenever the number of class methods goes above two or three; b) is fine when there are just a few of them.
Invoking singleton methods
In Ruby there are many ways to invoke a class method. A few common ones:
-
Name the class explicitly:
user = User.find(123)
Predators.feed_to_wolves(1, user)
-
Use a helper instance method
class Steak
def self.logger
@logger ||= Logger.new('log/steak.log')
defdef logger
self.class.logger
enddef beef
logger.debug "MONDAY BEEF!"
beef_for!(:monday)
end
end
Using an instance method this way to invoke a class level method is of course useful only if you invoke it very often and readability becomes very important. In the above example we're also memoizing (caching) the class level Logger object for fast access.
When used from an instance of the class, use the context: self.class
class Predators < ActiveRecord::Base
def self.feed_to_wolves(pack_id, meat)
# code
end
# we know how we were born...
def feed_us!(meat)
self.class.feed_to_wolves(id, meat)
end
end
I personally try 1) to avoid singletons altogether, 2) use method b) whenever possible and 3) start asking myself hard and awkward questions when doing a) more than 3 times a day. Too many singletons might be a codesmell.
Multiple return values
First an example of how people often fake multiple value returns in Ruby:
class Something
def many
[calc_monday_beef, calc_total_beef]
end
end
s = Something.new
retval = s.many
DON’T: when coding a method and you realize you need to return more than one value from a method, you should stop and think. You’re probably doing something wrong.
This is actually the reason why Ruby does not provide a way to return multiple values.
If you’re sure you know what you’re doing and want to return more than value here are some tips:
-
do NOT do what I did in the example.
-
use descriptive variable names. “retval” above sucks. Use Ruby multiple value assignments like so:
>> cows, sheep, fish = [1, 5, 3]
=> [1, 5, 3]
>> cows
=> 1
>> sheep
=> 5
>> fish
=> 3
Use this to assign multiple return values to local variables with helpful names:
s = Something.new
monday_beef, total_beef = s.many
-
c) if you’re returning many values and you don’t know beforehand how many values are coming back, use the splat operator (*):
>> me, *others = [:abe, :bob, :caesar, :donald]
=> [:abe, :bob, :caesar, :donald]
>> me
=> :abe
>> others
=> [:bob, :caesar, :donald]
-
d) avoid using array indexes and prefer using Array#first, Array#last:
-
BAD:
s = Something.new
retval = s.many
Beef.find(retval[0], :limit => retval[1])
-
STILL BAD SOMEWHAT BETTER:
s = Something.new
retval = s.many
Beef.find(retval.first, :limit => retval.last)
Why better? Because you communicate to the reader that that ‘retval’ variable only have two values. (Yeah, it still sucks I know)
-
ALSO BAD BUT SOMEWHAT-ER BETTER STILL:
s = Something.new
monday_beef, beef_count = s.many
Beef.find(monday_beef, :limit => beef_count)
-
GOOD:
s = Something.new
Beef.find(s.monday_beef, :limit => s.total_beef_count)
-
Drop the return
Use the fact that Ruby methods return the value of the last expression. The only place where I think an explicit return is legit is when we want to return early and that’s a performance optimization and as we know, premature optimization is the root of all evil, so unless you have benchmarks at hand proving the need for speed, just don’t user ‘return’.
-
Instead of:
def course_description
retval = course_shortname
retval = course_shortname + "(#{project.shortname})" if project
return retval
end
-
Do:
def course_description
course_shortname + (project ? "(#{project.shortname})" : '')
end
Enumerable
Learn to use and love ruby Enumerable module. It’s really really useful.
-
Instead of:
def Vacation.holidaytype_selectbox
types = Array.new
for vt in VacationTypes
types < < vt[:name]
end
@holidaytype_selectbox_model ||= types
end
-
Use:
def self.holidaytype_selectbox
@holidaytype_selectbox_model ||= VacationTypes.map(&:name)
end
-
Instead of:
for result in @candidate.results do
result.destroy
end
-
Use:
@candidate.results.each(&:destroy) -
Instead of:
@approved = 0
unless @do_it.nil?
@events = Event.hourreporting_entries(@person.user_id, params[:projectid], @week.first.date, @week.last.date)for e in @events
if e.approved == 1
@approved += 1
end
end
end
-
Do:
if @do_it
@events = Event.hourreporting_entries(@person.user_id, params[:projectid], @week.first.date, @week.last.date)
@approved = @events.sum{|e| e.approved ? e.approved : 0}
end
The Array#sum method is a Rails addition.
In the app where I spotted the above, the default value for the ‘approved’ field is set to NULL. Had the default value been 0 instead, we could have written the above in an even shorter way:
@events.sum(&:approved)
Truth
In Ruby, everything except false and nil evaluates to true. For realz.
-
Instead of:
if options[:add] == true && check_id.to_i != 0
meanie = MeanGuy.find(check_id)
if meanie.reason != '' && !meanie.reason.nil? && meanie.type == MeanGuy.allowed_types[0]
# Code
end
end
-
Do:
if options[:add] && !check_id.zero?
meanie = MeanGuy.find(check_id)
if !meanie.reason.blank? && meanie.type == MeanGuy.allowed_types.first
# Code
end
end
When chaining conditional checks like above with “&&”, remember that Ruby will stop the checks at the first failure, so always put the ‘cheapest’ checks first (e.g. put any checks that require a database query last).
In the above snippet we see the very common “!something.blank?” idiom. I personally really don’t like the prepended “!” and always try to avoid it. Hurts my eyes and makes me stumble while reading. Ideally the above should be:
if meanie.reason && meanie.type == MeanGuy.allowed_type
but if the meanie reason is allowed to be the empty string — which Ruby will consider True — then we need the explicit blank?-check.
Unrelated neat trick using &&:
-
Instead of:
def safe_death(dude)
if dude
if dude.destroy
if DudeMailer.deliver_destruction_notification
Call.his_mum
end
end
end
end
safe_death(Dude.first)
-
Do:
def safe_death(dude)
dude && dude.destroy && DudeMailer.deliver_destruction_notification && Call.his_mum
end
safe_death(Dude.first)
This works well only if you don’t care about handling any error conditions
and success depends on *all* method calls being successful.
To improve readability in general create as many predicate methods as possible. They are short,
cheap and really helps readability.
-
Instead of:
if @person && @person.active == true && @person.projects.include?(:beef_on_monday) && (@person.type == :human || @person.type == :dog)
# Code
end
-
Do:
class Person
def active_monday_beefer?
actvive? && member_of?(:beef_on_monday) && (human? || dog?)
end
endif @person.active_monday_beefer?
# Code
end
Method naming
When naming your methods, avoid prepending “get_” or “set_”. The Ruby convention is that getters are just the value name and the setter has the “=” postfix. Avoid the quasi-get_’s too: load_, retrieve_, store_, put_. Sometimes they’re ok, but just stop a second and think if:
store_incoming_beef()
is really really better than
more_beef_in_the_holds()
or
beefers_keepers()
or
not_pork_not_sheep_not_fowl_but?()
As always rules are made to be broken, judiciously.
DON’T:
#get_premium, #get_unix_time_stamp, #load_dates, #retrieve_mums etc
Do:
#premium, #unix_timestamp, #dates, #mums, #beefs
DON’T:
Server.set_clock(Time.now)
@doc.store_signer(Dude.new)
@tellus.put_fire(:wild)
Time.set_back(1.year.ago)
Do:
Server.clock = Time.now
@doc.signer = Dude.new
@tellus.fire!(:wild)
Time.now = 1.year.ago
Keep method names short: e.g. #shorten_timespaces_because_of_special_holiday is too long. Long method names are often a sign of complex methods. Complex methods are often a sign we should refactor the code into smaller pieces.
Sometimes the work done by a method is simply too complicated to be expressed properly
by a short and self-explanatory method name. If so, don’t even try. Leave the method name
short and cryptical. It becomes a marker for the reader of “hey, wanna understand this one?
Sorry but you really really have to go read the code!”.
DON’T:
#shorten_timespaces_because_of_special_holiday
DO:
#holiday_adjustment
Use the “?” for predicates (should *always* return true/false or at least something that evaluates to true false, such as “abc”/nil)
class Session
def finished?
finished_at
end
end
sess = Session.new
sess.finished_at = Time.now
sess.finished?
GOOD:
if sess.finished?
coffee!
end
BAD:
if sess.finished_at
coffee!
end
Use rdoc!
User rdoc for your rails apps. It’s actually nice. When your mum asks you what it is you actually do you can show her the rdocs with links and text and colors and lists and it will make her happy too. And very useful for people trying to read your code (ok, they can run the freakin’ rake task themselves, but you should do it too!).
rake doc:app
open doc/app/index
21 Comments
thanks David Palm!
This is super awesome.
Great writeup, lots of good practices in here!
Regarding the explicit return statement: another good reason not to use it unless you need to is that it’s slower. See Chris Wanstrath’s benchmarks: http://gist.github.com/160718
An additional con for using class << self for class methods is that you may not be able to tell whether this is a class or instance method just by looking at it without scrolling up to see if it’s inside a class << self block.
For predicate methods (i.e. finished?), you can precede the return value with !! which forces it to true/false.
Is it more accurate to call the “singleton” methods “class” methods? To me, singletons are objects that may be instantiated only once.
@jeff: do a search in the ruby-lang ML archives. That question has been discussed and re-discussed many many times over the years! Matz calls them ‘singleton methods’ in the C sources and hence many feel that settles it. You’re right though, ‘class methods’ is the more common idiom. I think there was a third nomenclature as well. Can’t remember right now.
As long as we all know what we’re talking about I guess it’s ok, right?
@arya: I didn’t know about the !! trick, very cool. :
I’d argue that predicate methods should always be used as such and their return value never used for anything but true/false-like tests, so it’s not really important that the actually return TrueClass/FalseClass.
I know many programmers that feel queasy about returning, say, a Date object from a predicate method and if an extra cast or two makes them feel safer I’m all for it.
This is a short one: requires go into the environment.rb.
You can, but that’s not really the place it for stuff like that anymore. Since somewhere around Rails 2.1, custom code like that is meant to go into a file in config/initializers.
thanks for a nice list! my 2 cents:
- you can use something.present? instead of the ugly !something.blank? (present? was added in some recent Rails version)
- i like “andand” gem, f.ex, not: if beast && beast.loose?, but:
if beast.andand.loose? A little bit more DRY.
- sometimes i find useful this construction:
if (someobject. ..some ugly chain.. rescue false)@remix: #present? is a great idea, thanks! (use #any? if you want to check collections only).
Re-andand and similar. I used to be a great fan of the andand (much better than #try imho) but in the end I feel that spelling it out wins:
if @dude && @dude.surfs? vs. if @dude.andand.surfs?
Both are ok though I think.
My point in the article though, was that it really pays off if you can code in such as to be able to trust your data not to be nil ever (or put differently, in such a way so that Nils are exceptions and bugs to be fixed).
@peter: I disagree. config/initializers is where I put *app* initialization code, not where I like my environment to be created. I see your point and using config/initializers works excellently. I for one don’t want to browse through 10 files to see what libs are required.= to run the app. I want *one* spot.
Useful and hilarious. Lazy returns should be used 99% of the time, once in a while you need to make it explicit (def contains a few if statements, etc).
@events.compact.sum(&:approved) can handle those nil’s
@chris: nah, it’s not the @events that may contain Nils, it’s the attribute values that, allowed to be NULL in the DB, can come across as Nils instead of 0.
Great stuff, enjoyed reading it! How about @approved = @events.sum{|e| e.approved || 0}
Am I missing something, or is DO:
Time.set_back(1.year.ago)The same as DON’T:
Time.set_back(1.year.ago)Great article – I’ve found a few things I can clean up in my own code.
@mlambie: you’re right, typo. Thanks!
@michael: you’re right, that’s a great solution.
Regarding multiple return values, I suggest that “you communicate to the reader that that ‘retval’ variable only have two values” is perhaps optimistic: all that using #first and #last tells me is that I’m only interested in the two extremeities of a possibly aribitrarily long array! But as you say, it sucks. I think it’s an evolutionary thing: at some point we learn that we can return multiple values into a list of receivers (possible terminology fail there) and we feel much more Ruby-ish. Then – and I’m only just arriving there at the moment – we realise how we could be more intention-revealing. I’ve been having a lot more fun returning Structs or OpenStructs (for the lazy) of late.
@mike: you’re right of course and #first/#last is merely the first baby step to unsuckify the code. Often times when talking to inexperienced programmers I find that I have to be mighty careful not to step on egos and hurt peoples’ feelings. In those cases it’s useful to convince them of small changes and then come back the week after suggesting a helper method or two, and in the end help them see how refacotoring the whole thing not to need multiple returns at all is the way to go…
Amazing list, thanks
you forgot the most elegant way to open the singleton class:
class C
class << C
def singleton_method() end
end
end
i personally always add this method
def singleton_class(&block)
@singleton_class ||= (
class << self
self
end
)
block ? @singleton_class.module_eval(&block) : @singleton_class
end
to pretty much every ruby project. it allows for
class C
singleton_class do
def singleton_method() end
end
end
and also
a = Array.new
a.singleton_class.module_eval{ alias_method ‘length’, ‘size’ }
really very useful.
end
@ara: how is
class B
class << B
def beefer() end
end
end
…different from what I suggested:
class B
class << self
def beefer() end
end
end
I agree that having a reference to the metaclass around is very handy and the ‘singleton_method(&blk)’ is neat, but the beauty of it becomes apparent only when you begin to grasp how the whole class/metaclass machinery fits together. I believe many programmers don’t (yet!) and I often meet resistance at the “class << self” part. There is so much bad Ruby code out there right now that need the basics taken care of first that I’m hesitant rising the level further…
:/
thanks