When reddit announced that they were going to be offering users the ability to create their own reddits, I sent them an email asking for a beta account. Guess what? They gave me one!
So, rubyists everywhere, here it is: our very own ruby reddit!
When reddit announced that they were going to be offering users the ability to create their own reddits, I sent them an email asking for a beta account. Guess what? They gave me one!
So, rubyists everywhere, here it is: our very own ruby reddit!
When one speaks of the readability of a computer program, they refer to the ease with which the source code is read by a human. More recently, with languages like Ruby and Python, one often hears praise for sections of code that seem to read like natural language. That sort of code is easier read, but seems to be more challenging to write. If english-like is what we're after, perhaps style manuals can teach us something about writing code.
Having always been interested in writing English, I've read several books on style. The text that I continue to return to is Strunk & White's The Elements of Style. It has served me well as a reference for writing English; let's see how it does for Ruby.
The rule from Strunk & White:
Make definite assertions. Avoid tame, colorless, hesitating, noncommittal language. Use the word not as a means of denial or in antithesis, never as a means of evasion.
Elements of Style provides a short explanation of each rule, followed by several illustrative examples. Each example contains a violating passage, and a correction. Here's an example from this rule...
The violating example:
He was not very often on time.
...is corrected by:
He usually came late.
Yes, Strunk and White provide refactorings; I'll do the same.
if !@post.save
render :action => 'create'
else
redirect_to post_url(@post)
end
That example is rather simple. It says: If the post doesn't save, render the create action, otherwise, redirect to the post's url. I think we can agree that in most cases, it would be better to express that as follows:
if @post.save
redirect_to post_url(@post)
else
render :action => 'create'
end
By putting our statements in positive form, we have refactored that code to read: If the post saves, redirect to its url, otherwise render the create action. Because we have eliminated the negation, the code reads more smoothly and easily. While that is an obvious example, it shows that there is something to this. Let's look at another rule.
The rule from Strunk & White:
The active voice is usually more direct and vigorous than the passive.
The violating example:
It was not very long before she was very sorry that she had said what she had.
The correction:
She soon repented her words.
This rule doesn't apply as directly, but I have always loved it as a style guideline for code. Take this rather common example:
[1, 2, 3].each do |number|
if number.even?
# do something with an even number
end
end
That example says: With each element in this array, if that element is even, do something with the even element. Collecting the desired elements that way reads passively. Making use of some more of our tools from ruby's Enumerable module, we can refactor it to use the active voice.
[1, 2, 3].select(&:even?).each do |number|
# do something with an even number
end
The refactoring says: With all of the even numbers in this array, do something. It reads shorter, and expresses the intent of the coder more directly.
The next example we're going to look at comes from ActionController::Base (a central class in rails). I have selected a few illustrative branches from a conditional that spans some sixty lines, in a method that spans nearly one hundred. It is the rendering logic:
if file = options[:file]
render_for_file(file, options[:status], options[:use_full_path], options[:locals] || {})
elsif template = options[:template]
render_for_file(template, options[:status], true)
elsif inline = options[:inline]
add_variables_to_assigns
render_for_text(@template.render_template(options[:type], inline, nil, options[:locals] || {}), options[:status])
elsif action_name = options[:action]
template = default_template_name(action_name.to_s)
if options[:layout] && !template_exempt_from_layout?(template)
render_with_a_layout(:file => template, :status => options[:status], :use_full_path => true, :layout => true)
else
render_with_no_layout(:file => template, :status => options[:status], :use_full_path => true)
end
end
It says (bear with me): If the file option is present, render the file, or if the template option is present, render the template, or if the inline options is present, pass the instance variables to the template, and render some inline text from the controller, or if the action option is present, render that action. Note: that transliteration has been shortened somewhat.
The passiveness of a multi-branch conditional damages clarity; it creates a meandering experience for the reader. The problem is that separate concerns are being forced together. In the case of the render method, the conditional determines which type of rendering to perform, and contains the actual code to perform that type of rendering.
Long conditionals also seem to resemble run-on sentences. Since one must remember all of the context as they read through the branches of this sort of structure, it quickly becomes difficult to follow. It would be better to separate the decision from the specific rendering logic; that will make it easier to be more direct.
def render
render_types = [:file, :template, :inline, :action]
type_to_render = render_types.detect { |render_type| options[render_type] }
send(:"render_#{type_to_render}")
end
def render_action
template = default_template_name(action_name.to_s)
if options[:layout] && !template_exempt_from_layout?(template)
render_with_a_layout(:file => template, :status => options[:status], :use_full_path => true, :layout => true)
else
render_with_no_layout(:file => template, :status => options[:status], :use_full_path => true)
end
end
def render_inline
add_variables_to_assigns
render_for_text(@template.render_template(options[:type], inline, nil, options[:locals] || {}), options[:status])
end
def render_template
render_for_file(template, options[:status], true)
end
def render_file
render_for_file(file, options[:status], options[:use_full_path], options[:locals] || {})
end
Now, it reads: The render types are file, template, inline, or action. The type to render is the one that is present in the options hash. Call the method named: render underscore the type to render. Refactored, each of the ways of rendering gets its own method, and those methods have a name. That way, it is clear what each of those blocks of code does, and what we're doing when we call it; we needn't decipher a conditional to find our way to the pertinent logic. Separating code in to small chunks housed by well named methods allows us to express our intentions directly.
As an aside, writing methods this way is far more testable. In its original form, the render method does many different things, depending on the parameters it receives. Testing it would be an exercise in managing side effects. You'd also be testing two different things at once: does it reach the correct branch of the conditional, and does it execute the rendering properly. With the simple refactoring above, you can test the rendering logic separately from the selection of which type of rendering to perform. Dividing methods in to smaller pieces nearly always allows you to isolate functionality more effectively.
I've only scratched the surface in this article. There is a lot more to be learned from natural language writing style. I highly recommend picking up a copy of The Elements of Style. Worst comes to worst, you'll greatly improve your writing of English; best case scenario, you'll pick up a lot of insightful tips on writing clearer and more readable code.
If you're still writing for loops, stop; there's a better way. Exactly why merits some examples.
[1, 2, 3].each { |number| puts number }
That's a pretty straightforward one. It says: with each element in this array, execute the given block. But, what if we wanted to do something more complicated, like determine whether there were any even numbers in the array?
[1, 2, 3].any? { |number| number % 2 == 0 } # => true
[1, 3, 5].any? { |number| number % 2 == 0 } # => false
The any? method says: are there any elements in the array for which this block evaluates to true? We might also want to know whether all? of the elements were even.
[2, 4, 6].all? { |number| number % 2 == 0 } # => true
[1, 2, 3].all? { |number| number % 2 == 0 } # => false
We can see that making good use of Enumerable methods produces highly readable code. Before we move on, there are a couple more methods that are worth demonstrating.
We've already seen how we can easily look inside of an array, using any?, and all?. Conveniently, Enumerable provides similar methods for extracting objects. What if we wanted, for example, to retrieve all of the even numbers.
[1, 2, 3, 4].select { |number| number % 2 == 0 } # => [2, 4]
...or, the first even number...
[1, 2, 3, 4].detect { |number| number % 2 == 0 } # => 2
Let's pick up the pace.
If I was writing some blogging software, I might want to display recent activity in the administration area. With a big array of notification objects, I can use select to easily divide them up for display, based on recency.
today = @notifications.select { |notification| notification.created_at > 1.day.ago.to_date }
yesterday = @notifications.select { |notification| notification.created_at < 1.day.ago.to_date && notification.created_at > 2.days.ago.to_date }
earlier = @notifications.select { |notification| notification.created_at < 2.days.ago.to_date }
I'm actually doing something very similar to this in an app I'm working on. Since I love fat models, I created some helper methods on AR::Base. They look (something) like this:
class ActiveRecord::Base
def today?
created_at > 1.day.ago.to_date
end
def yesterday?
created_at < 1.day.ago.to_date && created_at > 2.days.ago.to_date
end
def earlier?
created_at < 2.days.ago.to_date
end
end
So, now, we can greatly simplify our use of the select method, making it far clearer what we're up to.
today = @notifications.select(&:today?)
yesterday = @notifications.select(&:yesterday?)
earlier = @notifications.select(&:earlier?)
...wait, what? The ampersand operator, as described by the PickAxe book:
If the last argument to a method is preceded by an ampersand, Ruby assumes that it is a Proc object. It removes it from the parameter list, converts the Proc object into a block, and associates it with the method
But, a symbol isn't a Proc object. Luckily, ruby calls to_proc on the object to the right of the ampersand, in order to try and convert it to one. Symbol#to_proc is defined in ActiveSupport; it creates a block that calls the method named by the symbol on the object passed to it. So, for the above examples, the blocks send today?, yesterday?, or earlier?, respectively, to the each object that is yielded. Written out by hand, the expanded blocks would look like this:
today = @notifications.select { |notification| notification.today? }
yesterday = @notifications.select { |notification| notification.yesterday? }
earlier = @notifications.select { |notification| notification.earlier? }
For additional to_proc coolness, it's worth mentioning Reg Braithwaite's String#to_proc (or get the code). I won't go on any further about that today, though.
Let's look at one last example — this time from real software. In order to add support for polymorphic, deep nesting in resource_controller, I had to write an algorithm that would determine which of the possible sets of parent resources were present at the invocation of a controller action. This is done by testing to see which of the params (parent_type_id) are present. So, if I say belongs_to [:project, :message], [:project, :ticket] in my comments controller, I need to determine: for which of those two possible sets are both necessary params present? Since belongs_to is just a simple accessor, the algorithm is easy to write.
belongs_to.detect { |set| set.all? { |parent_type| params["#{parent_type}_id".to_sym].nil? } }
Enumerable's biggest advantage over the for loop, as we've seen, is clarity. Using Enumerable's looping power tools, like any?, all?, and friends makes your code read semantically. Especially combined with Symbol#to_proc, code "...seems to describe what I want done instead of how I want the computer to do it.", to quote Raganwald.
Finally, in order to take your Enumerable usage to the next level, there are a few more methods you should have in your toolbox.
I'd also recommend keeping the Enumerable, and Array documentation bookmarked, so that you can quickly refer to them. As a bonus, most of these methods exist in prototype.js, too; just take a look at the documentation.
I'm headed to Toronto for the next couple of weeks — leaving Tuesday as you might have guessed from the title. If any Torontonians want to meet up for a coffee (yes, I am a coffeegeek), a beer (but, really, preferably a coffee), or a hack session (preferably including coffee), shoot me an email (my address is listed top-right).
Bothered by all the noise in my tests' backtraces, I was thrilled when I first saw a thread on the shoulda mailing list with some discussion around making them a little bit easier on the eyes. Assuming that creating such a filter would be a long and tedious process of monkey-patching test/unit, I forgot about the idea, assuming the job better left for somebody with more time to spare than myself.
When Dan Croak revived the thread with some sample code, cooked up at a Boston.rb hackfest, it occurred to me that the job was far more manageable than I had originally conceived. I quickly fired Dan an email asking whether he'd be interested in a pluginization of their concept. With a resounding yes! from Dan, we set off to create quiet_stacktracebacktrace.
The rest of this post is cross-posted on GIANT ROBOTS
90% of this typical backtrace will not help you hunt and kill bad code:
1) Failure: test: logged in on get to index should only show projects for the user's account. (ProjectsControllerTest) [/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/assertions.rb:48:in `assert_block' /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/assertions.rb:500:in `_wrap_assertion' /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/assertions.rb:46:in `assert_block' /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/assertions.rb:63:in `assert' /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/assertions.rb:495:in `_wrap_assertion' /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/assertions.rb:61:in `assert' test/functional/projects_controller_test.rb:31:in `__bind_1196527660_342195' /Users/james/Documents/railsApps/projects/vendor/plugins/shoulda/lib/shoulda/context.rb:98:in `call' /Users/james/Documents/railsApps/projects/vendor/plugins/shoulda/lib/shoulda/context.rb:98:in `test: logged in on get to index should only show projects for the user's account. ' /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testcase.rb:78:in `__send__' /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testcase.rb:78:in `run' /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:34:in `run' /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:33:in `each' /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:33:in `run' /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:34:in `run' /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:33:in `each' /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:33:in `run' /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/ui/testrunnermediator.rb:46:in `run_suite' /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/ui/console/testrunner.rb:67:in `start_mediator' /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/ui/console/testrunner.rb:41:in `start' /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/ui/testrunnerutilities.rb:29:in `run' /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/autorunner.rb:216:in `run' /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/autorunner.rb:12:in `run' /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit.rb:278 test/functional/projects_controller_test.rb:36]: one or more projects shown does not belong to the current user's account. <false> is not true.
Noisy backtraces must be ruthlessly silenced like political dissidents in Stalinist Russia. This much is clear.
Install the gem:
sudo gem install quietbacktrace
Require quietbacktrace:
## test_helper.rb
require 'quietbacktrace'
Run your Test::Unit tests:
1) Failure: test: logged in on get to index should only show projects for the user's account. (ProjectsControllerTest) [test/functional/projects_controller_test.rb:31 vendor/plugins/shoulda/lib/shoulda/context.rb:98 vendor/plugins/shoulda/lib/shoulda/context.rb:98 test/functional/projects_controller_test.rb:36]: one or more projects shown does not belong to the current user's account. <false> is not true.
Ooh la la! Now we’re cooking with gas. However, those shoulda-related lines are cluttering an otherwise perfect backtrace. Luckily, Quiet Backtrace is designed to be extended by calling two types of blocks that yield one line of the backtrace at a time.
Say you want to remove Shoulda-related lines… you create a new silencer and add it the Array of backtrace_silencers:
class Test::Unit::TestCase
self.new_backtrace_silencer :shoulda do |line|
line.include? 'vendor/plugins/shoulda'
end
self.backtrace_silencers << :shoulda
end
Re-run your tests and bask in the sweet sounds of silence:
1) Failure: test: logged in on get to index should only show projects for the user's account. (ProjectsControllerTest) [test/functional/projects_controller_test.rb:31 test/functional/projects_controller_test.rb:36]: one or more projects shown does not belong to the current user's account. <false> is not true.
Exquisitely sparse. Quiet Backtrace clears distractions from the “getting to green” TDD process like a Buddhist monk keeping his mind clear during meditation.
On occasion, you’ll want to see the noisy backtrace. Easy:
class Test::Unit::TestCase
self.quiet_backtrace = false
end
You can set Test::Unit::TestCase.quiet_backtrace to true or false at any level in your Test::Unit code. Stick it in your test_helper.rb file or get noisy in an individual file or test. More flex than a rubber band.
After you have installed the gem on your local machine, I recommend using the excellent gemsonrails plugin to freeze it to your vendor/gems directory and automatically add it to your load path.
Install gemsonrails and add it your Rails app if you don’t already have it:
gem install gemsonrails cd rails-app-folder gemsonrails
Then freeze quietbacktrace:
rake gems:freeze GEM=quietbacktrace
Quiet Backtrace will now work with your tests, but because this gem is meant to work on any Ruby project with Test::Unit, it does turn any Rails-specific silencers or filters on by default. However, there is one of each, ready to be switched on, that remove the most dastardly lines.
Add these lines to your /test/test_helper.rb file to get perfectly clean Rails backtraces:
class Test::Unit::TestCase
self.backtrace_silencers << :rails_vendor
self.backtrace_filters << :rails_root
end
Bug reports and patches are welcome at RubyForge. Talk about it on the mailing list. It’s is a safe place. A place where we can feel free sharing our feelings. A nest in a tree of trust and understanding.
class Person
protected
def something_secret
"don't tell anybody"
end
private
def something_really_secret
"REALLY don't tell anybody"
end
end
## Ruby 1.8.5
person.instance_eval { self.something_secret } # NoMethodError: protected method `something_secret' called for ...
person.instance_eval { self.something_really_secret } # NoMethodError: protected method `something_really_secret' called for ...
person.send :something_secret # "don't tell anybody"
person.send :something_really_secret # "REALLY don't tell anybody"
## Ruby 1.8.6
person.instance_eval { self.something_secret } # "don't tell anybody"
person.instance_eval { self.something_really_secret } # "REALLY don't tell anybody"
person.send :something_secret # "don't tell anybody"
person.send :something_really_secret # "REALLY don't tell anybody"
@post.comments.reject!(&:spam?)
@post.save
Lots? Me too. So, I think it's worth clearing the air on our good friend association proxy. He really, really wants you to believe he's an array. He'll even tell you he is.
assert @post.comments.is_a?(Array)
But, don't believe him, because a lot of his array functionality is useless to us. Your favourite rubyisms (reject!, select!, etc) will modify the collection, but will not modify the associations. This test passes:
def test_association_proxy_really_looks_like_an_array_but_it_isnt
assert Post.find(1).comments.is_a?(Array)
assert Post.find(1).comments.select(&:spam?).length > 0
post = Post.find(1)
post.comments.reject!(&:spam?)
post.save!
assert_equal [], post.comments.select(&:spam?)
assert Post.find(1).comments.select(&:spam?).length > 0
end
Instead, of course, you have to use the delete method.
post.comments.delete(post.comments.select(&:spam?))
Association proxy is tricky.
class RecipesController < ApplicationController
# GET /recipes
# GET /recipes.xml
def index
@recipes = Recipe.find(:all)
respond_to do |format|
format.html # index.rhtml
format.xml { render :xml => @recipes.to_xml }
end
end
# GET /recipes/1
# GET /recipes/1.xml
def show
@recipe = Recipe.find(params[:id])
respond_to do |format|
format.html # show.rhtml
format.xml { render :xml => @recipe.to_xml }
end
end
# GET /recipes/new
def new
@recipe = Recipe.new
end
# GET /recipes/1;edit
def edit
@recipe = Recipe.find(params[:id])
end
# POST /recipes
# POST /recipes.xml
def create
@recipe = Recipe.new(params[:recipe])
respond_to do |format|
if @recipe.save
flash[:notice] = 'Recipe was successfully created.'
format.html { redirect_to recipe_url(@recipe) }
format.xml { head :created, :location => recipe_url(@recipe) }
else
format.html { render :action => "new" }
format.xml { render :xml => @recipe.errors.to_xml }
end
end
end
# PUT /recipes/1
# PUT /recipes/1.xml
def update
@recipe = Recipe.find(params[:id])
respond_to do |format|
if @recipe.update_attributes(params[:recipe])
flash[:notice] = 'Recipe was successfully updated.'
format.html { redirect_to recipe_url(@recipe) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @recipe.errors.to_xml }
end
end
end
# DELETE /recipes/1
# DELETE /recipes/1.xml
def destroy
@recipe = Recipe.find(params[:id])
@recipe.destroy
respond_to do |format|
format.html { redirect_to recipes_url }
format.xml { head :ok }
end
end
end
...with this:
class RecipesController < ApplicationController
make_resourceful do
actions :show, :index, :create, :edit, :update, :destroy
end
end
That's just the scaffolding. What if I want to start using a permalink for my model, to satisfy the SEO department?
def current_object
current_model.find_by_permalink(current_param)
end
...but, now my param is called id, and that's not really very accurate. Can I change it?
def current_param
params[:permalink]
end
...what about paging? (using the paginating_find plugin)
def current_objects
current_model.find(:all, :order => "created_at DESC", :page => {:current => params[:page], :size => 10 } )
end
...what about all of my fancy respond_to blocks, and RJS tricks?
response_for :show do |format|
format.html
format.js
end
...WOW. Where can I get it?
Here.
belongs_to :user, :company
Had the author of this code written tests for his code, or even run the generated test suite for his model, he might have realized that he'd made a typo, and that the belongs_to method doesn't accept multiple models as its first argument, and that the correct code would have looked like this:
belongs_to :user
belongs_to :company
Test first, test always.# PostsController#show # # Inputs: # params[:id] # Outputs: # @post <-- contains the Post which corresponds to the params[:id] input parameter # OR # throws ActiveRecord::RecordNotFound if Post w/id == params[:id] does not exist