Archive for September, 2007

Rails Inline Fixtures

Thursday, September 13th, 2007

Rails Fixures provide a way of organizing sample data to be used in tests. Traditionally, they reside under the test/fixtures directory of a Rails application.

One of the nice things about fixtures is the declarative nature of their authoring and the simple syntax of their platform (YAML). They are clear, easy to read, short and concise.

One implication that results from using fixtures in accordance to the above strategy is that the data under test is not directly visible in the test itself, i.e. one needs to navigate to a different file to verify parts of the test’s setup.

At the same time, the more these fixtures start being shared by more and more tests, the more difficult it becomes to keep them organized in a way that makes one fixture’s data directly appropriate for each of the tests that are using it. For example, a fixture, songs.yml, is being used by a unit test that requires two song entries, whereas the acceptance test for the songs page’s pagination logic requires 25 songs to be created. The fixtures :songs directive in the song unit test will happily create 25 fixtures each time, 23 of which are never used.

It might be useful to be able to declare fixtures inline on a per test or test case basis, while maintaining the effective, elegant syntax. The code bellow is an attempt to this direction.

[ruby]
module InlineFixtures
def delete_fixtures(table_name)
ActiveRecord::Base.connection.delete “DELETE FROM #{table_name}”, ‘Fixture Delete’
end

def load_inline_fixtures(table_name_sym, inline_fixture)
table_name = table_name_sym.to_s
delete_fixtures table_name
yaml_fixture = YAML.load inline_fixture
yaml_fixture.values.map do |f|
fixture = Fixture.new f, table_name
ActiveRecord::Base.connection.execute “INSERT INTO `#{table_name}` (#{fixture.key_list}) VALUES (#{fixture.value_list})”, ‘Fixture Insert’
end
end

alias :___ :load_inline_fixtures
end
[/ruby]

Including the InlineFixtures module allows for code that can be used anywhere inside a ruby script and looks like this:

[ruby]
class SongTest < Test::Unit::TestCase
include InlineFixtures

def test_can_count
___ :songs, %(
one:
title: orion
band: metallica
two:
title: legs
band: zztop
)
assert_equal 2, Song.count
end
end
[/ruby]

This technique has proven to be particularly useful in our Selenium On Rails tests (written in rselenese), as these tests commonly require the most elaborate database setup.

Rails View Adapter

Friday, September 7th, 2007

Closely related to the Presenter Pattern, we have been recently applying a similar approach in order to achieve thinner Controllers responding with name/value pair based data, in order to achieve Views which are decoupled from the rest of our application’s layers.

The main ViewAdapter module looks something like this:

require "ostruct"

module ViewAdapter
  module ClassMethods
    def prepare(records)
      records.map do |record|
        view_data = OpenStruct.new
        yield view_data, record
        view_data
      end
    end
  end

  extend ClassMethods

  def self.included(receiver)
    receiver.extend(ClassMethods)
  end
end

Each View Adapter includes the ViewAdapter module which allows for a clean, easily testable, declarative setup of the View Data that will be eventually rendered on the screen.

class ConcertViewAdapter
  include ViewAdapter

   def concerts
     prepare Concert.find(:all) do |view_data, concert|
       view_data.artist = concert.artist
       view_data.venue = concert.venue
       view_data.price = concert.currency + concert.price
     end
   end
end

As a general rule, we maintain a one-to-one relationship between the Controllers and the corresponding View Adapters.

class ConcertsController < ApplicationController
  def index
    @concerts = ConcertViewAdapter.concerts
  end
end

These Adapters are mainly used to consolidate and format data, as returned by a Controller’s action, for display.

Advantages/Trade-offs

Distinct decoupling of the View from the rest of the application layers which provides a single, well known point of maintenance to accommodate easy code changes or refactorings.

Established codebase location for view logic and formatting operations. Particularly useful for internationalization.

Easily testable. Tests for the View Adapters can run as part of a Fast Rails Test Suite because they don’t require any Rails environment setup.

As a possible downside, this approach will break some of the standard Rails view helpers which depend on ActiveRecord objects being exposed in ERB templates. I personally do not consider this a big issue, because I don’t find much benefit in most of these HTML helpers, especially considering the way they cross MVC boundaries by exposing Domain Objects to the View, or mask relatively straightforward mark up.