views:

1733

answers:

7

The Background

I started a Rails project recently and decided to use RESTful controllers. I created controllers for my key entities (such as Country) and added index, new, edit, create, show, update, delete. I added my map.resources :country to my routes file and life was good.

After development progressed a little, I started to encounter problems... I sometimes needed extra actions in my controller. First there was the search action that returned the options for my fancy autocompleting search box. Then came the need to display the countries in two different ways in different places in the application (the data displayed was different too, so it wasn't just two views) - I added the index_full action. Then I wanted to show a country by name in the URL, not by id so I added the show_by_name action.

The Question

What do you do when you need actions beyond the standard index, new, edit, create, show, update, delete in a RESTful controller in Rails? Do I need to add (and maintain) manual routes in the routes.rb file (which is a pain), do they go in a different controller, do I become unRESTful or am I missing something fundamental?

Clarification

I guess I am asking, do I need to work harder and add actions into my routes.rb file for the privilege of being RESTful? If I wasn't using map.resources to add the REST goodies, the standard :controller/:action, :controller/:action/:id routes would handle pretty much everything automatically.

+7  A: 

If I go beyond the standard CRUD actions with my models, I normally just add the methods as required. Searching is something I add to many controllers, but not every one, so I add it and maintain the routes normally:

map.resources :events, :collection => { :search => :get }

Moving these actions to an entirely separate controller might keep some of your controllers RESTful, but I find that keeping them in context is far more useful.

Mr. Matt
I just hate having to add and maintain lots action names in my routes file manually. If I wasn't using REST then I wouldn't need to do it. Is there a cleaner way? If not then this it is.
RichH
How would you not need to handle route edits otherwise? By just using the ":controller => XXX, :action => YYY" syntax? Named routes are a good practice, whether or not you're using REST.
Micah
Search, index_full, show_by_name are all parameterized retrieve actions (i.e. the R in CRUD). There's no need to add new routes.
Dave Nolan
+5  A: 

REST does not specify that you can't have additional views. No real world application is going to be able use only the supplied actions; this is why you can add your own actions.

REST is about being able to make stateless calls to the server. Your search action is stateless each time as the data so far is supplied back, correct? Your alternate display action is also stateless, just a different view.

As to if they should be manual routes or a new controller, that depends on how distinct the activity is. Your alternate view, if it provides a full set of CRUD (create, read, update, delete) operations would do well to be in a new controller. If you only have an alternate view to the data, I would just add an alternate view action.

In other words, it doesn't sound like your application is failing to be RESTful, it is more an issue of realizing that the automatically generated feature set is a starting point, not a conclusion.

Godeke
I am being stateful, so I'm using REST properly. I guess my concern is that bacause I've decided to be RESTful I now need to do lots more work in my routes.rb file which I wouldn't need to do if I didn't use REST.
RichH
Well, the code has to go somewhere: I'm curious if you are feeling that having clearly defined actions is less useful than having extra flag data that says which view to use? While the flag is easy to code, it obscures what the action is doing quite a bit. The discoverable nature of actions is good.
Godeke
I'm 100% against the flag idea - no worries there. The problem is that once I add the map.resources line for the controller to add the REST mappings, my actions go from being automatically discoverable to needing to be manually configured in the routes file. I then start having Struts flashbacks!!
RichH
+10  A: 

I would treat search as a special case of index. Both actions return a collection of resources. The request parameters should specify things like page, limit, sort order, and search query.

For example:

/resources/index # normal index
/resources/index?query=foo # search for 'foo'

And in resources_controller:

before_filter :do_some_preprocessing_on_parameters

def index
  @resources = Resource.find_by_param(@preprocessed_params)
end

As for index_full and search_by_name, you might look at splitting your current controller into two. There's a smell about what you've descibed...

Having said that, you're absolutely right that there's no point in forcing your app to user restful routes when it doesn't deliver anything over /:controller/:action/:id. To make the decision, look how frequently you're using the restful resource route helpers in forms and links. If you're not using them, I wouldn't bother with it.

Dave Nolan
The approach you describe at the start breaks the MVC pattern in my mind. I go from having an action that does one thing cleanly to an action that does lots of different things in a hidden way. Your last point is interesting - maybe I shouldn't be using restful routes at all?
RichH
I see it this way: a search finds a bunch of resources and displays them in some kind of order. Index action does the same, but - by default - in a hardcoded way. So actually, index is a special case of search :)
Dave Nolan
I agree with EmbiggensTheMind: I see index as a special case of search.
MiniQuark
A: 

To remain RESTful in your design, you need to rethink what you call a resource.

In your example a show action for a search controller, (search resource) is the direction to remain restful.

In mine, I have a dashboard controller (show) and controllers for single fields of in-place ecditors (show and update)

theschmitzer
I'm not sure I understand what you mean. Could you be more specific? Are you saying that some actions (such as searching) should be seen as resources? Something like "map.resources :user_search"?
MiniQuark
A: 

A slightly creative solution gives you the best of both worlds:

Add a new map.connect in your routes.rb and put it above the standard ones. For instance, you can use

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

Now when you use a non-named route like

link_to 'Login', :controller => "users", :action => "login"

the path will be rendered with the double slash like /users//login. This way it will not get processed by show.

For the REST paths, Rails will use the automatic routes provided by map.resources like /users/32569.

Edit: You are not limited to using the double slash (which is a bit weird). You can use anything. For instance, if you use

 map.connect ':controller/a/:action/:id'

then the path

link_to 'Downgrade', :controller => "users", :action => "downgrade", :id => @id

will be rendered with /users/a/downgrade/35, which will of course be serviced by the action downgrade on the users controller (just like we wanted in the first place), and the REST routes are left intact (e.g., users/35 and users/edit/35).

Yar
I don't mind being downvoted, but please tell me why in comments. Then I can either change my answer, delete my answer, or defend my answer. Then we can all learn.
Yar
I didn't down vote you, but I can see a couple of reasons why other may have. First // may cause problems with other applications. From a quick Google search there are multiple reports of libraries not behaving correctly with double slashes in urls (except in http:// obviously!). Secondly, adding the map connect line allows people to call an arbitary public method on any controller. Many peopkle appear to say that all of the controller methods should be explicitly specificied in the routes.rb. I'm not fully sure I agree with this, but I can see some reasons why they may feel this way.
RichH
Rich, thanks for your comment. Regarding your second point, that makes sense, but it's also why we have visibility of methods on controllers. Regarding your first point, you do not hvae to use //. You can also use /norest/ or /z/ or whatever you want. I used the double slash because it kind of gets out of the way visually, but anything will work.
Yar
A: 

In my opinion they may have gone a bit off the rails here. What happened to DRY?

I'm just getting back into Rails not having done much development with it since beta and I'm still waiting for the light-bulb to come on here. I'm still giving it a chance but if it hasn't happened for me by the end of my current project I'll probably just drop-back to the old standard routes and define the methods as I actually need them for the next one.

Jeremy
Resource routes have little effect on DRYness, it's more about convention over configuration. They set up standard named routes for all CRUD actions, which for the 20 or so Rails apps I've worked on have always been upwards of 90% of routes. Of course using the default :controller/:action/:id seems terse, but how DRY is it when you have thousands upon thousands of url_for :controller => 'foo', :action => 'bar', :id => @foo littered throughout your templates rather than foo_url(@foo)?
dasil003
A: 

Wont go on to explain more about REST since I think that has been answered to death in this question, however I will talk a little bit about the default route.

My main problem with the default route is that if you have multiple sites using the same rails app it can look horrible.

For example there may be controllers that you don't want people to be able to see on one app:

e.g.

http://example1.somesite.com/example_2/foo/bar/1

compare this to

/:controller/:action/:id

This would go to the controller example_2/foo, action bar and id 1

I consider this to be the main flaw of the rails default route and this is something that RESTful routes (with subdomain extensions) or only named routes (map.connect 'foo' ... ) can fix.

Omar Qureshi