Archive

Tag Archives: routes

Well, this has been a pain. I have a case where I need to have the ID of a resource be a string, not an integer. Rails routes don’t like that — if you search a bit, you’ll find a number of arguments about the practice, with the Rails core folks coming down on the side of integer-only IDs. Shame that, and people have come up with various workarounds. For me, I ended up doing this:

  map.resources :workers
  map.inventor 'worker/:id', :controller => 'workers', :action => 'show'

  # In the view, I can then do this, which isn't pretty but is temporary, okay?
  link_to worker_name, worker_path("#{CGI::escape(worker_name)}")

I have my nice RESTful routes created for workers, which is nice for the usual index, update, destroy, etc which I can do using IDs most of the time. But for show, I need to be able to pass in a name rather than an integer. That’s where the unfortunate additional named route comes in, so I can have a URL like /worker/your_name_here. It’s human-readable and SEO-friendly, plus for various reasons I’m working with String keys instead of integers in this case (using Redis as the data store).

So that worked…except that I discovered some cases where the strings contained periods. Which is another Rails no-no! Sigh. So, I could muck around with additional special cases in my routes file, which is not nice. Thankfully, though, my searching stumbled on a couple of bugs filed in the Rails core, most particularly this one, which provided a workaround that’s not too awful. I changed my route to the following:

  map.inventor 'worker/:id', :requirements => { :id => /.*/ }, :controller => 'workers', :action => 'show'

Et voila, there we go. Adding the requirements parameter instructs Rails to allow periods, and it works. But I still hate routes.

Having gotten some sample reports working using RaphaelJS (see previous post), I needed to provide a CSV export as well. I’ve done this before, but I had a feeling that there might be an easier way now, and sure enough I found the csv_builder plugin. Built on top of the old reliable FasterCSV gem, the plugin provides CSV exporting using templates, so you can simply add a respond_to in your controller for the “csv” format, and you’re done.

Of course, it’s never quite that easy. The issues I had were mainly around working out a nice way to set up the routes and getting things to respond correctly. I’m still not entirely happy with the results yet, but they’re not bad so I’ll share the info here.

First, the routes. I ended up needing two routes, unfortunately, one that includes the :format designation and one that doesn’t. See this blog post for more.

  map.connect 'reports/:action.:format', :controller => 'reports'
  map.connect 'reports/:action', :controller => 'reports'

This allows the use of links like “/reports/monthly” to get the HTML output (with the RaphaelJS graphs) and “/reports/monthly.csv” to get the CSV export. In the controller, then:

  def monthly
    # gather up the data
    respond_to do |format|
      format.html {}
      format.csv do
        @output_encoding = 'UTF-16'
        @filename = "Monthly_Report"
      end
    end
  end

That takes care of handling both the HTML and the CSV formats, which are automatically farmed out to the appropriate templates: /app/views/reports/monthly.html.erb for the HTML, and /app/views/reports/monthly.csv.csvbuilder for the CSV. It also sets a couple of parameters for csv_builder, including the encoding (UTF-16 seems to make the file slightly more Excel-friendly) and the filename which will be delivered to the browser as the default for downloading. Other parameters are available — see the plugin’s README for more.

In the CSV template, it’s a simple matter of building the rows of the file:

csv << [ 'Month', 'Totals' ]
@monthly.each_with_index do |month_value, i|
    csv << [ @month_names[i], month_value ]
end

This is using the structure assembled by the controller, which fills @month_names with the names of the 12 months, and @monthly with the numbers for each month. The first line creates the header row with the field names, and then the code simply iterates through the @monthly array. The resulting CSV file has the header row, and 12 rows following, one for each month.

This is obviously a very simple CSV, which was the idea so I could demonstrate that the CSV export is working. On my report page, I ended up creating simple links to get the three forms of report that I'm providing: bar chart, pie chart, and CSV export:

<%= link_to('Bar Chart', :controller => "reports", :action => "monthly", :chart => 'bar') %> |
<%= link_to('Pie Chart', :controller => "reports", :action => "monthly", :chart => 'pie') %> |
<%= link_to('Export CSV', :controller => "reports", :action => "monthly", :format => 'csv') %>

As you can see, I can use params[:chart] to determine which chart type to display, and :format is automatically used to delegate to the csv_builder template.

I'm pretty pleased with how simple this is to work with, once the basics were figured out. The template is easy to work with, and once I set up the data in the controller's action method, everything just happens like it should.

I want to give a quick link to this blog post, which had some helpful hints on this as well.

Follow

Get every new post delivered to your Inbox.