dangerousdave
dangerousdave

Reputation: 6408

Rails restful routes and singular resources

I am having a problem with restful routes and singular resources, originally I had this code in the show view on my account resource.

<%= link_to book.title, book_path(:search => book.title) %>

and it worked fine, then I changed account to be a singular resource, e.g

from

map.resources :accounts

to

map.resource :account

and now I get the error ...

book_url failed to generate from {:search=>"Dracula", :controller=>"books", :action=>"show"}, expected: {:controller=>"books", :action=>"show"}, diff: {:search=>"Dracula"}

remove the view line of code, and everything works fine. Also changing it to

<%= link_to book.title, :controller => "books", :action => "show", :search => book.title %>

makes it work.

I have created a standalone rails application demonstrate my problem in isolation http://github.com/jowls/singular_resource_bug

Is this a bug? brought about through some combination of singular resources and restful routes?

This was on rails 2.3.10

thanks

Upvotes: 3

Views: 1049

Answers (1)

David Demaree
David Demaree

Reputation: 385

The account singleton resource is a red herring. Your problem is just that you're missing the :id parameter of books_url (which is always the first argument). You can pass in additional arguments like so:

book_url(book, :search => 'Dracula') # book.id would also work here

But there must be a book. That's just how Rails's resource routes work.

It's strange if this worked before, because you should have been getting this error on your books resource the whole time.

An easy way to refactor your code to work around this would be to add a line to your controller's show action:

class BooksController < ActionController::Base

  def show
    # If :search is blank, populate it with :id's value
    params[:search] ||= params[:id]

    # your code ...
  end

end

Then just pass the search/title string in as the first argument to book_url/book_path:

# "Dracula" will be passed to your controller as params[:id]
book_path("Dracula")

Alternatively you can override the default/generated route like this:

map.resources :books

# Explicit named routes take precedence over generated ones
map.book '/books/:search', :controller => 'books', :action => 'show'

One caution against the latter idea is that it prevents you from using an :id parameter instead—it makes :search the default REST identifier, so if you tried using :id you'd have your original error, just with a different param.

I've tested these solutions in the example app you posted and they all seem to work.

Upvotes: 3

Related Questions