Archive for the ‘ Rails ’ Category

Handling Holidays and Business Hours

It seems I’m always apologizing for not writing blog posts more often. I looked back and see that the last entry was back in July. Things have been tremendously busy to say the least.

I ran into a quick requirement the other day for NDstorefront’s new admin application. An important metric to track is the time between order placement and fulfillment. Pretty straightforward requirement, right? Of course upon further inspection, it became clear that in order to have meaningful data on these times between order placement and shipping of the order, we’d need to take into account business hours. For example, if an order is placed on a Saturday night and fulfilled by the distributor on the following Monday, it’s effectively same-day fulfillment, as Monday is the first business day following the order date.

To that end, I located a pretty useful gem called business_time that handles this quite nicely. It’s an extension to ActiveSupport that adds some nice extensions to Time and Date to say things like:

4.business_days.from_now

You can configure the business hours as well to properly manage times outside of operating hours like so:

BusinessTime::Config.beginning_of_workday = "8:30 am"
BusinessTime::Config.end_of_workday = "5:00 pm"

I won’t go into the details of implementation as the documentation is quite good. This of course is only half a solution as it really only accounts for weekdays versus weekends as workdays. There is holiday support in the gem, but you must configure the list of holidays. I really didn’t want to be in the business of configuring and maintaining a set of holidays for each year. As well, there are all sorts of special cases as to when statutory holidays are observed as was the case in our last Christmas season. December 25th fell on a Sunday in 2011 and here in Ontario, that effectively meant Monday and Tuesday were stat holidays (with the observation of Boxing Day being shifted one day because Christmas was shifted as well).

Seems like a possible nightmare. Enter the holidays gem.

This is really well written gem by a fellow Canadian Alex Dunae out of Vancouver (Island). He’s tackled the non-trivial task of determining holidays for particular locales, including handling tricky cases like the one mentioned above, Easter (which is really tricky because it can fall in different months) and whether you want the actual holiday dates or the observed dates.

For my purpose, I needed to fork the gem and add some functionality to handle some dates for Ontario in a different manner. In practice, New Year’s Day is observed on a Monday when falling on a weekend as it is granted as a statutory holiday. The gem also didn’t quite have the support for the case where Boxing Day falls on a Monday , but needs to be shifted to Tuesday to make way for Christmas Day. Fortunately, modifying it was a breeze. Alex has used YAML files to describe the rules that in turn build the code required via Rake task.

With a modified version of the gem in hand, I was now able to feed the holidays into business_time. I worked on the assumption that we would be deploying a new app within 2 years and only needed to project that far forward with holidays:

Holidays.between(Date.civil(2011, 1, 1), 2.years.from_now, :ca_on, :observed).map{|holiday| BusinessTime::Config.holidays << holiday[:date]}

And that was it. I was able to look back on some recent data we had collected on shipments by our distributor and get an accurate reflection just how long it was actually taking to fulfill orders.

Advertisements

Customizing ActiveAdmin

Has it really been 3 months since I last wrote a blog post? Embarrassingly: yes. Things have been pretty busy and there’s been lots learned over that time.

I thought I would write a quick post about customizing the ActiveAdmin gem. I first saw ActiveAdmin at RailsConf and thought that it was a pretty cool idea to simplify the tedious task of creating CRUD interfaces for administrative tasks. It’s something you have to do in virtually every project, but is a pretty mundane exercise of repeating virtually the same code for each model you need to manage.

I’m not going to go into detail on setting up the gem for initial use because I think they’ve done an excellent job of it, but I will share some code samples that go above and beyond what appears in the documentation. One of the pitfalls of tools like these is that often the easy stuff is easy, but anything that requires customization is not. Happily, I’ve found this to be untrue with ActiveAdmin.

Adding a Custom Action

ActiveAdmin is DSL-driven and they’ve anticipated that users will want to add to the controllers that are automatically generated under the Admin namespace. In my example, I am using ActiveAdmin to list all of my application users. I wanted to add the ability for the administrator to be able to impersonate or sign in as a particular user.

My basic registration of the User model in admin/users.rb looks like this:

ActiveAdmin.register User do
  filter :username
  filter :email

  index do
    column "Username", :sortable => :username do |user|
      link_to user.username, schedules_path(user)
    end
    column :email
    column :user_type
    column :current_sign_in_at
  end
end

To add the impersonate action, there is a single DSL block that needs to be added:

  member_action :impersonate do
    sign_in(:user, User.find_by_username(params[:id]))
    redirect_to root_path
  end

Adding the member_action call to the registration definition will add both the appropriate controller action and route. Adding the new link to support the impersonate action is as simple as referencing the new named route:

  index do
    column "Username", :sortable => :username do |user|
      link_to user.username, schedules_path(user)
    end
    column :email
    column :user_type
    column :current_sign_in_at
    column "Action" do |user|
      link_to 'Impersonate', impersonate_admin_user_path(user)
    end
  end

Of course, the corresponding collection_action call is available as well.

Removing the New Button

Out of the box, one of the default actions is to provide a ‘New :model’ button. In my example, managing Devise users manually was not necessary and I wanted to remove this button from the layout. It’s easily handled by adding the actions keyword to the registration block.

I specifically only wanted to show an index and added this to the block:

  actions :index

Huh. Really? That simple.

Default Sort Order for Resource

If you’d like to set a default sort order for your resource index, it’s quite simple as well. On registration, add an options hash:

ActiveAdmin.register User, {:sort_order => :username} do
...

All in all, my first experience in using ActiveAdmin in a new project has been pretty worthwhile. As I tackle more sophisticated customizations, I’ll publish a few more posts about it. As well, there is a Google Group for addressing any technical questions about the gem.

Cartographer Plugin for Rails 3/Google Maps 3 support

Maps are integral part of any application with spatial data. In the past, I’ve utilized Google Maps interface with the YM4R plugin. It was always a little cumbersome in spots but did the trick. There was also the restriction of having to register a key for use in each API call.

But what if you wanted to take advantage of great version 3 features like Street View or Driving Directions? You’d be a little wanting as the YM4R plugin only supported V2 of the API.

Enter the cartographer plugin (https://github.com/joshuamiller/cartographer). This plugin provides both v2 and v3 API support, as well as clean Rails 3 integration. I feel the code is a little cleaner too. Here’s a brief tutorial of how to add it to your application.

Installation

Either install the plugin for via:

rails plugin install git://github.com/joshuamiller/cartographer.git

or

git clone git://github.com/joshuamiller/cartographer.git vendor/plugins/cartographer

Implementation

Next, you will need to add some basic code to your controller to create the map object. I prefer to create a helper to do this as it will be used frequently.

module GoogleMapsHelper  
  def map_init    
    @map=Cartographer::Gmap.new('map')    
    @map.zoom=:bound    
    @map.icons << Cartographer::Gicon.new  
  end
end

I typically add code to the model to be mapped to create the geomarker and info window.

class Site < ActiveRecord::Base  
  def geomarker    
    Cartographer::Gmarker.new(:name=> 'site', :marker_type => "Building", :position => ll)  
  end    

  def ll    
    Array.[]( location.y, location.x )  
  end  
end

From within the controller, call the initialization method and add a marker.

class HousesController < ApplicationController  
  include GoogleMapsHelper
  def index        
    if @site      
      map_init      
      @map.markers << @site.geomarker    
    end          
  # and more code...  
  end
end

Finally to your view, add the necessary code to render the map to a div with the specified id.

<div id='map'>  
  <%= raw Cartographer::Header.new.to_s %>    
  <%= raw @map.to_html %>  
</div>

And that’s it! Pretty simple and very clean in my estimation. I’m now not dreading the upgrade of my mappable applications to Google Maps v3.

exception_logger gem 0.1.8 supports jQuery

There’s an older but popular plugin called exception_logger that I’ve encountered in a few different Rails 2.x project. I found it a useful little tool for tracking uncaught exceptions in your production application, including views for managing them and supporting an RSS feed. The plugin fell into a state of disrepair and was showing signs of age. It would no longer run under Rails 3 due to changes to the way that exceptions are rendered in a production environment. As well, it used older Rails remote calls and prototype.js on it’s management page.

Enter Roland Guem (git user name QuBiT) who undertook the task of converting the plugin to a gem and made it Rails 3 compatible. The only issue left was that it meant you still had to include prototype support in your application. In an effort to try to contribute a little more to Open Source, I undertook my second project and converted the calls to unobstrusive jQuery. Needless to say, the code looks a lot cleaner and is easier to maintain going forward. With the release of the gem today (0.1.8), I’ve been able to upgrade a couple of production applications. I’m looking forward to tackling a little more in the Open Source domain as github really makes it a straight-forward process.

iCal support for Rails

I’ve been pretty busy this week making a ton of headway on a significant project. Said project has support for both events and contacts within it and it made a lot of sense to let users get these out seamlessly to other applications. I’ll write about the vCard support tomorrow and will focus on calendar support tonight.

I’d used the iCalendar gem before so it was only a matter of minutes to get it up and running with my application. In brief, here’s how I would suggest using it.

Add an ical method to the model that will be exporting events. You’ll need to require ‘icalendar’ in order to get access to that class within your model. Here’s an example of the mapping I did between my object and the Icalendar::Event.

def ical
  e=Icalendar::Event.new
  e.uid=self.uid    
  e.dtstart=DateTime.civil(self.date_start.year, self.date_start.month, self.date_start.day, self.date_start.hour, self.date_start.min)    
  e.dtend=DateTime.civil(self.date_end.year, self.date_end.month, self.date_end.day, self.date_end.hour, self.date_end.min)    
  e.summary=self.name    
  e.created=self.created_at    
  e.url=self.url    
  e.last_modified=self.updated_at    
  e   
end

Take notice of the UID field. You’ll want to ensure you have some sort of permanent UID identifying your event in the application so that subsequent exports (or RSS feeds) of the event will be updates.

You’ll want to handle the request to export the event in your controller. You could map it to the show method, but in my case, I added an export method.

def export
  @event=Event.where(:id => params[:id]).first
  respond_to do |format|
    format.ics
  end  
end

You’ll notice that the format is set to ics, the extension for a calendar file. You’ll need to make a modification to your config/initializers/mime_types.rb to add ICS support, like so:

Mime::Type.register_alias "text/calendar", :ics

Finally, in order to send the event to a calendar file, you’ll need add a view for the export method (in my case views/events/export.ics).

<% @calendar=Icalendar::Calendar.new %>
<% @calendar.add @event.ical %>
<% @calendar.publish %>
<%= @calendar.to_ical %>

That’s all there is to it – it’s really dead simple and should be an easy addition to any app. You can see how you might expand on this to share entire calendar feeds rather easily.

End before the start? validates_timeliness

It’s been a busy last few days working on a new project that has forced me to produce some of the best work (cleanest) work I’ve produced. Along the way, I’ve come across a few new gems that I’ve utilized to great effect. One of the things I love about Rails is that if you think of a problem, you can be assured it’s not unique to you. I was developing a model today that had a start and end date associated with it. Of course, I could written a custom validator to ensure to the end date was after the start date, but I realized I needed a few variation on this and decided to look for a gem. I found validates_timeliness, which fit the bill perfectly.

Installation and usage of the gem is as simple as advertised. In my case, I had some quick validations in place, such as:

validates_datetime :date_end, :after => :date_start, :if => :date_end

The gem provides a host of options, is Rails 3 ready and works on ActiveModel (meaning you aren’t restricted to ActiveRecord). I can see making good use of this in the future and wouldn’t be surprised if it made it’s way into Rails.

accepts_nested_attributes_for and _destroy without checkboxes

I’ve never been a fan of using check boxes on a form to mark child records for deletion. It seems a little counter-intuitive for most users. While I do use accepts_nested_attributes to mark items for deletion upon save, I don’t use checkboxes.

On my forms, I prefer to delete any list item or table rows that are to be destroyed (after prompting the user to confirm they want these deleted) and then figure out what needs to be deleted upon save. Take as an example Organizations that have many PhoneNumbers associated with them.

We update an organization object as follows:

@organization=Organization.where(:id => params[:id]).first    
@organization=params[:organization]

And then, before saving, perform the following for the nested attributes:

@organization.phone_numbers.each {|phone| phone.mark_for_destruction unless params[:organization][:phone_numbers_attributes] && params[:organization][:phone_numbers_attributes].detect {|k, v| v.detect {|fld, value| fld=="id" && value==phone.id.to_s}} }

This effectively marks for deletion any phone numbers which belong to the organization, but were no longer present on the form when submitted (because the user asked to remove them with confirmation). I find it makes for a cleaner interface. Two caveats to note in the inner detect call:

  1. Make sure you compare your ID field as a string because the params hash will represent the values as strings.
  2. You want to compare the field name to “id” and not :id as it is not a symbol in the incoming params hash, but a literal string.