SpringOne Americas

Private Events

Blogs

View all Blogs >>
  • Andrew Glover

    Co-author of "Continuous Integration"

    more»

  • Brian Goetz

    Author of Java Concurrency in Practice

    more»

  • Scott Leberknight

    Chief Architect at Near Infinity

    In late 2006 Neal Ford wrote about Polyglot Programming and predicted more»

  • Stuart Halloway

    CEO of Relevance

    This article is part of a series describing a port of the samples from Practical Common Lisp more»

  • Michael Nygard

    Agile technology leader and dynamicist

    In my series on dirty data, I made the argument that sometimes incomplete, inaccurate, or inconsistent data was OK. In fact, not only is it... more»

  • Jared Richardson

    Agile coach and co-author of Ship It

    Last week in Washington, D.C. I was teaching a test automation class. The class ended up being composed of more»

  • Kenneth Kousen

    President of Kousen IT, Inc.

    In this entry in my “Making Swing Groovy” series, I want to talk about threading issues. Specifically, more»

  • Ryan Shriver

    Business and Technology Consulting

    more»

  • Alex Miller

    Sr. Engineer with Terracotta Inc.

    Stanley Ho announced today on the JSR 277 mailing more»

  • Mike Levin

    Software Developer specializing in Web2.0 websites

    more»

  • Richard Monson-Haefel

    VP of Developer Relations, Curl Inc.

    more»

  • Matt Raible

    Creator of AppFuse and author of Spring Live

    more»

  • Graeme Rocher

    Project Lead of the Grails Project & CTO of G2One

    The main portal for Sky television has relaunched written in Grails. Sky, also know more»

  • Jason Rudolph

    Author of Getting Started with Grails

    While working on the more»

  • Neal Ford

    Application Architect at ThoughtWorks, Inc.

    Way back in 1968, Edsger Dijkstra almost caused a riot at the ACM conference. His audacious crime? " more»

  • David Bock

    Principal Consultant, CodeSherpas Inc.

    I was driving to work this morning listening to all the doom and gloom on the radio, thinking to myself, "You know, I have survived a major... more»

  • Brian Pontarelli

    Brian Pontarelli - founder of Inversoft

    I might be smokin’ crack, but I think that todays (September 30th, 2008) Java update from Apple finally fixed the command-tab issue. I... more»

  • Pramod Sadalage

    Co-author of "Refactoring Databases:Evolutionary Database Development"

    Recently when our test databases where upgraded new version of Oracle, we started noticing that the order in which some drop down lists were... more»

  • Craig Walls

    Author of Spring in Action

    As you've no doubt heard and as I've already commented on elsewhere on this blog, SpringSource has enacted a new maintenance policy around... more»

  • Erik Doernenburg

    Principal Consultant @ Thoughtworks

    One of my favourite tools to render graphs is Gra phViz Dot and in an more»

  • Venkat Subramaniam

    Founder of Agile Developer, Inc.

    I wrote a four part article for Java World on creating DSLs in Java and Groovy. For your convenience, I decided to list the links to those... more»

  • Jason Harwig

    Senior Software Engineer at Near Infinity

    The most popular entry I've written at Near Infinity has been the more»

  • Nathaniel Schutta

    Author, speaker, software engineer focused on user interface design.

    I spent my formative years on a small hobby farm. In addition to witnessing first hand the whole circle of life thing, I learned just how... more»

  • Ted Neward

    Enterprise, Virtual Machine and Language Wonk

    One of the more interesting logistical problems faced by the people who run the Microsoft Conference Center is that several events are often... more»

  • Pratik Patel

    Enterprise Architect

     Every now and then I read challenges to Frederick Brooks' wisdom. Mr. Brooks is the au more»

  • John Heintz

    Principal Consultant with New Aspects of Software

    In a recent discussion interview questions came up, here's my favorite one.To set some context this question is designed to gauge the abst more»

  • Mark Johnson

    Director of Consulting at CGI

    At the Columbus NFJS show held on July 25-27th during one of the BOF sessions Dave Bock, Scott Davis and I discussed unit tests vs functional... more»

  • Joseph Nusairat

    Author of Beginning JBoss Seam & Co-Author of Beginning Groovy & Grails

    Well i am assuming Apress has the most random site in the world at times.But today only they have our recent book, Beginning Groovy & Grai more»

  • Keith Donald

    Lead of Spring Web and Creator of Spring Web Flow

    I am pleased to announce that Developing Rich Web Applications with Spring, a three-day bootcamp lead by SpringSource engineers on web... more»

  • Vladimir Vivien

    Software Engineer / Consultant

    Judging from the list of features that will be included in NetBeans 6.5, more»

  • Kirk Knoernschild

    Software Developer & Mentor

    I’ve published a summary of the OSGi survey results on the APS blog more»

  • Pete Behrens

    Organizational Agility Coach

    Marti nig & Associates Methods & Tools group recentl more»

  • Brian Sam-Bodden

    Java author, Ruby geek and Open Source Advocate

    In this installment we are going to build the Dashboard page of the Tempo application. T more»

  • Mark Fisher

    Spring Integration Lead

    In my recent post, I had mentio more»

  • Ron Bodkin

    Chief Software Architect, Quantcast

    I'm looking forward to speaking at The Rich Web Experience conference in San Jose next month. The event runs from September 7th through 9th.... more»

  • Mark Goodwin

    Web Application Security Specialist

    We've already looked at one of the two big problems posed by anti DNS pinning on Java applets; because there's rebinding on the applet and... more»

  • Scott Davis

    Author of "Groovy Recipes" & TDD Expert

    Every time I see a live show at the Denver Botanic more»

  • Romain Guy

    Java User Interface expert.

    more»

  • Ramnivas Laddad

    Author of AspectJ in Action, Principal at SpringSource

    InfoQ.com has published my AOP myths and realities talk recorded at a No Fluff Just Stuff conference. InfoQ.com founded by Floyd Marine more»

  • David Geary

    Author of Graphic Java and co-author of Core JSF

    The 2006 NFJS tour kicked off t more»

  • Jeff Brown

    G2One Vice President of Professional Services - Groovy and Grails Developer

    <a href="http://groovy.codehaus.org/"& gt;Groovy</a> 1.0 RC2 was released today. If all goes well over the next few days the 1.0... more»

  • Howard Lewis Ship

    Creator of Tapestry and HiveMind

    <p> Just got back from Europe on Friday, and have been recovering from a nasty cold and jet lag. This week I'm prepping for another... more»

  • Kito Mann

    Editor-in-chief of JSF Central and the author of JSF in Action

    Our current schedule for JSF 2.0 has us handing off the spec artifacts to the JCP on 15 December 2008. That's 62 business days from today. We... more»

  • Jason Hunter

    Author of Java Servlet Programming

    I just posted the JDOM 1.1 release for download. This release includes about 20 improvements and bug fixes. more»

In the Spotlight - Brian Sam-Bodden

Brian Sam-Bodden

Java author, Ruby geek and Open Source Advocate

Brian Sam-Bodden is an author and recognized international speaker that has spent over twelve years working with object technologies, with an emphasis on the Java platform and in recent times falling in love with Ruby. He holds dual bachelor degrees from Ohio Wesleyan University in computer science and physics and is the president and chief software architect for Integrallis http://www.integrallis.com, where he focuses on building great applications with Java and Ruby. Brian has worked as an architect, developer, mentor, and trainer for several Fortune 500 companies in the tax, insurance, retail sciences, telecommunications, distribution, banking, finance, aviation, and scientific data management industries. As an independent consultant, he has promoted the use of open source in the industry by educating his clients on the cost benefits and productivity gains they can achieve. He is a frequent speaker at user groups and conferences nationally and abroad. Brian is the author of "Beginning POJOs: Spring, Hibernate, JBoss and Tapestry" and has also co-authored the Apress Java title "Enterprise Java Development on a Budget: Leveraging Java Open Source Technologies".
















Presentations by Brian Sam-Bodden

Beginning Drools - Rule Engines in Java

Drools is an open source pure-Java implementation of a forward chaining rules engine. Drools can be used in a J2SE or J2EE application and allows you to express rules programatically or by building domain specific rule languages. Learn how Business Rules with Drools can make your Java applications more flexible and robust.


"

Beginning Object-Relational Mapping with Hibernate

Hibernate is an open source Object-Relational Mapping Framework that mostly automates the tedious and time-consuming task of persisting Java objects to a relational database.
Hibernate is quickly becoming the preferred way for enterprise developers to overcome the object-relational impedance mismatch and a good alternative to the coarse-grained Entity EJBs, low-level raw JDBC, and by-committee specifications like JDO. Learn what your choices in the ORM arena, what to look for in an ORM tool, and how to get started with Hibernate for your next J2SE or J2EE project."

Professional Java UI development with the Eclipse RPC

Learn how to build featured rich applications using the Eclipse Rich Client Platform. The Eclipse platform is an open tools platform, on top of this platform you can build your own applications (which do not need to be IDE like or IDE related). Yet you can enjoy the benefits of working with a mature and featured rich platform that can greatly reduce the amount of time required to create a professional-looking and robust Java UI application.
"

10 ways to use Hibernate effectively

Learn 10 tried and true ways to improve the way you use Hibernate today. In this session you would learn about a collection of 10 tips, tricks, practices and tools that will make you more effective at designing, implementing, testing and tuning your application's Hibernate-powered object-relational layer."

Advanced Rules Programming with Drools

In this session you'll learn some of the more advanced features of Drools; a pure-Java Rule Engine. This session will walk through the construction of an advanced Rules application covering such topics as:

- Fine control and monitoring of a Working Memory session
- Using Decision Tables
- Advanced Rule Language Features
- Building Domain Specific Languages
- Managing your Rules"

Boosting Programmer productivity with Mylyn

Mylyn is a task-focused toolkit for the Eclipse IDE that allows developers to focus on tasks in a way that they never been able to do before. Mylyn eliminates the constant context switching produced by typical ways IDEs are used. No more scrolling/browsing/searching/tagging/sending emails with progress updates... Mylyn provides a new way of working that allows you to focus on specific tasks by reducing information overload. Mylyn also provides a framework for integrating with the most commonly usage task tracking systems and version control systems.
In this talk you'll learn how Mylyn can boost your productivity as a Java developer by letting you get the most out of your IDE."

Improving Java with JRuby

Learn how JRuby can bring simplicity to the complex and rich APIs available in the Java platform. In this session you'll learn how to use JRuby to tackle some common tasks in Java SE and Java EE as well as how to abstract and simplify complex APIs. "

Meta-programming in Groovy

This session explores some of the programming techniques that a powerful dynamic language enables, in particular meta-programming or the art of writing code that writes code. Meta-programming techniques are being used extensively in many successful frameworks based on dynamic languages such as Rails, Grails and countless others. Learn how you can use meta-programming in Groovy to improve and streamline your Java applications."



Books by Brian Sam-Bodden

by Brian Sam-Bodden

  • Beginning POJOs takes you through the construction of a complex enterprise Java application centered around JBoss, Spring, Hibernate, Tapestry, Ant, and other supporting tools for development and testing. This book is ideal if you’re new to Open Source Java, and want to build enterprise Java applications from scratch, using the full range of available Open Source tools and frameworks.

    This book features the most successful and prevalent Open Source tools, along with some lightweight frameworks and tools. You’ll learn how to build a complete enterprise application, how to integrate the different Open Source frameworks to achieve this goal, and techniques for rapidly developing such applications.
  • Available At: http://www.apress.com/book/bookDisplay.html?bID=10059

by Brian Sam-Bodden, Christopher Judd

  • Open source has had a profound effect on the Java community. Many Java open source projects have even become de-facto standards. The principal purpose of Enterprise Java Development on a Budget is to guide you through the development of a real enterprise Java application using nothing but open source Java tools, projects, and frameworks.

    This book is organized by activities and by particular open source projects that can help you take on the challenges of building the different tiers of your applications. The authors also present a realistic example application that covers most areas of enterprise application development. You'll find information on how to use and configure JBoss, Ant, XDoclet, Struts, ArgoUML, OJB, Hibernate, JUnit, SWT/JFace, and others. Not only will you learn how to use each individual tool, but you'll also understand how to use them in synergy to create robust enterprise Java applications within your budget.

    Enterprise Java Development on a Budget combines coverage of best practices with information on the right open source Java tools and technologies, all of which will help support your Java development budget and goals.
  • Available At: http://apress.com/book/bookDisplay.html?bID=197




The Integrallis Blog :
Musings on Ruby, Java and programming in general


Brian Sam-Bodden's complete blog can be found at: http://www.integrallis.com/blog/

Monday, January 14, 2008

Rails

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 Savila. 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:

tempo dashboard sketch

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:

  1. The Dashboard page can only be shown if there is a user logged in. (done, taken care by the restful authentication plug-in)
  2. 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
  3. The Daily Work table should show all TimeEntries for the current user for the selected date
  4. 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:

tempo dashboard sketch

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:

tempo dashboard sketch

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:

tempo dashboard sketch

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.

tempo dashboard

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.


Thursday, December 6, 2007

Rails

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
      # save the original end time
      original_end = self.end 
      # first change the current TimeEntry to the end_of_day of the start day
      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
      # save the original end time
      original_end = self.end 
      # first change the current TimeEntry to the end_of_day of the start day
      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).


Thursday, November 29, 2007

Rails

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 

  #skip_before_filter :login_required

  active_scaffold :user do |conf|
    conf.actions.exclude :show
    # List
    conf.list.columns = [:person, :login, :email] 
    # Create
    conf.create.columns = [:person, :login, :email, :password, :password_confirmation]
    # Update
    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>)&nbsp;
      <% else -%>
      <a href="/login" title="Login">Log-in</a>&nbsp;
      <% 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">&nbsp;</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.

# Methods added to this helper will be available to all templates in the application.
module ApplicationHelper
  # http://dev.rubyonrails.org/ticket/2382
  # current_page? does not respect alternative routes
  def nav_link_to(