Posted by Joseph Nusairat
Wed, 04 Mar 2009 01:45:00 GMT
As one of the co-authors of Beginning Groovy and Grails and author of the securities chapter in that book I have been using Grails security for quite some time. One of the security plugins I have looked at with the most interest is the JSecurity plugin. I was attracted to the JSecurity because it seemed to provide a good mix of out of the box support but also being flexible enough for customizations.
On the surface JSecurity provides much of the normal security you'd expect from any framework. There is out of the box support for login/logout authentication, support for role based lock downs, and finally even the ability to create permission based security. It's the last one I wanted to write about.
Understanding how to do normal authentication with JSecurity and implementing roles is pretty straight forward and the documentation on the grails wiki ( http://www.grails.org/JSecurity+Plugin+-+Quick+Start ) shows how to use it. Where i believe the documentation dies a bit on is using permissions. That being said, the documentation on the wiki is very good at understanding WHAT permissions are and creating custom ones but not necessarily how to create basic ones in the first place.
So I am going to assume you have all used JSecurity or can go to the previous URL and read about it and will discuss implementing permissions for JSecurity. Sometimes knowing when to use permissions can be rather complex. And sometimes justifiably so, the way to use permissions (and of course there are many ways to use them) is when you keep needing to fine tune access to a particular source on a per user basis. Take a typical web application with a user list. We may want to allow someone to have read access to the list, another to have read and write, and another to have read, write, delete. To perform this check with roles we would basically have to have 3 different roles and assign them when needed. The problem with this solution is it basically gets messy fast especially if you have quite a bit of resources you want to lock down in this way. Permissions are designed to solve this problem exactly, by allowing a finer grain access on a resource. Let's dive into how to create permissions in JSecurity.
So how do we perform this in Grails with JSecurity? JSecurity essentially has three tables that handle permissioning: JsecPermission, JsecUSerPermissionRel, and JsecUserRoleRel. We will look at how these tables are going to help us create and lock down pages on a per user basis.
Let's first discuss how JSecurity interprets permisssions to begin with. JSecurity permission is more complex than some because it does not force one to treat every resource (ie: web page, file system, specific part of the app) equally. JSecurity allows you to define what the permission resource IS in the first place. For example the permission logic for a web URL like our in example of a user page, to-do page, etc would differ from a file resource if you were creating a documentation management site. The wiki for the JSecurity shows examples of a FilePermission or a ProjectPermission, anything that implements the Permission interface. Each of these classes will have an implies() methods that actually determines HOW one is to interpret the permission. This is great because it gives the user the maximum flexibilty in determining how they want there permission applied. Now while i say that is great, let's face it most of us are going to use permissions for as I described above in Grails, to lock down a particular controller / action. The good news is out of the box Grails JSecurity plugin provides a specialized permission for controller based security. They have what is called a basic permission 'org.jsecurity.grails.JsecBasicPermission'. This permission is good for when you want to apply the concept of controller / action security. And it is this particular use case I am going to show how to use below.
To start with even though we have the permission as a resource on the class path its name needs to be stored to the database so the system can do reflection on it later. The first table we need to add to is the JsecPermission table. This table is simply going to keep a list of all the permission classes we have and the possible actions for each. So for our controller / action lockdowns we will simply add a reference to the JsecBasicPermission into the database:
Creating the Database Permission
def perm = new JsecPermission(type: 'org.jsecurity.grails.JsecBasicPermission', possibleActions: '*').save()
Now as you have noticed i have used an * for the possible actions, this is more to fill in the blanks than for anything useful. By default with the JsecBasePermission it really does not care about the actions used here. Now just because this particular permission is not using possible actions does not mean that another permission in the future could use it nor does it mean it HAS to use it either. This is part of JSecurity's methodology of trying to keep maximum flexibility.
Assigning the User to the Permissions
Now that we created the permission let's assign it to a user you have already created. To assign it you basically have two choices. Either assign it to a role or assign the permission directly to the user. I am going to do the latter and assign it to the user. In the following code we are taking our user and our permission and allowing the actions of delete and edit.
new JsecUserPermissionRel(user : user, permission: perm, target : 'user', actions : 'delete, edit').save()
Locking Down the Resource
So now that we have the permission we are going to lock down the security for the controller, and here is where it may get a bit tricky to understand. First off remember we have not done any permissioning for create or list, so by default we do not want to do any permissioning check for those because we have not assigned any. So in our filter lets add two permissioning interceptors for delete and edit only:
user(controller: "register", action: "delete") {
before = {
accessControl {
permission(type: 'user',
actions: actionName)
}
}
}
user(controller: "register", action: "edit") {
before = {
accessControl {
permission(type: 'user',
actions: actionName)
}
}
}
Now the application will check for permissions with edit and delete only, and unless you are assigned will take you to a page saying that you aren't allowed.
Now where I said it can get confusing is the naming as you go through, and this is where we harp back to saying that the permissions in JSecurity is very much user based. While this provides a huge degree of flexibility it can also cause naming you thought should go together not always go together. So take where we initially defined type in the JsecPermission table. This type was our fully qualified class type. However, the type down in the filter we define has nothing to do with the JsecPermission type. This type is instead going to relate to the target we defined in JsecUserPermissionRel. This is an important distinction to make, and one that confused me at first when using the JsecBasicPermission. The actions part, that is more self explanatory and the actions you pass in will be the actions that it will check against.
Posted in Groovy, Grails | Tags Grails | no comments | no trackbacks
Posted by Brian Sam-Bodden
Tue, 15 Jan 2008 05:00:00 GMT

In this installment we are going to build the Dashboard page of the Tempo application. The first incarnation of the Dashboard page will serve as the entry point/main page of the application and its main function will be to provide an interface for users to report time against a project-activity combination.
The user story that we are targeting is:
TMPO-29: “User enters Time” Business Critical [OPEN] 40 points 0% unassigned
The description above is from the Agile Project Management and Collaboration tool Caimito One. The “User enters Time” story is business critical, it is still OPEN, we have estimated 40 complexity points to it, is 0% completed and it has not been assigned to any team member (or no team member has volunteered for it!)
The description of the user story is "As a User I would like to enter time against a project and activity by providing a starting and ending time"
User Interface
Based on the story description I’ve come up with a rough sketch of what I picture the Dashboard page looking like after the first pass:

The sketch above shows 3 different areas:
- Date Selection: A Calendar-style component that will enable the user to select a given (active) date to enter time
- Time Entry Details: A form that will provide drop-downs for the Project, Activity, Start Time, End Time for the TimeEntry as well as a text area for a description. Optionally the user will be allowed to enter time as a number of hours to be reported against the given date (with no specific start or end times(1)).
- Daily Summary: A table displaying the time entries reported for the active date.
(1) Notice that I’ve just created a new requirement that was not present in the original User Story.
In our work to fulfill the main User Story we will set the stage to satisfy this new requirement but we should really create a new issue (story) to tackle that new piece of functionality (of course first we should check with the project owner and/or stakeholders)
Dashboard Controller
I started by creating the skeleton code and tests for the Dashboard Controller:
/> script/generate rspec_controller dashboard
exists app/controllers/
exists app/helpers/
create app/views/dashboard
exists spec/controllers/
exists spec/helpers/
create spec/views/dashboard
create spec/controllers/dashboard_controller_spec.rb
create spec/helpers/dashboard_helper_spec.rb
create app/controllers/dashboard_controller.rb
create app/helpers/dashboard_helper.rb
Since we want the dashboard to be the default page for our application we can modify the config/routes.rb accordingly:
map.connect '', :controller => "dashboard", :action => "index"
With the skeleton in place I can now do a little planning about what I need to build.
A bit of Design
Here's what I know about the Dashboard page so far:
- The Dashboard page can only be shown if there is a user logged in. (done, taken care by the restful authentication plug-in)
- When the user first navigates to the Dashboard page, the current date should be shown in the calendar date picker and on the “blog” style display on the time entry form
- The Daily Work table should show all TimeEntries for the current user for the selected date
- When the user selects a new date form the calendar date picker the "current date" (the date which under the times entered will be reported) should change.
Create the Views
Let’s start by creating an index.rhtml template in the Views/dashboard directory. The index template will render three partials; one for a blog-style date, the TimeEntry form and one for the Daily Work table. The partials will be _current_date.rhtml, _form.rhtml and _time_entries.rhtml respectively.
views/dashboard/index.rhtml
The listing below shows the contents for index.rhtml. I used some of the CSS styles provided by ActiveScaffold to keep the look and feel consistent.
<div class="active-scaffold">
<div class="create-view view">
<% form_tag :action => 'create' do %>
<h4><div id="current_date_div"><%= render :partial => "current_date" %></div></h4>
<ol class="form" >
<%= render :partial => 'form' %>
<p class="form-footer">
<%= submit_tag "Create" %>
</p>
</ol>
<% end %>
</div>
</div>
<br />
<p>
<div id="time_entries_div">
<%= render :partial => "time_entries" %>
</div>
</p>
The index.rhtml view renders a form (which body is contained in the _form.rhtml partial), the current date which is render by the partial _current_date.rhtml. Finally at the bottom we have a section that contains the daily work table summary which is rendered by the _time_entries.rhtml partial. Let's take a look at each one of those partials next:
views/dashboard/_current_date.rhtml
The idea here is to render a blog style data block as shown below:

To render the blog style date I have a CSS snippet that I use to style a partial containing the current date. The partial _current_date.rhtml is shown below:
<div class="dateblock">
<div class="dateblock_month">
<%= "#{Time::RFC2822_MONTH_NAME[@current_date.month-1]}" %>
</div>
<div class="dateblock_day">
<%= "#{@current_date.day}" %>
</div>
<div class="dateblock_year">
<%= "#{@current_date.year}" %>
</div>
</div>
Notice that I use an instance variable current_date that will be set in the index method of the dashboard controller if it cannot be retrieved from the session:
def index
unless session[:current_date]
session[:current_date] = Time.today
end
@current_date = session[:current_date]
end
In the controller the method to render the partial and update the instance and the session variable follows:
def current_date
session[:current_date] = Time.parse(params[:current_date])
@current_date = session[:current_date]
render :partial => 'current_date'
end
The CSS code for the dateblock is shown below (I've found it on a CSS site but I can't remember where):
/*-------------------------------------------------
DATE BLOCK
-------------------------------------------------*/
.dateblock {
text-align: center;
width: 50px;
font-family: sans-serif;
border: solid 1px black;
padding-top: 5px;
color: white;
background-color:black;
margin-top: 10px;
margin-bottom: 10px;
line-height: 1.22em;
font-family: sans-serif;
}
.dateblock_month {
font-size: 12px;
}
.dateblock_day {
font-size: 26px;
position: relative;
top: -2px;
}
.dateblock_year {
font-size: 12px;
position: relative;
top: -2px;
}
views/_time_entries.rhtml
For the Daily Work table showing all TimeEntries for the current user for the selected date we use the _time_entries.rhtml partial. This partial makes use of ActiveScaffold's ability to embed a scaffold in another controller, or view. To accomplish this we use the
render :activescaffold => 'timeentries'
which will call the render_component passing a specific set of conditions (to be appended to the SQL select statement) and a custom label for the scaffold table:
<%= render :active_scaffold => 'time_entries',
:conditions =>
["user_id = ? AND YEAR(start) = ? AND MONTH(start) = ? AND DAY(start) = ?",
@current_user.id, @current_date.year, @current_date.month, @current_date.mday],
:label =>
"Time Entries for #{Time::RFC2822_DAY_NAME[@current_date.wday]}, #{Time::RFC2822_MONTH_NAME[@current_date.month-1]} #{@current_date.day} #{@current_date.year}" %>
In the controller we need to modify the index method to also filter the collection of TimeEntry objects retrieved:
def index
unless session[:current_date]
session[:current_date] = Time.today
end
@current_date = session[:current_date]
@time_entries = TimeEntry.find(:all, :conditions => ["user_id = ? AND YEAR(start) = ? AND MONTH(start) = ? AND DAY(start) = ?", @current_user.id, @current_date.year, @current_date.month, @current_date.mday])
end
We also need a time_entries method to render the TimeEntry(s) based on the current_date in the session:
def time_entries
session[:current_date] = Time.parse(params[:current_date])
@current_date = session[:current_date]
render :partial => 'time_entries'
end
The daily work table should now display all the TimeEntry(s) for the current/selected date:

views/_form.rhtml
The form partial is fairly simple and provides elements to select the project, the activity, time picker for the start and end dates, an optional total hours worked field and a text area to enter comments associated with the work being reported.
The complete source for the partial is shown below:
<%= error_messages_for 'time_entry' %>
<!--[form:time_entry]-->
<li class="form-element">
<dl>
<dt><label for="time_entry_project">Project</label></dt>
<dd><%= collection_select 'time_entry', 'project', current_user.projects, 'id', 'name' %></dd>
<%= observe_field 'time_entry_project', :update => 'time_entry_activity', :with => "project_id", :url => { :controller => "dashboard", :action => "load_activities" } %>
</dl>
</li>
<li class="form-element">
<dl>
<dt><label for="time_entry_projects_activity">Activity</label></dt>
<dd><%= select 'time_entry', 'projects_activity', ['--select a project--'] %></dd>
</dl>
</li>
<li class="form-element">
<dl>
<dt><label for="time_entry_start">Start</label></dt>
<dd><%= time_picker Time.now, {:time_format => '12', :minute_step => 30, :prefix => 'time_entry', :field_name => 'start', :time_separator => ':'} %></dd>
</dl>
</li>
<li class="form-element">
<dl>
<dt><label for="time_entry_end">End</label></dt>
<dd><%= time_picker Time.now, {:time_format => '12', :minute_step => 30, :prefix => 'time_entry', :field_name => 'end', :time_separator => ':'} %></dd>
</dl>
</li>
<li class="form-element">
<dl>
<dt><label for="time_entry_hours">Hours</label></dt>
<dd><%= text_field 'time_entry', 'hours' %></dd>
</dl>
</li>
<li class="form-element">
<dl>
<dt><label for="time_entry_comment">Long Description</label></dt>
<dd><%= text_area 'time_entry', 'comment', 'rows' => 12, 'cols' => 45 %></dd>
</dl>
</li>
<!--[eoform:time_entry]-->
A little Ajax
Let's dissect the form contents. First at the very top we have a form element that displays a drop-down for the projects that the current user is part of. Since we have specific activities per project we need to use some Ajax magic to populate the project activities drop-down.
<li class="form-element">
<dl>
<dt><label for="time_entry_project">Project</label></dt>
<dd><%= collection_select 'time_entry', 'project', current_user.projects, 'id', 'name' %></dd>
<%= observe_field 'time_entry_project', :update => 'time_entry_activity', :with => "project_id", :url => { :controller => "dashboard", :action => "load_activities" } %>
</dl>
</li>
<li class="form-element">
<dl>
<dt><label for="time_entry_projects_activity">Activity</label></dt>
<dd><%= select 'time_entry', 'projects_activity', ['--select a project--'] %></dd>
</dl>
</li>
To accomplish this I use the observe_field tag, Rails lets you listen to events on a field such as the value of a field to which you can respond by making an Ajax call to an action handler with the current value of the field being observed sent to the action handler in the post data of the call. In the case above we are updating the drop-down time_entry by invoking the action load_activities in the controller, which is shown below:
def load_activities
project = Project.find(params[:project_id])
@projects_activities = project.projects_activity
end
Time Picking
To enable time picking on the form fields for start and end times I used Thong Kuah TimePicker plug-in which can be found at http://rubyforge.org/projects/timepicker/. As described in the read me file, this plug-in follow the usage of ActionView::Helpers::DateHelper closely.
Below is a preview of what the time picking drop-downs will look like:

To install the plug-in I again use Piston:
/> piston import svn://rubyforge.org/var/svn/timepicker/trunk vendor/plugins/timepicker
Exported r4 from 'svn://rubyforge.org/var/svn/timepicker/trunk vendor/plugins/timepicker' to 'vendor/plugins/timepicker'
With the plug-in installed you can now use time_picker(datetime, options = {}) in your views.
In our case the usage is shown below:
<li class="form-element">
<dl>
<dt><label for="time_entry_start">Start</label></dt>
<dd><%= time_picker Time.now, {:time_format => '12', :minute_step => 30, :prefix => 'time_entry', :field_name => 'start', :time_separator => ':'} %></dd>
</dl>
</li>
<li class="form-element">
<dl>
<dt><label for="time_entry_end">End</label></dt>
<dd><%= time_picker Time.now, {:time_format => '12', :minute_step => 30, :prefix => 'time_entry', :field_name => 'end', :time_separator => ':'} %></dd>
</dl>
</li>
Calendar Picker
For the current date selection I'm using a set of plug-ins that work well with ActiveScaffold. Calendar Date Select can be found at http://code.google.com/p/calendardateselect/. There is also a bridge to integrate it into ActiveScaffold at http://wiki.activescaffold.com/wiki/published/CalendarDateSelectBridge:
/> piston import http://calendardateselect.googlecode.com/svn/tags/calendar_date_select vendor/plugins/calendar_date_select
Exported r154 from 'http://calendardateselect.googlecode.com/svn/tags/calendar_date_select' to 'vendor/plugins/calendar_date_select'
/> piston import http://activescaffold.googlecode.com/svn/bridges/active_scaffold_calendar_date_select_bridge vendor/plugins/active_scaffold_calendar_date_select_bridge
Exported r634 from 'http://activescaffold.googlecode.com/svn/bridges/active_scaffold_calendar_date_select_bridge' to 'vendor/plugins/active_scaffold_calendar_date_select_bridge'
/>
Once installed, I placed the code for the date picker in the main application layout (application.rhtml). The snippet below shows only when the controller is the dashboard controller and the action is the index action:
<!-- ============ -->
<!-- Right Column -->
<!-- ============ -->
<div class="Right">
<div class="col">
<% if current_page?({'controller' => 'dashboard', 'action' => 'index'}) -%>
<h1>Select a Date</h1>
<%= calendar_date_select_tag
:current_date,
@current_date,
{:embedded => "true",
:onchange => "new Ajax.Updater('current_date_div', '/dashboard/current_date?current_date=' + $H({current_date: this.value}).toQueryString(),
{asynchronous:true, evalScripts:true});
new Ajax.Updater('time_entries_div', '/dashboard/time_entries?current_date=' + $H({current_date: this.value}).toQueryString(),
{asynchronous:true, evalScripts:true})"}
%>
<% else -%>
<h1>[[Help Title]]</h1>
<p>[[Help Body]]</p>
<% end -%>
</div>
</div>
Finally we need a create method in the controller to handle the creation of the new time entry when we submit the form:
def create
project = Project.find(params[:time_entry][:project])
projects_activity = ProjectsActivity.find(params[:time_entry][:projects_activity])
@time_entry = TimeEntry.new
@time_entry.user = current_user
@time_entry.project = project
@time_entry.projects_activity = projects_activity
start_time = Time.parse(params[:time_entry][:start])
end_time = Time.parse(params[:time_entry][:end])
@time_entry.hours = params[:time_entry][:hours]
unless session[:current_date]
session[:current_date] = Time.today
end
@current_date = session[:current_date]
@time_entry.start = Time.utc(@current_date.year, @current_date.month, @current_date.mday, start_time.hour, start_time.min)
@time_entry.end = Time.utc(@current_date.year, @current_date.month, @current_date.mday, end_time.hour, end_time.min)
if @time_entry.save
flash[:notice] = 'TimeEntry was successfully created.'
redirect_to :action => 'index'
else
render :action => 'index'
end
end
In the code above we first find the project and the project activity based on the parameters posted with the form, next we create a new TimeEntry object and set the values on the form. The start and end times on the time entry are calculated using the current_date value and the hour and minutes selected on the time pickers.

In the next installment we will move beyond the simple capabilities implemented so far and begin thinking about what it takes to polish a product so that it can be ready for mass consumption. Until the next time.
Posted in Ruby, Rails | 2 comments | no trackbacks
Posted by Brian Sam-Bodden
Fri, 07 Dec 2007 01:28:00 GMT

This installment was supposed to be about building the Dashboard page of the Tempo application. Instead, I'm addressing some bugs reported by readers.
Fixing Bugs TDD-style
An observant reader pointed out that one of the tests for the TimeEntry class broke when testing using a date range across a month boundary.
The offending code is shown below:
def split!
remaining_time_entries = []
if needs_splitting
original_end = self.end
self.end = self.start.change(:hour => 23, :min => 59, :sec => 59)
(self.start.day+1..original_end.day).each do |day|
time_entry = self.clone
time_entry.start = time_entry.start.change(:mday => day).beginning_of_day
if day == original_end.day
time_entry.end = original_end
else
time_entry.end = time_entry.start.change(:hour => 23, :min => 59, :sec => 59)
end
remaining_time_entries << time_entry
end
end
remaining_time_entries
end
Even though the code above was created in a TDD fashion and several of the tests created exercised the code, the tests failed to cover all of the possible cases.
The lesson here is that when dealing with date ranges, you should test across months and year boundaries. The code above only works when the two datetimes are in the same month and year which is a critical flaw. The code uses the day method to loop form the start day to the end day of the range. This will obviously break across months and of course across years.
One good thing about the test above was (as some might argue whether this is a good thing or not) is that I used a date range that started on the current date and time. In a continuous integration scenario this broken test would had revealed itself in the hourly build.
I've also made the same mistake (of using the day method) to check if a range of dates needed splitting, here's the original code:
def needs_splitting
self.start.day < self.end.day
end
First, let's prove that the code is indeed broken. To accomplish this I've written a test that crosses a month boundary:
it "should know how to split a time entry across multiple days over a month boundary" do
time_entry = create_time_entry
time_entry.start = Time.local(2007,"nov",30,23,0,0)
time_entry.end = 5.hours.since(time_entry.start)
entries = time_entry.split!
entries.should_not be_empty
total = 0.0
entries.inject(0) {|total, e| total += e.total_hours}
total += time_entry.total_hours
total.should eql(5.0)
time_entry.needs_splitting.should_not be_true
end
The above test fails with the existing code. Now we can refactor the code to pass the test.
def needs_splitting
(self.start.year != self.end.year) ||
(self.start.month != self.end.month) ||
(self.start.day != self.end.day)
end
def split!
remaining_time_entries = []
if needs_splitting
original_end = self.end
self.end = end_of_day(self.start)
days = ((original_end - self.start) / 86400).ceil.to_i
(1..days).each do |day|
time_entry = self.clone
time_entry.start = time_entry.start.advance(:days => day).beginning_of_day
time_entry.end = time_entry.end.advance(:days => day)
if same_day(time_entry.end, original_end)
time_entry.end = time_entry.end.change(:hour => original_end.hour, :min => original_end.min, :sec => original_end.sec)
else
time_entry.end = end_of_day(time_entry.start)
end
remaining_time_entries << time_entry
end
end
remaining_time_entries
end
The needs_splitting method now checks the year, month and day. The split! method now finds the total number of whole days (plus one) in the datetime range. It then creates new TimeEntry(s) for each of the whole days (24 hours each) and a partial one of the last day on the range. To advance each one of the days I use the advance method (see Rails extensions to the Time class).
Posted in Ruby, Rails | no comments | no trackbacks
Posted by Brian Sam-Bodden
Fri, 30 Nov 2007 05:00:00 GMT

In the last three installments of this series we concentrated our efforts on building the most important entities of the Tempo domain. If you are anything like me you need visual feedback to truly get a sense of progress, if anything for the psychological effect of seeing something working (other than on the command line)
Before we jump into building the visual aspects of the application we should finish configuring the restful_authentication system we installed in installment #1.
Configuring Restful Authentication
The first step is to protect all controllers in our application by applying a before filter that will trigger the authentication logic. In Rails the ApplicationController is the ideal place to do so since is the default parent class for all controllers in an application:
class ApplicationController < ActionController::Base
include AuthenticatedSystem
session :session_key => '_tempo_session_id'
before_filter :login_required
end
In the file application.rb we include the AuthenticatedSystem and apply the before filter :login_required which will cascade down to any new controllers we create.
The restful_authentication plugin creates two controllers, the UsersController; a typical CRUD controller for User objects and the SessionsController which is the controller responsible the login/session management logic.
Taking a peek at the Session controller we see that it is set to skip the :login_required filter:
class SessionsController < ApplicationController
skip_before_filter :login_required
...
end
In the Views/sessions directory we customize the new session view (a.k.a the login screen):
<div class="login">
<div id="Dialog">
<h1>Welcome to Tempo, please log in</h1>
<dl>
<% form_tag session_path do -%>
<dt>Username:</dt>
<dd><%= text_field_tag 'login' %></dd>
<dt>Password:</dt>
<dd>
<%= password_field_tag 'password' %>
<span>(<a href="/forgot_password">I forgot my password/username</a>)</span>
</dd>
<dd><%= check_box_tag 'remember_me' %>Remember me on this computer</dd>
<dd><%= submit_tag 'Log in' %></dd>
<% end -%>
</dl>
</div>
</div>
Permanent Scaffolding?
To give us a jump start in the right direction I'm going to make use of a Rails scaffolding plug-in that should allow us to get some fairly decent web pages up and running in no time. There seems to be two contenders in the area of "permanent" Rails scaffolding plugins, one is the Streamlined Framework produced by the brilliant folks at Relevance the other one is ActiveScaffold created by the same team that built the now deprecated AjaxScaffold.
Both frameworks attempt to provide you with a way to generate ActiveRecord-driven scaffolding screens that are configurable and flexible enough that you theoretically won't have to later tear them down to build the "real" pages of you application. I've briefly looked at both frameworks previously and they both seem fairly even in terms of features. So just for the sake of trying something new I'm gonna use ActiveScaffold for the Tempo time tracking application.
ActiveScaffold
The ActiveScaffold website has quite a bit of documentation that should help you get started. I in particular found their "How to Approach Active Scaffold" article useful in understanding where it would be a good idea to use the framework.
To get started with ActiveScaffold in the Tempo project you'll need to first install the plug-in using Piston:
/> piston import http://activescaffold.googlecode.com/svn/tags/active_scaffold vendor/plugins/active_scaffold
Exported r633 from 'http://activescaffold.googlecode.com/svn/tags/active_scaffold' to 'vendor/plugins/active_scaffold'
Once you have installed the plug-in all that you need to do to enable the scaffolding for one of you model objects is to add a minimal amount of code to your controllers:
class UsersController < ApplicationController
active_scaffold :user
end
In your application layout head section you also need to include:
<%= javascript_include_tag :defaults %>
<%= active_scaffold_includes %>
That will give you the basic ActiveScaffold CRUD functionality.
RESTful Scaffolding
To make your scaffolding "RESTful" with ActiveScaffold, you need set up the resource map entry in your routes.rb file for each of your ActiveScaffold controllers:
map.resources :users, :active_scaffold => true
User Controller
ActiveScaffold will let you configure what fields are shown for each of the CRUD screens (and their formatting ...to an extent). ActiveScaffold allows you set configuration options globally for each controller in a global config block which you will place in your ApplicationController or with per-controller config blocks as we have done in the UsersController below:
class UsersController < ApplicationController
active_scaffold :user do |conf|
conf.actions.exclude :show
conf.list.columns = [:person, :login, :email]
conf.create.columns = [:person, :login, :email, :password, :password_confirmation]
conf.update.columns = [:person, :login, :email, :password, :password_confirmation]
end
end
In the code snippet above we turn off the "show" action and configure which objects fields we want to be shown in the different screens. For the "list" page we only want the to_s of Person, the login and email. In the create or update screens we also want to show the password and the password confirmation.
To test this screen you can point your browser to /users/new and create a new user. Make sure to uncomment the skip_before_filter line above since we don't have an official "registration" page yet. Once you create at least one user that you can log in with you can enable the filter again.
Other controllers will be similarly configured, but before we get there let's set up the look of the application with a set of CSS stylesheets and configuring some simple application navigation logic.
Main View Layout
To configure the main view layout for our application, under your Rails application Views/layouts directory customize the application.rhtml as shown below and pair it with a "good enough" stylesheet:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<head>
<title>Tempo</title>
<%= javascript_include_tag :defaults %>
<%= active_scaffold_includes %>
<%= stylesheet_link_tag 'tempo' %>
</head>
<body>
<!-- ====== -->
<!-- Header -->
<!-- ====== -->
<div id="Header">
<h3 class="global">
<% if logged_in? -%>
Logged in as <%= current_user %> (<a href="/logout" title="Log-out">Log-out</a>)
<% else -%>
<a href="/login" title="Login">Log-in</a>
<% end -%>
<a href="/help" class="image"><img alt="Help" src="/images/help.png" align="absmiddle" height="16" width="32"></a>
</h3>
<h1>
<img src="/images/tempo.png" title="Tracking time with Ease" alt="Tempo" border="0">
</h1>
<% if logged_in? -%>
<!-- =============== -->
<!-- Navigation Tabs -->
<!-- =============== -->
<div id="Tabs">
<%= links_for_navigation %>
</div>
<% end -%>
</div>
<!-- ============== -->
<!-- Beta Indicator -->
<!-- ============== -->
<img id="beta" src="/images/beta.png" alt="">
<div id="Wrapper">
<div class="Container">
<div id="DashContentFrame">
<!-- =========== -->
<!-- Left Column -->
<!-- =========== -->
<div class="Left">
<div class="col">
<% if flash[:notice] -%>
<div class="good" id="Flash">
<p><%= flash[:notice] %></p>
</div>
<% end -%>
<% if flash[:error] -%>
<div class="bad" id="Flash">
<p><%= flash[:error] %></p>
</div>
<% end -%>
<%= yield %>
</div>
<div class="bottom"> </div>
</div>
<!-- ============ -->
<!-- Right Column -->
<!-- ============ -->
<div class="Right">
<div class="col">
<h1>[[Help Title]]</h1>
<p>[[Help Body]]</p>
</div>
</div>
</div>
</div>
</div>
<!-- ====== -->
<!-- footer -->
<!-- ====== -->
<div id="footer">
<a id="powered" href="http://integrallis.com/" title="Integrallis Software, LLC.">
<img src="/images/powered-by-integrallis.png" title="Integrallis Software, LLC." alt="Integrallis logo" border="0">
</a>
</div>
</body>
</html>
Let's examine what the template provides. At the top of the page I plan to have a simple header that shows the currently logged in user and a logout link. If there is no user logged in then a login link is shown.
Further down the page (after the application logo) we will have a set of application tabs that will be generated by the utility method links_for_navigation which we will create later in the ApplicationHelper.
The body of the page will be divided into two columns, the largest of the two on the left at about 75% of the page that will contain the main elements of the application and the right column for application controls and (if I ever get to it) some interactive help.
Navigation
The navigation code is fairly simple, I'm doing this old school (read: I'm sure there are cleaner ways to do this) but we'll get to refactor this code later on. In this helper I have a method that builds a link for a given page (tagging the "current" page with a specific CSS class) and a method that returns all the links for the application.
module ApplicationHelper
def nav_link_to(name, options, html_options)
if current_page?(options)
html_options.store(:class, 'current')
end
link_to name, options, html_options
end
def links_for_navigation
tabs = []
tabs << nav_link_to('Users', {:controller => 'users', :action => 'list'}, {:title => 'Manage Users', :style => 'float: right; margin-right: 30px;'})
tabs << nav_link_to('Projects', {:controller => 'projects', :action => 'list'}, {:title => 'Manage Projects', :style => 'float: right;'})
tabs << nav_link_to('Activities', {:controller => 'activities', :action => 'list'}, {:title => 'Manage Activities', :style => 'float: right;'})
tabs << nav_link_to('Time Entries', {:controller => 'time_entries', :action => 'list'}, {:title => 'Manage Time Entries', :style => 'float: right;'})
tabs << nav_link_to('Dashboard', {:controller => 'dashboard', :action => 'index'}, {:title => 'Dashboard', :style => 'float: left;'})
html = '<ul id="MainTabs">'
tabs.each do |tab|
html += '<li>'
html += tab
html += '</li>'
end
html += '</ul>'
end
end
In the links_for_navigation method I construct an HTML unordered list and add the links for the application, notice that I'm styling the links/tabs to show right justified except for the tab that will become the entry point of the application. The "dashboard" which will be left justified.
Stylesheet
Instead of boring you with the CSS, here is a screenshot of the original HTML template styled by my CSS and a link to download the CSS and graphics if you are interested.

I've zipped up the tempo specific files in the public directory which you can download here
Finishing the controllers with ActiveScaffold
Now that we have all the elements in place let's tackle the configuration of each of the controllers. Along the way we'll make enhancements to the domain objects.
If you haven't done so already, navigate to /users/new on you application and create a new user (remember to disable the login filter)

Activities Controller
The Activity model object is extremely simple, having only two string fields; name and description. To correctly display the object instances in other ActiveScaffold screens (in drop-downs) we can provide an implementation of the to_s method as shown below:
class Activity < ActiveRecord::Base
def to_s
self.name
end
end
The controller code is minimal:
class ActivitiesController < ApplicationController
active_scaffold :activity do |conf|
conf.actions.exclude :show
conf.list.columns = [:name, :description]
conf.create.columns = [:name, :description]
conf.update.columns = [:name, :description]
end
end

Project Controller
As we did with Activities add the following to_s method to the Project model:
The controller again is very simple to configure:
class ProjectsController < ApplicationController
active_scaffold :project do |conf|
conf.actions.exclude :show
conf.list.columns = [:name, :description]
conf.create.columns = [:name, :description, :projects_activity, :projects_users]
conf.update.columns = [:name, :description, :projects_activity, :projects_users]
end
end

TimeEntries
The TimeEntries controller requires the most configuration. For the TimeEntry create and update screens we want to have the user, project, and project_activity appear as drop-down select elements. To accomplish this ActiveScaffold provides the form_ui element of the column API. The :select option renders a select box or a collection of checkboxes. There are also options for :calendar, :checkbox, :country, :password, :textarea and :usa_state
class TimeEntriesController < ApplicationController
active_scaffold :time_entry do |conf|
conf.actions.exclude :show
conf.columns[:project].form_ui = :select
conf.columns[:user].form_ui = :select
conf.columns[:projects_activity].form_ui = :select
conf.columns[:comment].form_ui = :textarea
conf.list.columns = [:project, :user, :date, :total_hours, :projects_activity]
conf.create.columns = [:project, :user, :projects_activity, :start, :end, :hours, :comment]
conf.update.columns = [:project, :user, :projects_activity, :start, :end, :hours, :comment]
end
end

In the next installment we will build the DashboardController that will provide a more stylish and user friendly interface for users to enter time into Tempo.
Posted in Ruby, Rails | 3 comments | no trackbacks
Posted by Brian Sam-Bodden
Wed, 31 Oct 2007 22:00:00 GMT

Now that we have a basic understanding of BDD with RSpec let's pick a business
critical user story and build it from tests outwards. With Tempo being a time tracking system
the most crucial story should be a "User enters Time".
TimeEntry
At the core of this story is the model class TimeEntry. In Tempo we want users to be able to
enter time by:
- a) picking a beginning time an an end time for a given date
- b) picking a beginning date-time and an end date-time
For billing purposes it is usually convenient to split the hours in a range of date-times into
chunks of hours per day (for the ranges that span over multiple days). The way I envision these
two entry methods are:
- a) a simple page that defaults to the current date and that allows the user to pick two
times for the given day along with other information required (project, activity, etc.)
- b) a page with two date/time pickers that enables the user to pick a range (or some fancier
UI control if I can find one)
In the spirit of TDD let's start with the skeleton RSpec specification for TimeEntry.
In the spec/models directory of the application create the Ruby file time_entry_spec.rb:
describe TimeEntry do
before do
@time_entry = TimeEntry.new
end
it "should be invalid without an associated User" do
end
it "should be invalid without an associated Project" do
end
it "should be invalid without an associated Project Activity" do
end
it "should know that it needs to be split across days" do
end
it "should know how to split a time entry across multiple days" do
end
it "should not have a value higher than 24 hours" do
end
end
Notice that I've added an expectation that a given time entry should know that it
needs to be split across multiple days and it also should know how to perform this split.
If I run the tests now they should obviously fail. We don't even have a TimeEntry class in place:
/> rake spec
(in /Users/bsbodden/Documents/projects/tempo)
/.../lib/active_support/dependencies.rb:266:in `load_missing_constant': uninitialized constant TimeEntry (NameError)
Let's get pass that one problem and create the model:
/> script/generate rspec_model TimeEntry
exists app/models/
exists spec/models/
exists spec/fixtures/
create app/models/time_entry.rb
create spec/fixtures/time_entries.yml
overwrite spec/models/time_entry_spec.rb? [Ynaqd] n
skip spec/models/time_entry_spec.rb
exists db/migrate
create db/migrate/004_create_time_entries.rb
/>
Notice that the generate task will try to overwrite the spec we just wrote so answer NO (n) to the overwrite question as shown above.
Let's add the basic fields we would expect to find in our TimeEntry class/table:
class CreateTimeEntries < ActiveRecord::Migration
def self.up
create_table :time_entries do |t|
t.column :user_id, :integer
t.column :project_id, :integer
t.column :projects_activity_id, :integer
t.column :start, :datetime
t.column :end, :datetime
t.column :hours, :float
t.column :comment, :string
end
end
def self.down
drop_table :time_entries
end
end
Run the migration to get the table created:
/> rake db:migrate
(in /Users/bsbodden/Documents/projects/tempo)
== CreateTimeEntries: migrating ===============================================
-- create_table(:time_entries)
-> 0.0414s
== CreateTimeEntries: migrated (0.0416s) ======================================
If we run the tests, they all pass now, that's not good, not quite TDD, so let's add some substance and break things as they should:
Let's start with the easy stuff:
before do
@time_entry = TimeEntry.new
@user = User.new
end
it "should be invalid without an associated User" do
@time_entry.should_not be_valid
@time_entry.errors.on(:user).should eql("must have an associated user")
@time_entry.user = @user
@time_entry.should be_valid
end
Run the tests again and we now have our first 'expected' failure:
1)
'TimeEntry should be invalid without an associated User' FAILED
expected valid? to return false, got true
./spec/models/time_entry_spec.rb:14:
Finished in 0.06691 seconds
8 examples, 1 failure
As we learned in part II, to pass this test we need a one liner to add an ActiveRecord validation:
class TimeEntry < ActiveRecord::Base
has_one :user
validates_presence_of :user, :message => 'must have an associated user'
end
Test again to confirm that the validation test passes:
/> rake spec
(in /Users/bsbodden/Documents/projects/tempo)
........
Finished in 0.071294 seconds
8 examples, 0 failures
Next one down the line is the test for an associated project:
before do
@time_entry = TimeEntry.new
@user = User.new
@project = Project.new
end
it "should be invalid without an associated Project" do
@time_entry.should_not be_valid
@time_entry.errors.on(:project).should eql('must have an associated project')
@time_entry.project = @project
@time_entry.should be_valid
end
Running the test again should confirm that we do not have a Project model yet:
/> rake spec
(in /Users/bsbodden/Documents/projects/tempo)
..FFFFFF
1)
NameError in 'TimeEntry should not have a value higher than 24 hours if expressed as a single hour value'
uninitialized constant Project
./spec/models/time_entry_spec.rb:12:
2)
NameError in 'TimeEntry should be broken into two or more TimeEntry instances if it date-time range spans over multiple days'
uninitialized constant Project
./spec/models/time_entry_spec.rb:12:
3)
NameError in 'TimeEntry should either consist of a date-time range or a single hour value'
uninitialized constant Project
./spec/models/time_entry_spec.rb:12:
4)
NameError in 'TimeEntry should be invalid without an associated Project Activity'
uninitialized constant Project
./spec/models/time_entry_spec.rb:12:
5)
NameError in 'TimeEntry should be invalid without an associated Project'
uninitialized constant Project
./spec/models/time_entry_spec.rb:12:
6)
NameError in 'TimeEntry should be invalid without an associated User'
uninitialized constant Project
./spec/models/time_entry_spec.rb:12:
Finished in 0.121636 seconds
8 examples, 6 failures
Add the Project model to the mix using the rake task:
/> script/generate rspec_model Project
exists app/models/
exists spec/models/
exists spec/fixtures/
create app/models/project.rb
create spec/fixtures/projects.yml
create spec/models/project_spec.rb
exists db/migrate
create db/migrate/005_create_projects.rb
Modify the migration to add the basic fields expected in a Project:
class CreateProjects < ActiveRecord::Migration
def self.up
create_table :projects do |t|
t.column :name, :string
t.column :description, :string
t.column :owner_id, :integer
end
end
def self.down
drop_table :projects
end
end
and run the migration:
/> rake db:migrate
(in /Users/bsbodden/Documents/projects/tempo)
== CreateProjects: migrating ==================================================
-- create_table(:projects)
-> 0.0377s
== CreateProjects: migrated (0.0380s) =========================================
Let's test again with the Project model in place:
/> rake spec
(in /Users/bsbodden/Documents/projects/tempo)
......F..
1)
'TimeEntry should be invalid without an associated Project' FAILED
expected "must have an associated project", got nil (using .eql?)
./spec/models/time_entry_spec.rb:24:
Finished in 0.081038 seconds
9 examples, 1 failure
Great, now we broke what we wanted to break ;-)
Let's now add the validations for project:
class TimeEntry < ActiveRecord::Base
has_one :user
has_one :project
validates_presence_of :user, :message => 'must have an associated user'
validates_presence_of :project, :message => 'must have an associated project'
end
Let's test one more time:
/> rake spec
(in /Users/bsbodden/Documents/projects/tempo)
......FF.
1)
'TimeEntry should be invalid without an associated Project' FAILED
expected valid? to return true, got false
./spec/models/time_entry_spec.rb:26:
2)
'TimeEntry should be invalid without an associated User' FAILED
expected valid? to return true, got false
./spec/models/time_entry_spec.rb:19:
Finished in 0.083707 seconds
9 examples, 2 failures
Let's fix our problems by adding a module with a helper create method for TimeEntries and we'll just nil the associations that
we are looking to test:
module TimeEntrySpecHelperMethods
def create_time_entry(options = {})
@user = User.new
@project = Project.new
TimeEntry.create({ :user => @user, :project => @project }.merge(options))
end
end
describe TimeEntry do
include TimeEntrySpecHelperMethods
it "should be invalid without an associated User" do
time_entry = create_time_entry(:user => nil)
time_entry.should_not be_valid
time_entry.errors.on(:user).should eql('must have an associated user')
time_entry.user = @user
time_entry.should be_valid
end
it "should be invalid without an associated Project" do
time_entry = create_time_entry(:project => nil)
time_entry.should_not be_valid
time_entry.errors.on(:project).should eql('must have an associated project')
time_entry.project = @project
time_entry.should be_valid
end
Pending Tests in RSpec
What do you know! it always helps to have an expert near by, Joe O'brien from the
EdgeCase just stopped by and schooled me on some of
the subtleties of RSpec.
If you remember I started with a skeleton for a test that was passing and that's not
quite following TDD since we should have a test that initially fails. Otherwise,
in a team environment there is a big chance that we will "forget" to implement the test.
A very useful trick when you are "scaffolding your tests" like I was is to remove the block,
that is the do..end and RSpec will report the tests as pending. So my test now looks like:
require File.dirname(__FILE__) + '/../spec_helper'
module TimeEntrySpecHelperMethods
def create_time_entry(options = {})
@user = User.new
@project = Project.new
TimeEntry.create({ :user => @user, :project => @project }.merge(options))
end
end
describe TimeEntry do
include TimeEntrySpecHelperMethods
it "should be invalid without an associated User" do
time_entry = create_time_entry(:user => nil)
time_entry.should_not be_valid
time_entry.errors.on(:user).should eql('must have an associated user')
time_entry.user = @user
time_entry.should be_valid
end
it "should be invalid without an associated Project" do
time_entry = create_time_entry(:project => nil)
time_entry.should_not be_valid
time_entry.errors.on(:project).should eql('must have an associated project')
time_entry.project = @project
time_entry.should be_valid
end
it "should be invalid without an associated Project Activity"
it "should know that it needs to be split across days"
it "should know how to split a time entry across multiple days"
it "should not have a value higher than 24 hours"
end
Notice the do..ends are gone in the unimplemented tests. When we run the test we now get:
/> rake spec
(in /Users/bsbodden/Documents/projects/tempo)
...PPPP..
Finished in 0.074545 seconds
9 examples, 0 failures, 4 pending
Pending:
TimeEntry should know that it needs to be split across days (Not Yet Implemented)
TimeEntry should not have a value higher than 24 hours (Not Yet Implemented)
TimeEntry should know how to split a time entry across multiple days (Not Yet Implemented)
TimeEntry should be invalid without an associated Project Activity (Not Yet Implemented)
Now that is cool! Thanks Joe.
Next we'll tackle a more challenging test and associated functionality:
TimeEntry "should know that it needs to be split across days"
This test should check that a TimeEntry can detect that it spans over multiple days. The test is simple,
set the start and end dates so that they span over multiple days and check that a method, let's call it
"needs_splitting" returns true.
it "should know that it needs to be split across days" do
time_entry = create_time_entry
time_entry.start = 2.hours.ago(Time.today.beginning_of_day)
time_entry.end = 5.hours.since(time_entry.start)
time_entry.needs_splitting.should be_true
end
Run the test and watch it fail:
/> rake spec
(in /Users/bsbodden/Documents/projects/tempo)
...PFP..
1)
NoMethodError in 'TimeEntry should know that it needs to be split across days'
undefined method `needs_splitting' for #
./spec/models/time_entry_spec.rb:40:
Finished in 0.088024 seconds
8 examples, 1 failure, 3 pending
We need a 'needs_splitting' method:
class TimeEntry < ActiveRecord::Base
has_one :user
has_one :project
validates_presence_of :user, :message => 'must have an associated user'
validates_presence_of :project, :message => 'must have an associated project'
def needs_splitting
false
end
end
testing again should now gives us failing test:
/> rake spec
(in /Users/bsbodden/Documents/projects/tempo)
...PFP..
1)
'TimeEntry should know that it needs to be split across days' FAILED
expected true, got false
./spec/models/time_entry_spec.rb:40:
Finished in 0.127189 seconds
8 examples, 1 failure, 3 pending
TimeEntry should not have a value higher than 24 hours (Not Yet Implemented)
TimeEntry should know how to split a time entry across multiple days (Not Yet Implemented)
TimeEntry should be invalid without an associated Project Activity (Not Yet Implemented)
To pass the test we can simply check that the day on the end Time is greater than the day on the
start Time:
def needs_splitting
self.start.day < self.end.day
end
Now we can run the test, passing with flying colors:
/> rake spec
(in /Users/bsbodden/Documents/projects/tempo)
...P.P..
Finished in 0.079906 seconds
8 examples, 0 failures, 3 pending
TimeEntry should not have a value higher than 24 hours (Not Yet Implemented)
TimeEntry should know how to split a time entry across multiple days (Not Yet Implemented)
TimeEntry should be invalid without an associated Project Activity (Not Yet Implemented)
TimeEntry "should know how to split a time entry across multiple days"
The next piece of functionality is a bit more involved. We want a method that tells us whether a
TimeEntry instance spans over multiple days, if that is the case then I want to also have a method
that can give me a collection of TimeEntry(s) representing all the single day interval.
So here's a simple test that I think can accomplish that:
it "should know how to split a time entry across multiple days" do
time_entry = create_time_entry
time_entry.start = 2.hours.ago(Time.today.beginning_of_day)
time_entry.end = 5.hours.since(time_entry.start)
entries = time_entry.split!
entries.should_not be_empty
total = 0.0
entries.inject(0) {|total, e| total += e.total_hours}
total += time_entry.total_hours
total.should eql(5.0)
time_entry.needs_splitting.should_not be_true
end
In this test I have a start time that is 2 hours before midnight of the current day and an end date
that 5 hours ahead which gives us a day crossing range. Then we call the split! method that should
return a collection of one or more TimeEntry(s) each containing any hours allocated to a whole or
partial day. The split! method should also modify the current TimeEntry to contain only the hours
reported on the first day of the range.
In the test we also check that the total number of hours is still the same after the split and that
after the split we no longer need to split (e.g. the needs_splitting should return false)
Let's add the skeleton methods for split! and total_hours:
def split!
remaining_time_entries = []
end
def total_hours
0.0
end
Running the test should show a failure:
/> rake spec
(in /Users/bsbodden/Documents/projects/tempo)
...PF.P..
1)
'TimeEntry should know how to split a time entry across multiple days' FAILED
expected empty? to return false, got true
./spec/models/time_entry_spec.rb:48:
Finished in 0.144307 seconds
9 examples, 1 failure, 2 pending
TimeEntry should not have a value higher than 24 hours (Not Yet Implemented)
TimeEntry should be invalid without an associated Project Activity (Not Yet Implemented)
Let's work out the split! method. Below is my first attempt (somehow I think my Java background might show and I welcome more rubyeske versions of this method):
def split!
remaining_time_entries = []
if needs_splitting
original_end = self.end
self.end = self.start.change(:hour => 23, :min => 59, :sec => 59)
(self.start.day+1..original_end.day).each do |day|
time_entry = self.clone
time_entry.start = time_entry.start.change(:mday => day).beginning_of_day
if day == original_end.day
time_entry.end = original_end
else
time_entry.end = time_entry.start.change(:hour => 23, :min => 59, :sec => 59)
end
remaining_time_entries << time_entry
end
end
remaining_time_entries
end
The total_hours method is fairly simple:
def total_hours
(((self.end - self.start) / 3600) * 10**2).round.to_f / 10**2
end
We can run the tests again and see what we have left to build:
/> rake spec
(in /Users/bsbodden/Documents/projects/tempo)
...P..P..
Finished in 0.15218 seconds
9 examples, 0 failures, 2 pending
Pending:
TimeEntry should not have a value higher than 24 hours (Not Yet Implemented)
TimeEntry should be invalid without an associated Project Activity (Not Yet Implemented)
TimeEntry "should not have a value higher than 24 hours"
This test if fairly simple to code, we are looking to check that TimeEntry(s) that have been split do not report more than 24 hours.
it "should not have a value higher than 24 hours" do
time_entry = create_time_entry
time_entry.start = 2.hours.ago(Time.today.beginning_of_day)
time_entry.end = 5.hours.since(4.days.since(time_entry.start))
entries = time_entry.split!
entries.should_not be_empty
entries.each do |entry|
entry.total_hours.should be <= 24.0
end
time_entry.total_hours.should be <= 24.0
end
I added a range of time that spans multiple days and then call split! and check that any of the resulting TimeEntry(s)
as well as the original TimeEntry do not have more than 24 hours
Let's run the tests again:
/> rake spec
(in /Users/bsbodden/Documents/projects/tempo)
..............P..
Finished in 0.129319 seconds
17 examples, 0 failures, 1 pending
Pending:
TimeEntry should be invalid without an associated Project Activity (Not Yet Implemented)
TimeEntry "should be invalid without an associated Project Activity"
The final test on for TimeEntry seems fairly simple:
time_entry = create_time_entry(:projects_activity => nil)
time_entry.should_not be_valid
time_entry.errors.on(:projects_activity).should eql('must have an associated project activity')
time_entry.projects_activity = @projects_activity
time_entry.should be_valid
If you got back to installment #2 you might
remember that from the initial Tempo domain model we know that "A Project has some Project Activities associated which are a subset of
a global list of Activities". Therefore we need to create a few migrations and a few models to complete to get this test to pass.
Activities
Activities are the the global set of activities. Activities can be added to a project (see Project Activities below)
First create the migration:
/> script/generate migration create_activities
exists db/migrate
create db/migrate/006_create_activities.rb
An Activity needs a name and a description:
class CreateActivities < ActiveRecord::Migration
def self.up
create_table :activities do |t|
t.column :name, :string
t.column :description, :string
end
end
def self.down
drop_table :activities
end
end
Run the migration:
/> rake db:migrate
(in /Users/bsbodden/Documents/projects/tempo)
== CreateActivities: migrating ================================================
-- create_table(:activities)
-> 0.0030s
== CreateActivities: migrated (0.0032s) =======================================
Project Users
A Project can have Users associated with it. Only users associated with a Project should be able to enter
time against the Project:
Let's create the migration:
/> script/generate migration create_project_users
exists db/migrate
create db/migrate/007_create_project_users.rb
A Project User table associates a Project and a User. We also need a Role in this table since we will have
project owners (managers) and project workers:
class CreateProjectUsers < ActiveRecord::Migration
def self.up
create_table :projects_users do |t|
t.column :project_id, :integer, :null => false
t.column :user_id, :integer, :null => false
t.column :role_type, :integer, :null => false
end
end
def self.down
drop_table :projects_users
end
end
Run the migration:
/> rake db:migrate
(in /Users/bsbodden/Documents/projects/tempo)
== CreateProjectUsers: migrating ==============================================
-- create_table(:projects_users)
-> 0.0029s
== CreateProjectUsers: migrated (0.0031s) =====================================
Project Activities
Individual Projects will have some Activities that apply. The Project Activities table
will maintain those associations:
/> script/generate migration create_project_activities
exists db/migrate
create db/migrate/008_create_project_activities.rb
In the table we need only the id of the Project and the Activity we are associating:
class CreateProjectActivities < ActiveRecord::Migration
def self.up
create_table :projects_activities do |t|
t.column :project_id, :integer, :null => false
t.column :activity_id, :integer, :null => false
end
end
def self.down
drop_table :projects_activities
end
end
Run the migration:
/> rake db:migrate
(in /Users/bsbodden/Documents/projects/tempo)
== CreateProjectActivities: migrating =========================================
-- create_table(:projects_activities)
-> 0.1885s
== CreateProjectActivities: migrated (0.1887s) ================================
Finishing the Models
To get the first working Models I will add some ActiveRecord associations.
For a Project we have a bunch of has_many associations. You guess the obvious ones,
like a:
- A Project has many Users via the projects_users table
- A Project has many Activities via the projects_activity table
To set those associations I am using a couple of explicit join models.
The join model for Users in a Project:
class ProjectsUser < ActiveRecord::Base
belongs_to :project
belongs_to :user
end
and the join model for the Activities that time can be reported against for a
particular Project:
class ProjectsActivity < ActiveRecord::Base
belongs_to :project
belongs_to :activity
end
Notice that below in the Project class I can also use the :has_many with the :through option to have
a list of actual Activity objects available in the model explicitly via the join model projects_activity.
Similarly we have Users and specific type of Users by using the :conditions option to filter
the project_users join model:
class Project < ActiveRecord::Base
has_many :projects_users
has_many :projects_activity
has_many :activities, :through => :projects_activity, :source => :activity
has_many :users, :through => :projects_users, :source => :user
has_many :managers, :through => :projects_users, :source => :user,
:conditions => "projects_users.role_type = 2"
has_many :workers, :through => :projects_users, :source => :user,
:conditions => "projects_users.role_type = 1"
end
Update:
A reader pointed out that at this point the last test for TimeEntry; "should be invalid without an associated Project Activity" was not passing. That's not surprising since I forgot to post the code for the validations in TimeEntry.
Here's the current state of TimeEntry:
class TimeEntry < ActiveRecord::Base
belongs_to :user
belongs_to :project
belongs_to :projects_activity
attr_reader :total_hours, :date
alias :total_hours? :total_hours
validates_presence_of :user, :message => 'must have an associated user'
validates_presence_of :project, :message => 'must have an associated project'
validates_presence_of :projects_activity, :message => 'must have an associated project activity'
def needs_splitting
self.start.day < self.end.day
end
def split!
remaining_time_entries = []
if needs_splitting
original_end = self.end
self.end = self.start.change(:hour => 23, :min => 59, :sec => 59)
(self.start.day+1..original_end.day).each do |day|
time_entry = self.clone
time_entry.start = time_entry.start.change(:mday => day).beginning_of_day
if day == original_end.day
time_entry.end = original_end
else
time_entry.end = time_entry.start.change(:hour => 23, :min => 59, :sec => 59)
end
remaining_time_entries << time_entry
end
end
remaining_time_entries
end
def total_hours
if (self.start != nil) && (self.end != nil)
(((self.end - self.start) / 3600) * 10**2).round.to_f / 10**2
else
0.0
end
end
def year
self.start.year
end
def date
"#{Time::RFC2822_DAY_NAME[self.start.wday]}, #{Time::RFC2822_MONTH_NAME[self.start.month-1]} #{self.start.day} #{self.start.year}"
end
def to_s
"\"#{self.comment}\" on #{self.date} for #{self.total_hours} hours"
end
end
Now you can run the test again and we should now have a clean set of tests for TimeEntry. In the next installment
we will tackle testing and creating the application controllers and finally get something that we can show our
besides tests on the command line.
Posted in Ruby, Rails | 2 comments | no trackbacks
Posted by Brian Sam-Bodden
Wed, 24 Oct 2007 23:00:00 GMT

Welcome back to part II of this series. Like I mentioned at the end of the first post; now we start the real work.
BDD, TDD and DDD
The sentence below summarizes what our development strategy will be:
"We are going to flesh the domain (DDD) with behaviours using (BDD) that we will express as tests (TDD) before writing any code"
We are going to be using RSpec, a Ruby XUnit framework that facilitates the writing of tests that test behavior rather than state, in the spirit of Behavior-Driven Development (BDD). With RSpec we will write the tests that will guide the development of our application. We will try to stick with Test-Driven Development (TDD) as closely as possible. RSpec can be thought of as a domain specific language for behavior testing via expectations.
If you have been using Test::Unit and you want to use RSpec you don't have to chose one over the other. Although if you have to make a choice, RSpec will give you more readable tests.
If you want to get some background on the ideas behind BDD take a peek at
Installing RSpec
As we did with Restful Authentication plugin we'll use Piston to install RSpec:
/> piston import svn://rubyforge.org/var/svn/rspec/tags/CURRENT/rspec
vendor/plugins/rspec
Exported r2563 from 'svn://rubyforge.org/var/svn/rspec/tags/CURRENT/rspec'
to 'vendor/plugins/rspec'
Installing RSpec On Rails
An RSpec plugin that integrates with Rails exist which gives you the ability to create specs (read behavior tests) for your models, views, controllers and helpers.
To install the RSpec On Rails plugin we again use Piston:
/> piston import svn://rubyforge.org/var/svn/rspec/tags/CURRENT/rspec_on_rails
vendor/plugins/rspec_on_rails
Exported r2563 from 'svn://rubyforge.org/var/svn/rspec/tags/CURRENT/rspec_on_rails'
to 'vendor/plugins/rspec_on_rails'
To create the necessary RSpec directories and bootstrap code you need to run the rspec rake task:
/> script/generate rspec
exists spec
create spec/spec_helper.rb
create spec/spec.opts
create previous_failures.txt
create script/spec_server
create script/spec
Building Tempo's Domain
Now we are ready to tackle the domain of the application
Initial Domain Model
I know it is a cliché but I actually modelled the domain on a paper towel which I later scanned (Jim Weirich is much more classy and uses fine napkins).
Below is my first pass at the nouns that immediately came to mind when thinking about tracking time in the context of a project at a consulting firm:

Based on the noun list above I created a simple UML-like diagram (in later issues of this blog I'll create something nicer):

The list below spells out what my thinking was when creating this model:
- A User is associated with a Person *
- A User can be part of zero or more Projects
- A Project belongs to a Client *
- A Project has some Project Activities associated which are a subset of a global list of Activities
- A TimeEntry represents time entered against a project by a User in the context of a particular Activity
(1*) my thinking here is that I would have non-user entities in the system therefore I wanted to separate the Person/People
(3*) don't know yet if a Client is a Person, User or other Entity
Person
Based on my napkin domain model a User has an associated Person. Let's use the Rspect generate script to create a model class for Person with its associated specs:
/> script/generate rspec_model Person
exists app/models/
create spec/models/
create spec/fixtures/
create app/models/person.rb
create spec/fixtures/people.yml
create spec/models/person_spec.rb
exists db/migrate
create db/migrate/002_create_people.rb
Just like the normal Rails "generate script" we end up with a Person class in person.rb:
class Person < ActiveRecord::Base
end
But we also get the skeleton code for a Person spec in person_spec.rb:
require File.dirname(__FILE__) + '/../spec_helper'
describe Person do
before(:each) do
@person = Person.new
end
it "should be valid" do
@person.should be_valid
end
end
The basic spec skeleton "describe"s the behavioral contract that a class should abide to. The before method gets
executed before each of the "it" or specification methods. In the case about we have the simplest specification
method for a Rails model object. In this case it will call the validation method exposed by ActiveRecord.
Before we move forward we need to flesh out the Person class via the ActiveRecord migration. I'm gonna add just the
basic fields that we would expect in a Person object (First Name, Last Name and Middle Initial):
class CreatePeople < ActiveRecord::Migration
def self.up
create_table :people do |t|
t.column :first_name, :string
t.column :last_name, :string
t.column :middle_initial, :string
end
end
def self.down
drop_table :people
end
end
Let's run the migration...
/> rake db:migrate
(in /Users/bsbodden/Documents/projects/tempo)
== CreatePeople: migrating ====================================================
-- create_table(:people)
-> 0.0033s
== CreatePeople: migrated (0.0035s) ===========================================
During development I might create a ton of little migrations, you can later glue some of those together and refactor your migrations.
In the Person class I want to make the first and last names mandatory but the middle initial optional. Now we can enhance the spec
to test for the presence of the required fields.
require File.dirname(__FILE__) + '/../spec_helper'
describe "A person (in general)" do
before do
@user = Person.new
end
it "should be invalid without a first and last name" do
@user.should_not be_valid
@user.first_name = 'Chunky'
@user.last_name = 'Bacon'
@user.should be_valid
end
end
The spec above tell us that a "user should be invalid without a first and last name". I check that right after
we call the new method the object should not be valid and that after we set the first and last name it should.
Let's run the test and see what happens:
/> rake spec
(in /Users/bsbodden/Documents/projects/tempo)
F
1)
'A person (in general) should be invalid without a first and last name' FAILED
expected valid? to return false, got true
./spec/models/person_spec.rb:19:
Finished in 0.100052 seconds
1 example, 1 failure
Ok, the test failed... and that's good. Now let's write enough code to pass the test. To do this we can just
piggy back on the power of ActiveRecord and add validations to the Person model:
class Person < ActiveRecord::Base
validates_presence_of :first_name, :last_name
end
There is sometimes a fine line between testing your code and inadvertently testing a framework. In this case
I think that we are testing that our code correctly uses the validation features of ActiveRecord but some might
argue this.
Let's run the tests again:
/> rake spec
(in /Users/bsbodden/Documents/projects/tempo)
.
Finished in 0.046277 seconds
1 example, 0 failures
User
Great, so we have our first pseudo real spec. The next thing we want to do is associate the Person object to the User object created by the Restful Authentication generator.
From the napkin model we know that a User should have a Person. Let's write a spec that tests that condition:
module UserSpecHelperMethods
def create_user(options = {})
User.create({ :login => 'cbacon',
:email => 'chunky@bacon.com',
:password => 'uhmmbacon',
:password_confirmation => 'uhmmbacon'
}.merge(options))
end
end
describe User do
include UserSpecHelperMethods
before do
@user = create_user
@person = Person.new
@person.first_name = 'Chunky'
@person.last_name = 'Bacon'
end
it "should be invalid without an associated Person" do
@user.should_not be_valid
@user.errors.on(:person)
.should eql("must have an associated person")
@user.person = @person
@user.should be_valid
end
end
Notice that I added a module to my spec class with a helper method that creates a User object with certain fields populated.
In the "before" method I create a User and a Person (a valid Person). Finally in the method that tests that a
User "should be invalid without an associated Person" I follow the pattern we used before. I test that the object is invalid and
then I set the Person object onto the User object and test that they User is now valid.
I added a little bit of code temporarily to inspect the errors hash so that I could grab the exact error String and test for it:
@user.errors.each do |error|
puts error
end
With new test in place we can run the tests again:
/> rake spec
(in /Users/bsbodden/Documents/projects/tempo)
.F
1)
NoMethodError in 'User should be invalid without an associated Person'
undefined method `person=' for #
./spec/models/user_spec.rb:17:
Finished in 0.054722 seconds
2 examples, 1 failure
As expected, it fails. Let's write just enough code to pass the test, let's create a relationship between User and Person
The ActiveRecord relationship I'm interested in is a has_one, as the code snippet from user.rb below shows:
has_one :person, :dependent => :destroy
The :dependent flag would make sure that the deletes cascade so we are implying that a Person that gets associated
with a User should not be left orphaned.
We also need to create a Rails Migration to add the relationship in the database:
/> script/generate migration add_person_to_user
exists db/migrate
create db/migrate/003_add_person_to_user.rb
Modify the migration to add a column for the person_id:
class AddPersonToUser < ActiveRecord::Migration
def self.up
add_column :users, :person_id, :integer
end
def self.down
remove_column :users, :person_id
end
end
... and of course, run the migration:
/> rake db:migrate
(in /Users/bsbodden/Documents/projects/tempo)
== AddPersonToUser: migrating =================================================
-- add_column(:users, :person_id, :integer)
-> 0.6555s
== AddPersonToUser: migrated (0.6556s) ========================================
... and test again
/> rake spec
(in /Users/bsbodden/Documents/projects/tempo)
.F
1)
'User should be invalid without an associated Person' FAILED
expected valid? to return true, got false
./spec/models/user_spec.rb:18:
Finished in 0.058424 seconds
2 examples, 1 failure
Add the validation in user.rb to pass the test:
validates_presence_of :person,
:message => 'must have an associated person'
... and test again:
/> rake spec
(in /Users/bsbodden/Documents/projects/tempo)
../spec/models/user_spec.rb:26: warning: multiple values for a block parameter (2 for 1)
from ...
person
must have an associated person
.
Finished in 0.059858 seconds
2 examples, 0 failures
So far we have learned the basics of testing your model using RSpec. In the next installment we will pick a critical
piece of functionality and build it using TDD/BDD/DDD.
Posted in Ruby, Rails | no comments | no trackbacks
Posted by Brian Sam-Bodden
Sat, 20 Oct 2007 18:57:00 GMT

This is the first blog entry in a series about the process of going from idea to running software, while following all of the practices that we praise and push at our clients.
Tempo
At Integrallis we have been using an open source PHP-based Time Tracking Tool for a few years now. Don't get me wrong, we are thankful that this tool was out there to help us run our business but it's missing several features that we consider key, we don't have anybody on staff that knows PHP and the app is just plain ugly ;-)
So we decided to write our own, Yeah, yeah we are suffering from the not invented here syndrome but in my defense I thought it would make for a nice series of blog entries/tutorials on how we are approaching the building of this application using Ruby on Rails.
So let's start by describing in just a few paragraphs what is that we are looking to build.
"Tempo is a project based time tracking web application targeted primarily at small consulting firms and independent consultants"
To track the development of Tempo we are using the agile project management tool Savila created by our friends at Caimito Technologies. We are starting with a few simple user stories that should take us to the basic functionality required.
The initial user stories revolve around the basics of a time tracking system, basic project/tasks management and the authentication system.
Getting Started
Before we get to the user stories let's make sure that you have everything that you need to get started. We are using Rails version 1.2.3, Ruby 1.8.6 and MySQL 5.0.24.
There are countless Rails tutorials on the web. If you've never worked with Rails I suggest you go through one the top RoR tutorials
I have created a skeleton rails application (hopefully you know how to do that by know but just in case you don't, simply type:
/>rails tempo
We can use the about script to see what we have so far...
/> script/about
About your application's environment
Ruby version 1.8.6 (i686-darwin8.9.1)
RubyGems version 0.9.2
Rails version 1.2.3
Active Record version 1.15.3
Action Pack version 1.13.3
Action Web Service version 1.2.3
Action Mailer version 1.3.3
Active Support version 1.4.2
Application root /Users/bsbodden/Documents/projects/tempo
Environment development
Database adapter mysql
Database schema version 4
At this point you should be able to get your skeleton application running.
Initial User Stories
The table and the Savila screenshot below show the user stories that we will be tackling on "Sprint #1" of the Tempo development. The duration of the Spring should be around 2 weeks (I'm building Tempo on my spare time ;-)
| Story Id | Short Description | Status | Complexity | Business Value |
| TMPO-4 | Login into the site | OPEN | 10 points | Business Critical |
| TMPO-15 | Site Admin adds global Activities | OPEN | 20 points | Business Critical |
| TMPO-19 | Site Administrator Creates a Project Owner Account | OPEN | 20 points | Business Critical |
| TMPO-21 | Site Administrator creates a User Account | OPEN | 20 points | Business Critical |
| TMPO-25 | Site Admin creates a Project | OPEN | 20 points | Business Critical |
| TMPO-27 | Site Administrator assigns a User as Project Owner for a given Project | OPEN | 20 points | Business Critical |
| TMPO-29 | User enters Time | OPEN | 40 points | Business Critical |

Managing Plug-ins with Piston
Before we start downloading and installing plugins I decided that I needed a better way to manage the plugins in my application. To accomplish this I installed Piston. Piston enables you to better manage the vendor branch. It does a better and simpler job that using svn:externals, and you can "install" plugins into your vendor/plugins directory and lock them to a certain version.
Install Piston
Install the piston gem by using the following command:
gem install -y piston
Bulk updating Gem source index for: http://gems.rubyforge.org
Successfully installed piston-1.3.3
Restful Authentication
Since a lot of the initial stories revolve around a User, a good starting point is to get an authentication system going. Since I want to also make Tempo a Restful Rails application. I will based my authentication system on the Restful Authentication plugin, which is a restful version of the familiar actsas_authenticated. Let's use Piston to import the restfulauthentication plugin and create our user model and sessions controller. I find that very often in web-based application the User model is a good starting point from where to flesh out the rest of the system.
Install Restful Authentication Plugin
/> piston import http://svn.techno-weenie.net/projects/plugins/restful_authentication
vendor/plugins/restful_authentication
Exported r2983 from 'http://svn.techno-weenie.net/projects/plugins/restful_authentication'
to 'vendor/plugins/restful_authentication'
Generate User model and the Sessions controller
/> script/generate authenticated user sessions
----------------------------------------------------------------------
Don't forget to:
- add restful routes in config/routes.rb
map.resources :users
map.resource :session
Rails 1.2.3 may need a :controller option for the singular resource:
- map.resource :session, :controller => 'sessions'
Try these for some familiar login URLs if you like:
map.signup '/signup', :controller => 'users', :action => 'new'
map.login '/login', :controller => 'sessions', :action => 'new'
map.logout '/logout', :controller => 'sessions', :action => 'destroy'
----------------------------------------------------------------------
exists app/models/
exists app/controllers/
exists app/controllers/
exists app/helpers/
create app/views/sessions
exists test/functional/
exists app/controllers/
exists app/helpers/
create app/views/users
exists test/functional/
exists test/unit/
create app/models/user.rb
create app/controllers/sessions_controller.rb
create app/controllers/users_controller.rb
create lib/authenticated_system.rb
create lib/authenticated_test_helper.rb
create test/functional/sessions_controller_test.rb
create test/functional/users_controller_test.rb
create app/helpers/sessions_helper.rb
create app/helpers/users_helper.rb
create test/unit/user_test.rb
create test/fixtures/users.yml
create app/views/sessions/new.rhtml
create app/views/users/new.rhtml
create db/migrate
create db/migrate/001_create_users.rb
/>
As the plugin clearly suggests I added the following code to my routes.rb file in /config
map.resources :users
map.resource :session, :controller => 'sessions'
map.signup '/signup', :controller => 'users', :action => 'new'
map.login '/login', :controller => 'sessions', :action => 'new'
map.logout '/logout', :controller => 'sessions', :action => 'destroy'
Ok now we have basic authentication and some tests to prove that. We could go ahead and add out changes to the User model but first let's run the migration in development and make sure that all the tests that the plugin generator created pass.
If you haven't done it yet, go ahead and configure the database and create the mysql instance for tempo_development
Running our first migration for Tempo
>rake db:migrate
/> rake db:migrate
(in /Users/bsbodden/Documents/projects/tempo)
== CreateUsers: migrating =====================================================
-- create_table("users", {:force=>true})
-> 0.0591s
== CreateUsers: migrated (0.0593s) ============================================
Running the existing tests
/> rake
(in /Users/bsbodden/Documents/projects/tempo)
Started
..............
Finished in 0.219704 seconds.
14 tests, 26 assertions, 0 failures, 0 errors
Yay! You can test the app (assuming that you've started the server) by pointing your browser to http://localhost:port /signup /login /logout
Now the real work starts, in the next installment of this series we will tackle the development of the domain using DDD (Domain Driven Development) and TDD (Test Driven Development).
Posted in Ruby, Rails | 1 comment | no trackbacks