Tuesday, February 28, 2012

Rails 3 routes and routing constraints with optional parameters

    I was recently faced with a problem where I needed to develop a routing system for a Rails application that handled human and SEO friendly urls.  This article will take you through the various stages of my journey to the final solution.  Just to forewarn you, I don't purport to be a routing expert, and I don't claim there isn't a more elegant solution for this problem set.  But since I was unable to find much help on the interwebs, I thought maybe a person or two might find this useful.
 
   Say, for example, you are tasked with developing a type of taxonomic application that would allow users to navigate a path of increasing levels of specificity (we'll only use 4 of the 7 levels).  Easy enough you say; just define a route for each level your app needs to handle.

get "/:kingdom" => "the_controller#classes"
get "/:kingdom/:class/:family" => "the_controller#species"
get "/:kingdom/:class/:family/:species" => "the_controller#details"

(Don't whine at me because I'm not using RESTful routes here; this is just a simple example.)

So an url such as /animals/mammals/dogs/wolf would be handled by our defined routes (matches the last one).  Ok, so what's the problem?  Well what if one of the requirements of the application was to allow users to SKIP one of these levels.  For example, what if the user is presented with a list of animal classes along with their associated families, AND they are allowed to select either the class or the family to see an associated list of species.
  • Mammals
    • Cats
    • Dogs
    So if they select "Cats", then the associated link would be /animal/mammals/cats and they are taken to the species page.  Well, what if they select "Mammals" and expect to be taken to the species page?  Do we have a route to handle that?  /animals/mammals just takes us back to where we just were.  Could we add another route like so:  /:kingdom/:class/:species?  Well how would Rails differentiate between :species and :family since two routes would have those parameters in the same location in their structure.

    You might be thinking at this stage; well, if :family is optional, let's define it that way: /:kingdom/:class(/:family)/:species But as any decent programmer could tell you, optional parameters only work as expected when placed AT THE END of an arguments list.

     So what do you do?  Static routes that don't map parameters aren't very useful.  You want Rails routing to do it's magic and not have to parse request urls (ewww).  So enter one of the uses of routing CONSTRAINTS.  What constraints allow us to do here is sort of fudge on the rule about optional parameters.  Our final routing solution might look something like this:

get "/:kingdom" => "the_controller#classes"
get "/:kingdom/:class(/:family)" => "the_controller#species", :constraints=>{:family=>Family.all.map(&:name).join('|')}
get "/:kingdom/:class(/:family)/:species" => "the_controller#details", :constraints=>{:family=>Family.all.map(&:name).join('|')}

    Let me explain what this accomplishes.  First you'll notice that :family has now become optional.  But wait, that can't work!  Well what the conditional does is define what :family can match against. (All the extra stuff in there is just pulling together a list of Family instance names into a regular expression.)  Now if an url segment exists in the location defined by :family AND it matches the regexp we defined, it will be recognized as a family.  However, if that segment does NOT match the regexp, routing will say: "Hey, this isn't a family, it must be a species."

Please note: The order in which your routes are defined does matter.  For example. if you swapped the placement of the inner-two routes with each other, then any routes with two segments would always go to the species action.  Let's see if you can tell me why...


Friday, August 14, 2009

Lightbox for Rails

   There are plugins available that will create a lightbox effect for you, but in each case I ran into problems adapting the plugin to my needs. I'm going to share with you the steps I took to introduce my own lightbox effect that has proven to be extremely flexible and easy to use.

Gray Out
   The first thing we need is the gray-out effect. I did a little searching and found a javascript function, grayOut, that has worked out really nicely. The code has some useful comments for explaining the various options it accepts, so I won't go into the details.
   Paste this code into your application.js file to ensure it will be available by default in all your layouts (assuming you are using the javascript_tag :defaults option)

HTML/CSS
   The next thing we need is an HTML container to hold the lightbox and some CSS to style it so it should show up center screen. Here are the steps:
  1. somewhere inside the body tag add an empty div tag with class "lightbox" and id "lightboxContainer and the style set to "display:none" inline.
  2. in default.css add the lightbox class: .lightbox{position:absolute; z-index:1000; width: 100%; margin: 0 auto; top: 50px}. The top should be adjusted to whatever fits your site template best.
Rails
   Finally we write some rjs code to display content in a lightbox. I like to use helpers for commonly used rjs code. This has proven really useful in keeping the controllers DRY. In whatever helper you wish (since helpers are global) create the following method:

def display_lightbox(partial)
  page << "grayOut(true, {'z-index':'25','opacity':'50'});"
  page['lightboxContainer'].show
  page['lightboxContainer'].replace_html(render(:partial=>partial))
end

Now in your controller to display a lightbox in response to a js request (ajax), you only need a single line of code inside your render block to display the lightbox. Example:

respond_to do |format|
  format.js do
    render :update do |page|
      page.display_lightbox("/[controller_views_directory]/some_partial")
    end
  end
end

And that's all there is to it. I went a couple steps further in my project to allow stacking n number of lightboxes that are managed by a javascript class. If I get enough requests I may post that as well.

Inherit Filters, Don't Inundate Your Controllers With Them

Filters are a great way to keep your controllers DRY, but to fully optimize their use a developer should take advantage of controller inheritance (filters are inherited as well as the actions).

I happen to be a big believer in nested controllers. We tend to use the small controller, large model approach in our Rails structure. This philosophy naturally results in numerous controllers. I also think a nested structure is especially relevant in a site that has many Ajax functions sprinkled throughout every page.

Since nested controllers tend to represent a subset of functionality provided by the higher level controller, it makes sense to inherit from this controller (Note: ALL controllers by default inherit from the application controller. When creating a nested controller, the namespace will reflect the parent, but you must manually change the controller from which it inherits).

It's common practice to implement filters on the Application controller that apply to the entire site (ie an authentication filter). However, specialized filters such as functionality specific permissions or state checking should not be applied to the Application controller. Otherwise, you will end up with skip_before_filters all over the place. This is poor practice.
So here's a simple example of a good way to use nested controllers in conjunction with filters:

class ClientController < ApplicationController

before_filter :verify_client_permission

def view_client_information
//some code
end

end

class Client::EditClientController < ClientController
//inherits filter from ClientController
end

Of course this is an extremely simple example, but I think it gets the point across. As with everything in Rails, there are probably numerous ways to accomplish the same idea. This is the method I have found that works well in pretty large Rails projects with a complex permissions model and lots of Ajax.

Sunday, August 9, 2009

Keep Your Rails Controllers Clean

One of the things I really love about Ruby on Rails is that, for every discovery, there seem to be two more waiting on the horizon; better, neater, faster ways of doing things. I, as programmer, am prone to becoming sidetracked by trying out newly discovered methods, and sometimes I lose site of some basic software principles. One of the most basic of these is "separation of concerns" (SoC).

I have found that the extreme accessibility of Rails models allowed me to pick up some bad habits: namely just grabbing models willy-nilly and having my way with them in my controllers. Although Rails implements MVC, it can only do so much to encourage good coding practices, and the rest is up to you. Here are a few lessons I've learned about keeping my controllers clean:

1) Stop playing with your parameters: Formatted user input is extremely common, and there are several approaches to dealing with the validation, character stripping, and reformatting required to support it. I'm ashamed to admit that the last project I worked on "gsubs" were splattered everywhere in the controller actions in order to remove formatting characters. First, assuming that formatting characters were input correctly by the user is bad bad. And this is essentially what's happening when validation occurs after character stripping. For example, say we have a phone number that was typed in as 12345-67890. Now, if we strip the '-' character and validate numericality and length, whoila, we have a valid phone number. WRONG! How do we know if it's supposed to be 123-456-7890 OR 1-234-567-890 and they forgot a digit? The answer is, we don't, so don't assume. There is a world of ready to use regular expressions out there (lately I've been referencing Alexey's Blog). Make use of them in conjunction with ActiveRecord's validates_format_of method.

OK, now we know that we can validate our user's input with formatted characters and all. We know what our user's intentions are, but now we still have to get rid of those pesky formatting characters before storage. Well, we could create a method for the model and call it from the controller (ugh), or, the cleaner approach is to add a method to the model's ActiveRecord callback before_save method: something like "before_save :remove_those_pesky_characters".

The result of implementing the above procedures is we don't have to do any parameter manipulation inside our actions to deal with formatting characters. Data manipulation and validation is handled in the model where it belongs.

The next part in this series I will talk about rolling up model dependencies using the ActiveRecord after_create callback.