More RESTlessness: "unbalanced" routes

I'm working on an application (on Edge Rails) that has a really long user profile page - viewed, of course, with a GET to /users/:id.

The editing side of things needs to be broken up into four logically-grouped screens - say, contact info, work history, education, and personal info. These screens are essentially update-only - their "show" counterpart is the whole profile at /users/:id. And the update can only be performed by current_user, so the :id is superfluous.

There is no Create function, and there is no Delete function.

So far, I see only two choices:

1. Don't use RESTful routes at all. Change the GET to /users/show/:id, and do the updates with POSTs to /users/edit_contact_info, /users/edit_education, etc. Put it all in users_controller. This is the path I've been taking, but it loses some of the simply_helpful magic.

2. Create RESTful routes, and manually draw them in routes.rb. This seems brittle, and I have to lie about (e.g.) "contact_info" belonging to the collection instead of the member, since I don't want the :id in the URL. Plus, I think that means each of the four mini-edit pages has to go in its own, one-action controller! Ugly.

Is there a third way?

Jay Levitt

Sure. The Rails implementation of REST allows you to extend the RESTful routes with a similar concept to choice #2 but without "lying."

Say you want a unique url that updates _only_ the contact_info of a user. You can extend the user routes in routes.rb like this:

map.resources :users, :member=>{:update_contact_info_of=>:post}

What this says is that there should be a url for an individual user resource (a _member_ of the users collection) with an action mapping to "update_contact_info_of" that accepts only POST data. Just in case it's not obvious, :member is a hash for defining actions for individual resources, keyed by the name of an action and with a value of the allowed http methods (:get, :put, :post, :delete, :any).

By extending the users routes in that way, the form_for that wraps the contact info can looks like this:

<% form_for :user, update_contact_info_of_user_path(@user) do |f| %> ... <% end %>

HTH, AndyV

To me, this smells like RPC. There's a verb in your URL, "update". You don't need that, you've already said you're updating because of PUT.

What I would do is define a singleton resource for profile.

map.resource :profile

The view for that profile is broken up, I gather from the original question, into multiple forms. They can all PUT to the same reasource, though, /profile. There's nothing that says a PUT/update has to include ALL the data for the resource.

Personally, I would add extra bits onto my profile resource, so a GET of /profile/contact_info would show the view for editing the contact info and /profile/work_history would show the view for editing the work history.

Rails routing is extremely flexible. There is almost never a "has to go somewhere I don't want it to" with Rails routing.

HTH, Michael

See below...

Michael D. Ivey wrote:

  

Say you want a unique url that updates _only_ the contact_info of a user. You can extend the user routes in routes.rb like this:

map.resources :users, :member=>{:update_contact_info_of=>:post}

What this says is that there should be a url for an individual user resource (a _member_ of the users collection) with an action mapping to "update_contact_info_of" that accepts only POST data. Just in case it's not obvious, :member is a hash for defining actions for individual resources, keyed by the name of an action and with a value of the allowed http methods (:get, :put, :post, :delete, :any).      To me, this smells like RPC. There's a verb in your URL, "update".
You don't need that, you've already said you're updating because of PUT.

What I would do is define a singleton resource for profile.

map.resource :profile

The view for that profile is broken up, I gather from the original
question, into multiple forms. They can all PUT to the same
reasource, though, /profile. There's nothing that says a PUT/update
has to include ALL the data for the resource.

Personally, I would add extra bits onto my profile resource, so a GET
of /profile/contact_info would show the view for editing the contact
info and /profile/work_history would show the view for editing the
work history.

You could also

map.resources :users, :collection => {:contact_info => :put}