Dynamic Named Routes for Semi-Static Pages in Rails

When I was designing the new UMSwing website, I had a few issues that, at the time, I didn’t have a clean method of implementing. One of those was the creation of semi-static pages. After watching this episode of Railscasts, I had a pretty good idea of how to implement them. The only issue with the solution offered was the lack of dynamically generated routes.

Semi-static pages are used everywhere on websites. They’re those pages like an “About” page, which has content on it that doesn’t really change that often. Typically, a controller would have to house these actions (/about, /faq, /contact, etc.), and  the routes specified manually. Railscasts came up with an ingenious idea to create a controller which was routed to /static/*, so that semi-static pages could be created on-the-fly and modified easily. It also allows for modifications to change without committing to a repository and going through the process of deploying all over again.

For those needing a quick Rails primer before going on, here’s the quick and dirty of what you need to know to understand this:

  • Rails is a MVC-based web application framework that runs on Ruby. In short, Ruby code is written to create webpages on-the-fly.
  • Every request in Rails is first put through the routes file in config/routes.rb. This file tells Rails which Controller and Action is run.

Okay, let’s get started. Let’s create our static pages scaffold (which includes model, views, and the controller). Obviously, there are sections of this that you would want to require authentication for (editing and deleting, for example), but that’s outside the scope of this tutorial.

script/generate scaffold pages title:string permalink:string content:text;
rake db:migrate

Now we need to modify our controller slightly. More specifically, our show action. Right now, it will respond to showing an element only when the ID is displayed. We want to modify it to handle a permalink as well (/about and /contact look better than /pages/135, don’t you think?). Here is your modified show action:

def show
  if params[:permalink]
    @page = Page.find_by_permalink(params[:permalink])
  else
    @page = Page.find(params[:id])
  end
end

Before we go any further, we need to create two custom methods in our model. These will format the permalink to remove any unwanted characters for the custom route name (replacing all unacceptable characters with an underscore) and for the URL (replacing all unacceptable characters with a forward slash to allow for nesting of pages). It’s also important to note here that previous validation should be done to ensure that the permalink does not have leading or tailing non-alphanumeric characters, but I removed that for simplicity’s sake.

class Page < ActiveRecord::Base
  def route_name
    p = self.permalink.gsub(/([^A-Za-z0-9])+/, '_').downcase # Change non-alphanumeric characters to an underscore
    "static_#{p}"
  end

  def uri
    self.permalink.gsub(/([^A-Za-z0-9])+/, '/').downcase # Change non-alphanumeric characters to a forward slash
  end
end

At this point, we can create and modify our pages as we would regularly expect from a new controller. All of our pages are accessible via /pages/1, /pages/2 etc. We now need to make our controller act as our catch-all (so that all requests that do not match any of the other controllers get routed to our Pages controller), and we also need to provide permalink support. Finally, we will dynamically generate customized, name routes for all of our semi-static pages. All of that gets accomplished with a few short lines of code. Add the following code to the top of your config/routes.rb file, starting at line 2 (inside the ActionController::Routing::Routes.draw section):

def map.static_page_actions
  pages = Page.find(:all)
  pages.each do |page|
    self.send("static_#{page.route_name}", "#{page.uri}", :controller => "Pages", :action => "show", :permalink => page.permalink)
  end
end

Finally, we need to call this method close to the bottom of the code, right before our default catch-all routes.

map.static_page_actions
map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'

What this method does is retrieve all of the static pages in the database, then creates a customized, named route for each page, telling Rails what each URI should look like, and where to direct the request to.

Hopefully this helps some people out with their dynamic page creation. I’m pretty sure there’s a pitfall or two here, but I think it could be taken care of by doing some simple route housecleaning in the Pages CRUD controller. The perk of this option is that it allows the routes to be named, and hopefully that is of some benefit for others.

I’m Back

Well, it’s been a while since I’ve posted; about three weeks, actually. To the one or two readers I have, my apologies that you don’t have something to waste your time on twice per week. I’m getting back into the writing mood, so I should be building up a buffer of things to write in the near future.

A lot has happened since I last talked about the IPAM presentation that I took part in. To start with the related topic, I was approached to do the presentation again, this time internally to other departments. Thus, the other co-op student and I set about cleaning up the presentation a bit, fixing some errors, and making it flow smoother. It went much better the second time, thankfully, both from a public speaking perspective and a demonstration perspective. As fun as it was to work on that, I’m glad it’s over and done with right now.

Speaking of work, the number of days that I have left at IPC are dwindling quickly as the new year approaches. I work until December 31st, at which point I’m back in class. It’s been a fun past couple of months, and the paychecks have been very nice, but I’m also looking forward to getting back on campus to get some more studying done. I’ve decided that I won’t get a job during the winter semester so I can concentrate on my studying; I’ll have more than enough money to get through four months, and then I’ll be working in the summer again.

After that presentation was done with at work, I found that I had a fair amount of spare time, as there weren’t too many tasks to work on. I spent that time learning Ruby on Rails, and putting that knowledge towards the new UMSwing site. Although on the outside it will look almost the same as before, this new site will have an extensive backend that will make UMSwing virtually paperless. Although you may not think we use that much paper, think again; I have a full 3″ 3-ring binder in our office that says otherwise. All of our memberships, attendance, and transactions will be tracked on the web application, thus eliminating the need for those pieces of paper to be printed in the first place. Anyways, I’ve been working very hard on the site, and it’s almost ready to be tested by some other people. So, if you’re interested in testing some software for an eco-friendly cause, let me know in the comments section and I’ll keep you informed.

That’s a quick update on what’s happened in the past few weeks at work. I have a few more updates to spew out in the coming days, one of them involving my server upgrade (*cough* RAID *cough*), and some involving some extra-curricular activities (including some new photos to go up soon).

From Paperwork to Web 2.0: UMSwing’s New Membership System

Nowadays, my life has a good amount of its time consumed with either work or swing dancing. I work every weekday, and four nights every week I’m dancing. Being the nerd that I am, I always look for opportunities to intertwine my hobbies, despite them being complete opposites. Being on the executive committee helps a lot with that, since I take the position of Web Administrator and Graphics Designer with UMSwing.

On the way home from an event a couple weeks ago, I was talking with a friend about the hassle of all the paperwork we have to go through every time we have a lesson; we need to fill out transaction logs for each payment, keep track of every person’s attendance for each class, and also mark it on their membership form that they attended and paid for that class. A single person dropping in to that class requires writing on three sheets of paper. When you’re trying to run everybody through quickly, that starts becoming an issue.

This friend, being the kind of person that seems to regurgitate good ideas on demand, suggested to me, “Brian, you’re a developer. Just write a program to do it for you. You’re learning Ruby and Rails, so you can do a web-based backend and a GUI frontend. Problem solved!”. Thus, I sat down and started planning. Rails seems to be yet another one of those languages that lacks any decent documentation or tutorials. If you plan on learning it, pick up “Agile Web Development With Rails“. It is by far the best development book I have ever read. If it’s any sort of selling point, one of the authors created the Rails framework; if he doesn’t know how to use the framework, nobody does.

As a method for potentially helping me brainstorm, I’ve decided to spill out some of my ideas and goals here. I’m only going to discuss a few ideas here; while I would normally immediately distribute this idea into the public domain, I’ve decided to keep this one closed source. If you have any suggestions or ideas, let me know and I will give you credit. Better yet, if you’re interested in this software, get in touch and we can discuss it.

Goals for Dance Site

  • Members: Keep track of all members, regardless of how long ago they joined. Eliminate the need to fill out a new membership form every semester. Each member should be assigned a member number, which can be put on a barcode. Keep track of personal information, interests, and attendance. Gather statistics/metrics from attendance vs. month/day/semester, etc.
  • Memberships: Handle multiple membership types, including drop-in. Integrate with finances to determine when a user has paid for their membership through drop-ins. Support for online payments through Paypal (ie. Mastercard, Visa, eCheck, etc.)
  • Finances: handle per-lesson incomes. Support for multiple lessons per day. Keep track of what is taught during that lesson. Provide unlockable content for each lesson; attendance to that lesson unlocks the content for that member; refresher videos, class notes, etc. Support for discounted membership dates/times.
  • Graduated system: attendance of X number of events allows you to attend higher level classes. Ability to override by administrator.
  • Mailing List: Separate old members by current members, allowing for class updates to be sent to current members, while global events to be sent to all. Ability to unsubscribe.

Ruby Documentation Sucks

Okay, this is going up a day late. My bad. I’ve been busy. Regardless, I have a rant which any programmer can sympathize with.

I’ve been recently programming a proxy in the Ruby programming language, which is known for its code elegance. When you know how to use it, it’s a great language. The problem, however, comes when to learning about the API in the language. To put it bluntly, the documentation is crap. To be more specific, a good amount of it is incomplete, and those sections that are completed fail to follow a consistent fashion. To put things in perspective, there are 108 core libraries included in the Ruby documentation; over half of those libraries have incomplete documentation.

Now, this isn’t that much of an issue if you know how to use the language; after all, there’s no need to go to the documentation when you know the language. The problem comes when you are like me, learning how to use the language, and don’t know what any of the constants for the sockets library do, which is a bit of a problem when you need to program a proxy. See where I’m going with this?

Maybe I’m complaining because I’ve been spoiled on PHP‘s phenomenal documentation, which is an amazing feat when it comes to documentation. All of the functions are properly laid out with plenty of cross-references, and tell you exactly what to expect for each and every function. The documentation is a work of art, I kid you not. Don’t believe me? Try learning how to do something complex in PHP using the documentation only, then try to do the same in Ruby.

I have heard some people make the argument that Ruby is open source and relies on its members to do the documentation, hence the lack of it. While I understand this argument, it doesn’t entirely make sense. Ruby has a large band of dedicated followers (think Jehovah’s Witnesses-style) who should have filled in the 1.9 documentation by now. Thinking about it from another perspective, PHP is a free and open source language as well, and look at the detail in there compared to Ruby.

All I’m saying is that Ruby needs to step up its game a bit, otherwise it will have trouble competing for those people looking at learning a new language. If it wasn’t for an amazing IBM document on Ruby socket programming, I would have moved on to another language by now.

Anyways, tune in this Friday for something different. I realize programming isn’t everybody’s cup of tea, so I’m hoping to branch off into something a little different for those of you who either find computers boring, or those of you that simply don’t understand them. As always, I appreciate you reading, and I appreciate even more those of you who tell a friend about my blog :).