The Rails Way

Well, scaffolding isn’t everything. There’s much more in Rails to see. This week we take a closer look at what is known as “The Rails Way” of doing things.

We’ve mentioned it in passing, but Rails is based on the Model-View-Controller design pattern. This interaction is played out between ActiveRecord, ActionController and ActionView, or in a different light your data, your logic and your presentation. We’ve had a look at Models so far with ActiveRecord, and how that helps us translate database rows into objects we can work with in our application. So now, let us focus a bit more on the other two parts of the equation.

Controllers, bridging the gap between Models and Views

When users visit your application, say visiting a page like /shows/list, Rails reacts by reaching for app/shows_controller.rb (matching /shows/) and within that file running the action list and passing the results to the templates associated with app/views/shows/list.rhtml. The purpose of actions can include manipulating and selecting objects from your data models. Controllers allow you to reach into your models and collect the data you need to build your views. Working the other direction, they also allow input received from your views to be processed into workable data for your models. Controllers are really the workhorse of your application, the go-to, production assistant, “go get my coffee” position. Be good to your controllers.

ShowsController < ApplicationController def list @shows = Show.find(:all) # @shows now holds an array of show objects that will get passed to # your show templates to display and output to the user end

The above code works by calling the Show object, finding :all rows from the database and saving them as an array of Show objects in the instance variable @show, which will get passed along to the associated rhtml templates. Here’s how you might display the results to the user with app/views/shows/list.rhtml:

<h2>My shows</h2> <div id="content"> <ul> <% for show in @shows %> <li><%= show.name %> - <%= show.description %></li> <% end %> </ul> <%= link_to('Add New Show', { :action => :new }) %> </div>

First off you probably noticed the ERB syntax for embedding Ruby into html, which is denoted by <% _ruby expression_ %> and <%= _print ruby value_ %>. The most action is happening with our for loop printing out the attributes of each Show element in shows. Note that the for statement is enclosed in <% %> with no = sign, since it's plain ruby code. The <%= %> gets used to output values like show.name and the result of link_to a oft-used url helper function that will return a html link based on the parameters set. The end result is a html page that looks like:

<h2>My shows</h2> <div id="content"> <ul> <li>The Closer - sweets-loving detective</li> <li>American Idol - simon hates you</li> <li>Veronica Mars - snarky, kick-ass sleuth</li> <li>The Amazing Race - race around the world</li> </ul> <a href="/shows/new">Add New show</a> </div>

If this was something that you used a lot, found in several places with little variation or just might be nicer in a method call then we can put it in a helper. There’s a helper created for each controller as well as application_helper, so when we visit /shows/* we activate the application_helper and the shows_helper. If we want to move the listing code above into a method it might look like this:

module WeeksHelper def shows_listing(shows) html = shows.collect do |show| "<li>#{show.name} - #{show.description}</li>" end content_tag('ul', html.join("\n")) end end

And here’s the change to list.rhtml:

<h2>My shows</h2> <div id="content"> <%= shows_listing(@shows) %> <%= link_to('Add New Show', { :action => :new }) %> </div>

Making Beautiful Code

One of the great side effects of working with Ruby and within Rails, is that many good coding practices tend to rub off on you. For more on treating code as a craft, check out The Pragmatic Programmer by Dave Thomas and Andy Hunt. It’s a great book about building up your programming skills and talent to a new level of passion and artsmanship.

Due to the concise, terse and readable way that one can produce Ruby code, bad code often sticks out like a sore thumb. Your goal as a Rails artist is to track down and replace these sore spots of code. Here’s a few valuable tidbits that will help you in your noble quest.

Version Control

We’ve mentioned it before, but it deserves it’s own headline. Use version control for your source files, config files and whatever file-based information is often changing, necessary for deploying your application from scratch or just isn’t tied down properly. It’s probably better if it’s versioned than if it’s not and would you really want to find out the hard way?

The benefits of using version control can multiply quickly if you are working with other people. The ability to fall back on older code, simple storage of shared code and the ability to easily merge differing versions of code can’t be overlooked.

Tips for Working with Subversion:

Fix Broken Windows

Ever been in one of those super spotless houses, with the huge dining table and nice china sets? Were you sure to do your best to keep everything clean and in order while you were there? Probably, nobody wants to eat the last cookie and nobody wants to be the person who left the muddy footprint on the freshly mopped floor. Code can work like that too, keep it clean and lean and encourage your teammates to do so as well.

Tips for Keeping Code Clean:

Stay DRY (Don’t Repeat Yourself)

This bit comes up a lot in the Ruby world. The principle is simple, the less code there is, the easier it is to maintain. If you find code, the same or very similar code, in mulitple places then your best bet is to eliminate the repetition. By reducing the places repeated code lives, you make it much easier to maintain that code because instead of tracking down every instance of some code you only need look in one place. This philosopy can be expanded further to include configuration and metadata. Why keep the same metadata in three places? To make life easier, keep the metadata in one place and write scripts to generate what is needed elsewhere (eg. generating documentation from comments with RDoc).

Testing

Are you sure you’re code works? Would you like to be really sure, or better yet confident that it works? Then test your code, and test it whenever you change something or even before you change something. Use a testing suite. This will give you a nice system to work with and allow you to streamline your tests. Be brutal. Try to break your code. If you find a bug, then write a test to trigger it. If your fix works, the test will pass and you’ll have a way to see if that bug ever creeps back. When you get the zen of testing, try writing the test before you write the code. This way, you’ll know when the code works and you’ll already have the test. Anytime the code changes, you will have a system in place to ensure that nothing is broken.

Rails Best Practices

Much of this area is still being explored. Rails is so new and it’s rapidly changing. This is a great time to search, write and discuss what works best. This is a great time to become an important light in the community.

Exercises

Exercise-3-1
### Assertions found in the test/unit library
### originally found here http://manuals.rubyonrails.com/read/chapter/24
### modified with examples

require 'test/unit'

class BigTestOfAssertions < Test::Unit::TestCase
  # assert( boolean, [msg] )
  # ensures the object/expression is true

  def test_boolean
    assert( 1+1 == 2 ) # => true
  end

  # assert_equal( obj1, obj2, [msg] )
  # ensures obj1 == obj2 is true 

  def test_equal
    assert_equal( "hello", "HELLO".downcase ) # => true
  end
  
  # assert_same( obj1, obj2, [msg] ) 
  # ensures obj1.equal?(obj2) is true
  # assert_not_same( obj1, obj2, [msg] )
  # ensures obj1.equal?(obj2) is false

  def test_sameness
    a = "test"
    b = a
    c = "test"

    assert_same(a, b) # => true
    assert_not_same(a, c) # => true
  end

  # assert_nil( obj, [msg] )
  # ensures obj.nil? is true
  # assert_not_nil( obj, [msg] )
  # ensures obj.nil? is false

  def test_nilness
    var = "not nil"

    assert_nil(nil) # => true
    assert_not_nil(var) # => true
  end

  # assert_match( regexp, string, [msg] )
  # ensures a string matches the regular expression
  # assert_no_match( regexp, string, [msg] )
  # ensures a string doesn’t matches the regular expression

  def test_regexp
    assert_match(/fish/, "dog cat fish hamster") # => true
    assert_no_match(/baby/,"dog cat fish hamster") # => false
  end

  # assert_in_delta( expecting, actual, delta, [msg] )
  # ensures the numbers expecting and actual are within delta of each other

  def test_delta
    result = 7
    assert_in_delta(5, result, 3) # => true
  end

  # assert_raises( exceptions ){ block }
  # ensures a block raises one of the comma-separated exceptions
  # assert_nothing_raised( exceptions ){ block }
  # ensures a block doesn’t raise one of the comma-separated exceptions

  def test_exceptions
    assert_raises(Errno::ENOENT) { f = File.open('not-exists.txt') }  # => true
  end

  # assert_instance_of( class, obj, [msg] )
  # ensures obj is the class type
  # assert_kind_of( class, obj, [msg] )
  # ensures obj is or descends from class

  def test_classes
    assert_instance_of(Fixnum, 1) # => true
    assert_kind_of(Object, 1) # => true
    assert_kind_of(Numeric, 1) # => true
  end

  def test_methods
    # assert_respond_to( obj, symbol, [msg] )
    # ensures obj has a method called symbol

    assert_respond_to(1, :zero?) # => true

    # assert_operator( obj1, operator, obj2, [msg] )
    # ensures obj1.operator(obj2) is true

    assert_operator(1, :equal?, 1) # => true

    # assert_send( array, [msg] )
    # ensures that executing the method listed in array[1] on the object in array[0] 
    # with the parameters of array[ 2 and up ] is true.

    array = [0, :zero?]
    assert_send(array) # => true
    array = [1, :==, 1]
    assert_send(array) # => true
  end
  
  # flunk ( [msg] )
  # ensures failure
  def test_failure
    # flunk("This will always fail.  Kind of sad.")
  end
end

link download

Exercise-3-2
## Rails specific assertions
## originally found here http://manuals.rubyonrails.com/read/chapter/28
## modified for examples

## Rails Controller Response Assertions

# assert_template ( expected_template, [msg] )
# ensures the expected template was responsible for rendering.

assert_template("user/profile") # asserts that app/views/user/profile.rhtml was rendered

# assert_response ( type_or_code, [msg] )
# ensures the response type/status code is as expected
#   :success (status code is 200)
#   :redirect (status code is within 300..399)
#   :missing (status code is 404)
#   :error (status code is within 500..599)
#   :any number (to specifically reference a particular status code)

assert_response :success      # page rendered ok
assert_response :redirect     # we've been redirected
assert_response :missing      # not found
assert_response 505           # status code was 505

# assert_redirected_to ( options={}, [msg] )
# ensures we’ve been redirected to a specific place within our application

assert_redirected_to :controller => 'widget', :action => 'view', :id => 555

## Rails Tag Assertions

# assert_tag ( options )
# ensures that a tag or text exists.
# assert_no_tag ( options )
# ensures that the tag does not exist.

assert_tag :tag => "span" # assert that there is a "span" tag
assert_tag :tag => "span", :parent => { :tag => "div" } # "span" inside a "div" 
assert_tag :tag => "span", :ancestor => { :tag => "table" } # "span" somewhere inside a table
assert_tag :tag => "span", :child => { :tag => "em" } # "span" with at least one "em" child

# assert that there is a "span" containing a (possibly nested) "strong" tag.
assert_tag :tag => "span", :descendant => { :tag => "strong" }

# assert that there is a "span" containing between 2 and 4 "em" tags
# as immediate children
assert_tag :tag => "span",
           :children => { :count => 2..4, :only => { :tag => "em" } }

## Rails Routing Assertions

# assert_generates ( expected_path, options, defaults={}, extras = {}, [msg] )
# ensures that the options map to the expected_path

opts = {:controller => "movies", :action => "movie", :id => "60"}
assert_generates "movies/movie/60", opts

# assert_recognizes ( expected_options, path, extras={}, [msg] )
# ensures that when the path is chopped up into pieces, it is equal to 
#   expected_options. Essentially, the opposite of assert_generates.

opts = {:controller => "movies", :action => "movie", :id => "60"}
assert_recognizes opts, "/movies/movie/60"

# assert_routing ( path, options, defaults={}, extras={}, [msg] )
# ensures that the path resolves into options, and the options, resolves into path
# If you’re going to test your routes, this assertion might be your best bet.

opts = {:controller => "movies", :action => "movie", :id => "60"}
assert_routing "movies/movie/60", opts

link download

Homework (Due 2006-06-06)

Reading

Things to Try

Development Journal