Thursday, March 29, 2007
Thoughtworks is hiring ruby developers
Thoughtworks is looking for a Ruby Specialist and Ruby Developers. In fact, we're pretty much hiring for all positions at the moment (Java, .NET and and Buisness Analysts). Send me an email at sharvie@thoughtworks.com if you have any questions, and mention my blog if you decide to apply.
Saturday, March 24, 2007
modifying constants in a class to aid testing
We needed a way to test pagination and sorting of lists across different pages in Rails controllers without having to create enough records to cross to the second page. So we created a method to alter the value of a constant in a class that we are testing.
It enables us to test pagination and sorting (amongst other things) like this:
with_constants takes a class name and a hash of constant => value pairs, and yields to a block which is the code you want to call with the altered constants:
It enables us to test pagination and sorting (amongst other things) like this:
class CarsController...
PER_PAGE = 20
def index
@pages, @cars = paginate :cars, :per_page => PER_PAGE, :order => "make, model"
end
class CarsControllerTest < Test::Unit::TestCase...
def test_index_sorts_by_make_and_then_model_for_page_1
car1 = Car.create!(standard_params.merge(:make => "Fiat", :model => "Uno"))
car2 = Car.create!(standard_params.merge(:make => "Fiat", :model => "Bravo"))
car3 = Car.create!(standard_params.merge(:make => "Citroen", :model => "C2"))
with_constants CarsController, :PER_PAGE => 2 do
get :index
end
assert_equal [car3, car2], assigns(:assets)
end
def test_index_sorts_by_make_and_then_model_for_page_2
car1 = Car.create!(standard_params.merge(:make => "Fiat", :model => "Uno"))
car2 = Car.create!(standard_params.merge(:make => "Fiat", :model => "Bravo"))
car3 = Car.create!(standard_params.merge(:make => "Citroen", :model => "C2"))
with_constants CarsController, :PER_PAGE => 2 do
get :index, :page => 2
end
assert_equal_arrays [car1], assigns(:assets)
end
with_constants takes a class name and a hash of constant => value pairs, and yields to a block which is the code you want to call with the altered constants:
def with_constants(klass, constants_hash)
begin
old_constants = {}
constants_hash.each_pair do |name, value|
old_constants[name] = klass.send(:remove_const, name)
klass.const_set(name, value)
end
yield
ensure
constants_hash.each_pair do |name, value|
klass.send(:remove_const, name)
klass.const_set(name, old_constants[name])
end
end
end
Thursday, March 22, 2007
wrapping stubba in stub_object method
The stub method in stubba gives me a warning when you stub :id ("Object#id will be deprecated; use Object#object_id"), which we seem to do fairly often with ActiveRecord objects. So we use a simple method called stub_object (which we wrote originally because we didn't know "stub" existed):
It gives us syntax like this:
And our manufacturer object will respond to any calls to :id, :name, or :telephone_number, without the warning for :id.
def stub_object(params)
object = Object.new
params.each do |attr, value|
object.stubs(attr).returns(value)
end
object
end
It gives us syntax like this:
manufacturer = stub_object(:id => 5, :name => "Ford", :telephone_number => "555-888-999")
Car.any_instance.expects(:manufacturer).returns(manufacturer)
And our manufacturer object will respond to any calls to :id, :name, or :telephone_number, without the warning for :id.
Saturday, March 17, 2007
cruisecontrol.rb released
Thoughtworks has released cruisecontrol.rb 1.0. You can download it here. I know of a lot of rails projects that were rolling their own continuous integration, which lead Thoughtworks to put some resources into developing cruisecontrol.rb. It takes about 10 minutes to set up, following rails' goal of convention over configuration.
Monday, March 5, 2007
table_for erb template
Background:
Rails provides a form_for method which can be used in a view to generate an html form. When this is used, there is no need to specify the <form> tags and, when combined with the convenience methods such as text_field, select and radio_button, very little html needs to be handwritten.
At my current project we create a lot of CRUD interfaces. We found that we were often creating simple tables displaying attribute name-value pairs for a given object in the 'Read' view. Inspired by the Rails Recipes 'tabular_form_for' template, my coding pair and I decided to create a table_for template.
For example:
Let's say an Aeroplane object has two attributes:
An rhtml view for displaying the Aeroplane object might look like this:
We want to remove as much of that html code as possible. A syntax like this would be nice:
We put the 'table_for' method in application_helper.rb, so that it is available to all templates. It creates a TableForContext object to perform all the work for us, yields this object back to the caller, and wraps any output generated in 'table' tags.
The 'print' method in TableForContext is straight-forward:
We want the links to be side-by-side in a separate row at the bottom. You'll notice in the view that the result of the 'print' method is output directly (it is wrapped in <%= %> tags), whereas the 'link_to' method is not (it is wrapped in <% %> tags). The links are concatenated together to be output at the end:
All that is left is some customization options. The ones we have required at so far are:
- custom wrapping of the attribute value in other html (eg making the value link to another page).
- custom headers (ie not just the attribute name humanized)
- custom css classes on the table headers
Let's say that an aeroplane has an associated Manufacturer object. We add a manufacturer_id attribute to aeroplane (and associated belongs_to and has_many declarations).
Let's say that the associated Manufacturer object has two attributes:
And that in the Aeroplane view we want to display the aeroplane's manufacturer name as a link to the manufacturer 'Show' view. Let's also say that we want a custom label on our Aeroplane 'name' attribute, and a class called "header" on all of our table headers.
With some changes to our 'table_for' and 'print' methods we should be able to accommodate the following syntax:
The 'table_for' method now looks like this:
The constructor for TableForContext becomes:
And the 'print' method now looks like:
We have added a private method 'th' which adds the header class, if specified:
Rails provides a form_for method which can be used in a view to generate an html form. When this is used, there is no need to specify the <form> tags and, when combined with the convenience methods such as text_field, select and radio_button, very little html needs to be handwritten.
At my current project we create a lot of CRUD interfaces. We found that we were often creating simple tables displaying attribute name-value pairs for a given object in the 'Read' view. Inspired by the Rails Recipes 'tabular_form_for' template, my coding pair and I decided to create a table_for template.
For example:
Let's say an Aeroplane object has two attributes:
:name, :string
:model_number, :string
An rhtml view for displaying the Aeroplane object might look like this:
<table>
<tr>
<th>Aeroplane Name</th> <td><%=h @aeroplane.name %></td>
</tr>
<tr>
<th>Model Number</th> <td><%=h @aeroplane.model_number %></td>
</tr>
</table>
<%= link_to 'Edit', :action => 'edit', :id => @aeroplane %> |
<%= link_to 'Back', :action => 'list' %>
We want to remove as much of that html code as possible. A syntax like this would be nice:
<% table_for @aeroplane do |t| %>
<%= t.print :name %>
<%= t.print :model_number %>
<% t.link_to 'Edit', url_for(:action => 'edit', :id => @aeroplane) %>
<% t.link_to 'Back', url_for(:action => 'list') %>
<% end %>
We put the 'table_for' method in application_helper.rb, so that it is available to all templates. It creates a TableForContext object to perform all the work for us, yields this object back to the caller, and wraps any output generated in 'table' tags.
def table_for(object, &block)
concat('<table>', block.binding)
context = TableForContext.new(object, block.binding)
yield context
concat(context.links + '</table>', block.binding)
end
The 'print' method in TableForContext is straight-forward:
def print(field)
"<tr>" + "<th>" + field.to_s.humanize + "</th>" + "<td>#{@object.send(field)}</td></tr>"
end
We want the links to be side-by-side in a separate row at the bottom. You'll notice in the view that the result of the 'print' method is output directly (it is wrapped in <%= %> tags), whereas the 'link_to' method is not (it is wrapped in <% %> tags). The links are concatenated together to be output at the end:
def link_to(link_name, url)
@links << ' ' unless @links.empty?
@links << class =""> 'button')", @view_binding)
end
All that is left is some customization options. The ones we have required at so far are:
- custom wrapping of the attribute value in other html (eg making the value link to another page).
- custom headers (ie not just the attribute name humanized)
- custom css classes on the table headers
Let's say that an aeroplane has an associated Manufacturer object. We add a manufacturer_id attribute to aeroplane (and associated belongs_to and has_many declarations).
Let's say that the associated Manufacturer object has two attributes:
:name, :string
:address, :string
And that in the Aeroplane view we want to display the aeroplane's manufacturer name as a link to the manufacturer 'Show' view. Let's also say that we want a custom label on our Aeroplane 'name' attribute, and a class called "header" on all of our table headers.
With some changes to our 'table_for' and 'print' methods we should be able to accommodate the following syntax:
<% table_for @aeroplane, :header_class => "header" do |t| %>
<%= t.print :name, :label => 'Super Happy Aeroplane Name:' %>
<%= t.print :manufacturer do |manufacturer, aeroplane|
link_to manufacturer.name, manufacturer_url(:id => @aeroplane.manufacturer_id)
end %>
<%= t.print :model_number %>
<% t.link_to 'Edit', edit_aeroplane_url(:id => @aeroplane) %>
<% t.link_to 'Back', aeroplanes_url %>
<% end %>
The 'table_for' method now looks like this:
def table_for(object, options={}, &block)
concat('<table>', block.binding)
context = TableForContext.new(object, block.binding, options)
yield context
concat(context.links + '</table>', block.binding)
end
The constructor for TableForContext becomes:
def initialize(object, view_binding, options={})
@object = object
@view_binding = view_binding
@links = ''
@options = options
end
And the 'print' method now looks like:
def print(field, options={})
label = options[:label].nil? ? field.to_s.humanize : options[:label]
if block_given?
content = yield @object.send(field), @object
else
content = @object.send(field)
end
"<tr>" + th(label) + "<td>#{content}</td></tr>"
end
We have added a private method 'th' which adds the header class, if specified:
def th(content)
html = '<th'
if @options[:header_class]
html << " class=\"#{@options[:header_class]}\""
end
html << ">#{content}</th>"
end
Subscribe to:
Posts (Atom)
