Find or find_by_id

By Renzo Borgatti on January 27th, 2009

Tagged with: rails, finders, exceptions, programming, ruby

If you are a Rails dev, you probably know that there are two "finder" behaviors in Rails. There is User.find(1) that raises an ActiveRecord::RecordNotFound if the record with the given ID was not found. There are also generated finders like User.find_by_id(1) that return nil instead. Inconsistency? I don't think so. Let's take the typical controller update method:
 
 1  def update
2 @user = User.find(params[:id])
3 respond_to do |format|
4 if @user.update_attributes(params[:user])
5 flash[:notice] = 'User was successfully updated.'
6 format.html { redirect_to(@user) }
7 else
8 format.html { render :action => "edit" }
9 end
10 end
11 end
 
The find method will raise RecordNotFound if for any reason the searched user cannot be found. Here is the same thing but checking if the returned user instance is null:
 
 1  def update
2 @user = User.find_by_id(params[:id])
3 if @user
4 respond_to do |format|
5 if @user.update_attributes(params[:user])
6 flash[:notice] = 'User was successfully updated.'
7 format.html { redirect_to(@user) }
8 else
9 format.html { render :action => "edit" }
10 end
11 end
12 end
13 end
 
I think the two options both have their place based on the following consideration:
 
  • Probability that the record will be found or not. Maybe the object is not a User but something more volatile like an Object which deleted from the database when it's "consumed".
  • There is a general catch mechanism by Rails when an exception raises up to the last possible moment. If you decide not to catch something you know a courtesy page can be presented and the error will be written in the log
  • If you decide to catch the exception you need to know what to check and maybe you need to look at the documentation.
  • Finally, there are cases where you really need to do something different if the record was not found than leave the error to rise. In this case you have the two options: find with catch or find_by_id with null checking.

Find should be used without catch in 90% of the cases. Because most of the time the ID you're searching for is just the result of a previous request on an object that is still there almost for sure. You can accept the 0.01% of cases where the find call rises an error that you can later track in the error log. If find had the same behavior of find_by_id (let's say for consistency reason), you'll be forced to check for null. If you let it go you end up with "nil when you didn't expect it" which is not as clear as a RecordNotFound.

If you need to catch that the record doesn't exist, then you have two options: catch the RecordNotFound or use find_by_id and check for null. I'm lazy and I prefer the second one: I don't need to check the documentation to remember what to catch. 

Happy coding coders!

What a difference a line makes

By Renzo Borgatti on December 12th, 2008

Tagged with: rails, activerecord, finders, sql, performances

I’m all for keeping the business logic in places where it can be easily isolated and eventually reused. I’m sure I’m not saying anything new, but there are cases where it makes a lot of sense to delegate. Examples of those cases are stored procedures and complex queries. Relational databases are pretty smart in parsing and optimizing SQL and when the database is a separate process on another machine it’s even more efficient to use a different CPU than the main application process.

Today I had the pleasure to apply one fast and simple change that drastically reduced the computation load. Here’s the “before”:

Song.find(:all).select(&:rating).sort_by(&:rating).reverse.first(10)

and here’s the “after”:

Song.find(:all, :joins => [:ratings], :order => "rating DESC", :limit => 10)

Assuming N is the number of songs in the database in the first case you need to load N object instances and issue N + 1 queries to load information about the rating of a specific song. In the second case 1 query is needed and all the filtering is delegated to the database. The nice thing is also the way the SQL string is hidden beautifully in the DSL-ish method call which is very similar to the first version. I remember the old days (for me!) in 2002 when this was just something like connection.executeStatement(“SELECT * BLAH…”) or something and I had to create my own SQLBuilder to reuse and test some of that SQL. O/R mappings tools and fluent interfaces of today are just a huge leap forward.

The take away for the Rails developer: think twice whenever you write or see a find(:all) with no options. Look carefully the console output to see what SQL Rails is generating and how long does it take. If the result set it operates on is reasonably small, no big deal. If the collection is huge, please delegate to the database.

Hey, by the way, do you see what the query is doing? :)

Poor Men Story Runner

By Renzo Borgatti on October 30th, 2008

Tagged with: rspec, rails, storyrunner

Say that for some reasons like for example time constraints on the project, lack of customer collaboration or lack of business analysts you can’t maintain a full fledged automated story base. In that case one option could be to maintain the story runner format to describe the feature to implement but not try to make that executable. I know, I know, what a waste for such a great opportunity. But I’m talking about a desperate case.

As funky as it sounds, you can try to implement the plain text story in rspec as controller specs with integrated view. For example:

 1  describe BlogsController, "blog details page" do
2 integrate_views
3
4 before(:each) do
5 Given "the user is logged in"
6 When "I click on a blog post"
7 end
8
9 it { Then "the blog detail view is displayed" }
10 it { And "the full text of the blog is shown" }
11 it { And "the comment list section is shown" }
12
13 end
14
15 [:Given, :When, :Then, :And].each do |method_name|
16 self.class.send :define_method, method_name do |what|
17 self.send(what.gsub(' ','_'))
18 end
19 end

Yes, very very not-readable. It just destroy all the effort to create stories in plain text. But it’s something a developer can read. The next step is to create a method for each of the descriptive texts, replacing blanks with underscores:

 1  def the_user_is_logged_in
2 @user = login
3 end
4
5 def I_click_on_a_blog_post
6 @blog = stub_model(Blog,
7 :title => "Random thoughts.",
8 :comments => comments,
9 :text => "MIDWAY upon the journey of our life")
10 Blog.should_receive(:find_by_id).with(1).and_return(@blog)
11 get :show, :id => 1
12 end
13
14 def the_blog_detail_view_is_displayed
15 response.should have_tag("div#blog") do
16 with_tag("strong", @blog.title)
17 end
18 end
19
20 def the_full_text_of_the_blog_is_shown
21 response.should have_tag("div#blog") do
22 with_tag("div#text", @blog.text)
23 end
24 end
25
26 def the_comment_list_section_is_shown
27 response.should have_tag("div#blog") do
28 with_tag("div#comments") do
29 with_tag("div#comment", 5)
30 end
31 end
32 end

As you probably guessed already, this is the first scenario for a blog section on some web page. You can click on the “more” link to read more and access comments to the blog post. The plan is to implement the other scenarios in the same spec file changing the describe block to change the target controller. The specs are just scratching the surface of the view, then you’ll need to change spec to implement other part of the system.

Another interesting thing is that for this to be called “integration” test, I shouldn’t have used any stub_model instruction. Correct. First step I want to create it fast, no database thinking. Second step I’ll remove all stubs and using some way of talking to the database (FixtureReplacement for example) I’ll be using real connections to the model and therefore to the database. At the end, a kind of acceptance testing.

I know what you’d like to ask now: is this really any better than just plain run the Story Runner itself? Not at all. It’s absolutely a bad practice to use RSpec like this, I don’t want to suggest in any way that this can substitute the story runner. But in case of emergency (read, fixed scope, fixed price, fixed time projects) where the customer is not helping writing stories, where there are no business analysts and everything should be done by developers, dropping the story runner is probably the less of all possible pains. You should also consider that if you are using Watir or Selenium on Rails connected to the story runner, the effort of maintaining such an environment sensible acceptance framework is big.

Specifically:

  • You don’t need the infrastructure to run stories which is usually a separated task from running specs
  • You don’t need to maintain steps either
  • You don’t need to attach the story runner binary to the continuous integration, stories will run as normal specs.
  • You are free to maintain story texts outside the project, on a wiki or online somewhere.
  • Coverage with rcov is just part of the package. For stories you can’t know how well are you covering that given aspect of the application. At least, I thought about it and I don’t know how to deal with the problem.


Well, hope you can forgive me for this post but find the idea useful for your project.

matchers_in_have_tag_blocks.should be(/with_tag/)

By Phil Matarese on October 15th, 2008

Tagged with: rails, rspec

The Setup

When writing test code for Rails apps, I prefer Rspec.  For me the spec definition syntax feels more natural.  Though, the other day I got tripped up by the Rspec equivalent of assert_select.  With standard rails tests, the assert_select method can be nested to help ensure properly structured html elements.

1  # This tests that both users show up in the yes_list.
2 # As opposed to the no_list I guess.
3 assert_select 'div#yes_list' do
4 assert_select 'div#user', /#{@user1}/
5 assert_select 'div#user', /#{@user2}/
6 end

Rspec uses have_tag as a wrapper to this method, but uses with_tag and without_tag to wrap the method when called inside the nesting.  The without_tag allows for simpler negative assertions, in case we wanted to test that user3 was not in the list for example.

1  # This also tests that both users show up in the yes_list.
2 response.should have_tag('div#yes_list') do
3 with_tag('div#user', {:text => /#{@user1}/})
4 with_tag('div#user', {:text => /#{@user2}/})
5 end

Once you know this, I think the spec code looks very clear.  Unfortunately, calling have_tag within a have_tag block won't raise an error.  There's no mechanism that will warn you that your inner have_tag is ineffective.  The results of the inner have_tag will simply be ignored.

The Pitfall

When I first wrote the above spec, I used have_tag and have_text instead of with_tag.  And to my naïve eyes, everything looked great.  The code ran and the tests passed.  Done and done.

 1  # This merely asserts the existence of a yes_list,
2 # no matter who's on the list.
3 # Here at the Rails Loft, we're more discerning than that.
4 response.should have_tag('div#yes_list') do
5 have_tag('div#user') do
6 have_text(/#{@user1}/)
7 end
8 have_tag('div#user') do
9 have_text(/#{@user2}/)
10 end
11 end

This was completely wrong.  The tests were passing, but they still passed when I removed @user1 from the yes_list one.  When used inside another have_tag block, a have_tag's assertions was completely ignored.  I think this had something to do with the inner have_tag setting up a new AssertSelect object that then got ignored by the outer AssertSelect object.  The reason for my misunderstanding doesn't really matter, though.  What matters is that I thought everything was fine; all the tests were passing after all.  Luckily, I fiddled with the code a bit before committing, and discovered that reversing an important condition in my application code didn't affect my test one bit.  Whoops.  This was a mistake that continuous integration and testing metrics could have never caught, which emphasized to me how important it is to be thorough and careful when writing tests.

The Lesson

Always, always, always make sure your tests fail before you make them pass.  Your tests are there to ensure proper functionality of application code, and this is the only way you can be certain your test code is doing what you expect.  This doesn't necessarily mean you always have to use test-first development, though you should certainly give TDD a try.  Making your tests fails could be as simple as toggling a conditional in your application code or changing the id of a tag against which you're matching.  This way you'll be confident that your tests are truly working the way they appear to work.

Thinking in Legacy

By Scott Roth on May 21st, 2008

Tagged with: legacy

If you get my reference in the title of this post, then you have probably been around for a while and may have fallen into the thought trap I did the other day.  Those of you that don't know Bruce Eckel's book probably are free from the "experience" (more accurately described as "baggage") that causes people to think in legacy patterns.

I was working on the metrics reporting section of a Rails application recently.  We needed to display a value which was derived by summing up a subset of the rows in a metrics table.  The metaphorical lightbulb went off over my head - I can do all this work in the database and just return the final result to my application!

So, I got busy and when I was done I had a a perfectly ugly SQL string using the sum function hardcoded into my model.  I then used the always nice call of "ActiveRecord::Base.connection.execute(hardcoded_sql_string)" to get my result back.  I thought job well done and moved on with my life.

Big Iron

Well, one of my less scarred co-workers saw the commit email go through and asked me why I was tainting the project with such hideous code.  Well, I proudly proclaimed, thinking I was about to teach a lesson, you always want to push the processing as far down the stack as possible - after all, those database boxes are Big Iron.  In addition, if you do the calculation in the database, you don't have to push all the rows over the wire in order to do a simple calculation in the app.

But, Scott, my co-worker exclaimed, the database is on the same box as the application server.  And as he said it, I realized I had pre-optimized based on a set of assumptions that didn't make sense for this application.  This application wasn't in some bloated enterprise infrastructure where there was a huge Oracle box lurking in the background with a firewall or two between it and the application server.  So, we rewrote the code in a more Railsy way, pulling back the data and populating the objects and iterating through them to arrive at our results, and, lo and behold, there wasn't a performance difference between the two methods - and the Rails way was much easier on the eyes and for any future developers to understand.

I'm not advocating that you never drop down to SQL in your Rails apps.  ActiveRecord is definitely a leaky abstraction (though a handy one) and it is well known that you can get into performance problems quickly if you don't understand the queries that are being made behind the scenes.  However, in this particular case, I had no reason to drop down an abstraction level to direct SQL.  I gained nothing and made the code less maintainable.  This specific example really just made me realize that we all carry around internalized assumptions that can turn out to not be valid for all situations.  It's good to remember this as we move from project to project, language to language, infrastructure to infrastructure, and environment to environment.