Friday, 28 May 2010

Moving to Rails 2.3.8

I have recently decided to move to Rails version 2.3.8. I have tried rails 2.3 a while ago, but it did not agree with JRuby. As JRuby 1.5 is now available, I thought I should give it another go. Though I had the same problem, I fixed it this time.


application_controller.rb
The first change is that app/application.rb must be renamed to app/application_controller.rb, which certainly makes it more consistent.


JRuby and Rake
I think this was the issue that stopped my moving to 2.3.2. Running any rake command that accesses the database (and, for example, the test tasks invoke database tasks, so will do this as well) generates this:
rake aborted!
Task not supported by 'jdbcpostgresql'

The problem seems to have been know about two years ago (see here; more recently here). When Rake has to interact with the database, it checks the hash generated from your database.yml file, and finds the adapter is called jdbcpostgresql. It then compares this against the adapters it knows, trying - and failing - to match postgresql. The solution is to edit the rakes file that handles database interactions (rails-2.3.8\lib\tasks/databases.rake) and change every occurance of "postgresql" or 'postgresql' (with quotes) to /postgresql/. Now it will sucessfully match the jdbc adapter. Obviously if you are using another database, you need to change it for that.


Is test_helper loaded twice?
I have a couple of constants defined in test/test_helper.rb, and this causes warning that they have already been defined. I can only assume this is because the file is getting loaded twice, but I could not see why that might be the case.

Also in test/test_helper.rb, you need to change:
class Test::Unit::TestCase

... to:
class ActiveSupport::TestCase

Otherwise, you will get "undefined method `use_transactional_fixtures=' "


Deprecation warnings

"Giving :session_key to SessionStore is deprecated, please use :key instead."

In config/environment.rb, change :secret_key to :key. It will look something like this:
config.action_controller.session = {
:key => '_AuthSampleLog_session',
:secret => #some big hex number
}


"Using assert_redirected_to with partial hash arguments is deprecated"

Usually, your redirect will specify a controller and an action, but occasionally you specify something else as well, such as an id. Now in Rails assert_redirected_to checks each of the key-value pairs that you specify (this is confusing because the API says they are all optional; considered with redirect_to they are option; they just have to match up).

So this is fine, because the test specifies the controller and the action.
# In the controller
redirect_to :controller => :audits, :action => :edit
# In the controller test
assert_redirected_to :controller => :audits, :action => :edit

In this case, the test must also specify the id, because it is given in the redirect_to.
# In the controller
redirect_to :controller => :audits, :action => :edit, :id => @audit_section.audit_id
# In the controller test
assert_redirected_to :controller => :audits, :action => :edit, :id => as.audit_id


Integration testing
I found some odd things happened with logging out in my integration tests; eventually isolated this to a known bug:
https://rails.lighthouseapp.com/projects/8994/tickets/2200-session-support-broken


Struggling with Ruby: Contents Page

Sunday, 2 May 2010

Ruby Unit Testing

Ruby has a built-in unit testing facility, Test::Unit. To use it, create a new file, and define a class that inherits from Test::Unit::TestCase. This is best done in a separate folder, by the way, so you can easily release your project without the unit tests (Rails sets this all up for yu by the way).
# Load in the unit test classes
require 'test/unit'
# Load in your class to be tested
require 'things'

class ThingsTest < Test::Unit::TestCase

def setup
# Set up some test conditions here
# This will be invoked before each test
end

def teardown
# This will be invoked after each test
# Use to close connections, etc.
end
end

You will also need to define your tests. Each test goes in its own method, the name of which must begin "test" (as Ruby will search your class for such methods for testing).
def test_thing_creates_okay
# assertions
end

There are several assertions you can use to test the results. Here are some of the more common ones:
assert_equal      # Compares two objects
assert # Tests for true
assert_nil # Tests for nil
assert_raise # Tests an exception is thrown
assert_in_delta # Test two floats are within a given amount

Rails (since about 2.2) uses a different format for tests:
test "Thing creates okay" do
# assertions
end

The end result is the same. In Rails, test is a method that dynaally defines a method (in this case called "test_thing_creates_okay") with the given block.

Running Tests

If you are using NetBeans you can run a test simply by right clicking on the file being tested, and selecting test. NetBeans can work out which the relevant test is as long as it is named conventionally (eg the test for things.rb would be things_test.rb). Alternatively, you can right click on the test file, and selecting test or run.

To test from the command prompt, simply run the test file through ruby (eg ruby things_test.rb; if you are using JRuby, do jruby things_test.rb).

For a big project, you can collect all your tests together to form a test suite. Just create a Ruby file that requires each of the tests in turn, and run that through Ruby. Alternativerly, put this in your test suite, and it will automatically go though every file in that folder that has a name ending "_test.rb".
files = Dir.entries(File.dirname(__FILE__)).select do |file|
file =~ /_test.rb$/
end
files.each { |file| require file }


Testing private methods and variables

Here is a trivial class with an instance variable and a private method.
class UnitTestee
def initialize x
@val = x
end

private

def do_stuff y
@val * @val * y
end
end

I can test the private method using send, like this:
def test_do_stuff
@ut = UnitTestee.new 12
assert_equal 12 * 12 * 5, @ut.send(:do_stuff, 5)
end

Note that I have left Ruby to calculate 12 * 12 * 5; not only does this save me doing it, it makes it clearer where the number comes from (even better to use named constants, of course).

To access the instance variable, I can define a new method. Doing that in my test file means that the new method is not part of the API. Here is one way to do it (there are others):
def test_variable
@ut = UnitTestee.new 12
eval "def @ut.get_val; @val; end"
assert_equal 12, @ut.get_val
end



Note

Test files often include something like this at the start:
$:.unshift File.join(File.dirname(__FILE__),'..','lib')


This simply adds the code folder (called "lib" here) to the load path, so Ruby can find the file to be tested.


Struggling with Ruby: Contents Page

Saturday, 1 May 2010

Using partials as methods

Most of my models have an associated view that lists the records on the index page. Rails generates the code, so generally I do not worry about it, but if I am doing the same thing across a dozen models/controllers, surely it would be better to just do it once? Actually, I am not sure. You do not save any typing, as Rails generates the views (assuming you are in the habit of specifying all your columns from the start), and it is less readable this way. Anyway, let us look at how it can be done, and you can decide for yourself if it is worth while or not.

The way to achieve this is through a partial. Conceptually a partial is just a method that returns a chuck of HTML code You send it a few parameters mapped to the :local key, it processes your code, and returns the HTML. I want my partial accessible from any view, so I created a new folder, app/views/shared, with a partial called _table.rhtml.

I will be invoking my table from within a view with something like this:
<%=
render :partial => 'shared/table', :locals => {
:list => SamplesHelper::WORKSHEET_LIST,
:data => @worksheets,
:links => check_role?('analyst') ? :edit : :show,
}
%>

The render method is sent a hash with a :partial key that maps to the location of the file (without the underscore), and a :locals key with my parameters. This is the standard procedures for partils. Within locals I have chosen to require three parameters that will define how the table is drawn. The first, :data, is simply an array of ActiveRecord::Base objects, that is, the database records that Rails got for me.

The :links parameter determines whether the user sees links to show, to show and edit, or to show, edit and destroy. In this case, I am checking if the user has the "analyst" role; if he does, I want the edit and show links, otherwise just the show links.

Finally, the :list parameter is an array of hashes, which I chose to define in a helper file, and which might look like this:
WORKSHEET_LIST = [
{:heading => "Date", :column => "created_at.format_date"},
{:heading => "Type", :column => :name},
{:heading => "Sample", :column => :sample.number},
]

This will give me three columns, with the given headings, and the values from the given columns (or rather, method calls). Note that the first is a symbol, the others are strings. This will be explained later.

My partial looks like this:
<table align="center">
<tr>
<% list.each do |item| %>
<th><%= item[:heading] %></th>
<% end %>
<th colspan="<%= [:none, :show, :exit, :destroy].index(links) %>"> </th>
</tr>

<% for datum in data %>
<tr class="<%= cycle('odd', 'even') %>">
<% list.each do |item| %>
<td><%= item[:column].is_a?(Symbol) ? h(datum.send(item[:column])) : eval("datum.#{item[:column]}") %></td>
<% end %>
<td><%= link_to 'Show', { :action => :show, :id => datum.id } %></td>
<% unless links == :show %>
<td><%= link_to 'Edit', { :action => :edit, :id => datum.id } %></td>
<% end %>
<% if links == :destroy %>
<td><%= link_to 'Destroy', { :action => :destroy, :id => datum.id }, :confirm => 'Are you sure?', :method => :delete %></td>
<% end %>

</tr>
<% end %>
</table>

For the headings, it iterates through the list, pulling out the :heading value. Then it iterates over the records, and for each record again iterates through the list, this time pulling out the column value. If the :column value is a symbol, the send method is invoked, and the output HTML-escaped. If the :column value is a string, an eval is performed, allowing you to do something more involved (in the example, format a date). Note that this is not HTML-escaped; I have methods that return HTML strings to, for example, highlight values a certain colour, so this preserves that feature. However, you should consider carefully if this is safe in your situation. Note that I coud have defined a formated_created_at method in my model, and invoked that method using a symbol, rather than "created_at.format_date".

The table pads out the headings over the links at the right, and adds only those links that are requested.

As an aside, I was surprised to find that the parameters you send in the :locals hash really are local variables; I expected them to be method calls, like the supposed variables for columns in ActiveRecord.

I added a section at the top of the page that verifies the parameters are there. It just throws an exception if a required parameter is missing, and defines a default for the :links value should that one be missing.
<%
raise RuntimeError.new("list not set for _form") unless defined? list
raise RuntimeError.new("data not set for _form") unless defined? data

links = :show unless defined? links
%>

I think it is important to document your partial, so anyone using it knows what he has to supply in the way of parameters.
<%#
This partial must be sent:
- an array of hashes called "list"
- an array of ActiveRecords in "data"; the records from the database

It can also be sent:
- a value, "links" set to one of: :edit, :show, :destroy (defaults to :show)

The hashes in the array "list" must have a :heading key and a :column key.
The :heading key should map to a string, giving the column name.
The :column key should map to a symbol or string.
If a symbol, then that will be used the send method on the record, and the
output with be HTML-escaped; use this for model column names.
If a string is supplied, it will be used in an eval
method call, and the output will not be HTML-escaped.
%>

The last thing to do is to test your partial. This needs to be done as a functional test, because you need the infrastructure that that implies. This means we need a new action, let us call it _render, which can be defined in the ApplicationController class (but in the test_helper.rb file, so it only exists in your tests). The action firstly executes a string, params[:eval], which would set up any instance variables required for your test. The page is then rendered, using the partial as defined in params[:args].
class ApplicationController
def _render
eval(params[:eval]) unless params[:eval].nil?
render :inline => "<%= render #{params[:args]} %>"
end
end

You should create a new file with your other functional tests. You need to tell Rails which controller it will use (should be okay to use any of them), using the tests method. You may need to load in some records (or you can use fixtures), and you may need to log in.

The meat of the test is the get command, invoking the _render action defined before, with two arguments, the parameters for the partial, and the code for grabbing some ActiveRecords to show (both as strings, please note).
class PartialTest < ActionController::TestCase
tests ChembaseRefsController

def test_table
load_sample_records
login_as 'librarian', @request


get :_render, :args => ":partial => 'shared/table', :locals => {
:list => ChembaseRefsHelper::REFS_LIST,
:data => @chembase_refs,
:links => :destroy,
}", :eval => "@chembase_refs = ChembaseRef.find :all"

doc = REXML::Document.new @response.body
assert_equal ChembaseRef.count(:all) + 1, doc.elements.to_a("table/tr").length#, "#{@response.body}\n"
assert_equal ChembaseRefsHelper::REFS_LIST.length + 1, doc.elements.to_a("table/tr/th").length#, "#{@response.body}\n"
end

The method ends by creating an XML document from the respoonse, and testing the number of columns and rows are what they should be.


Struggling with Ruby: Contents Page