views:

506

answers:

3

I'm not getting REST with Ruby on Rails, and I'm hoping someone here can set me straight.

Imagine that I'm building a site that keeps track of Widgets and the Users that own those Widgets. So I'd have a UsersController and a WidgetsController, and I could get a list of Widgets or Users with the index actions:

GET /users
GET /widgets

and I could get a specific User or Widget with the show actions:

GET /users/id
GET /widgets/id

That much I understand.

Where I'm getting confused is what RESTful request would I use to retrieve a list of Widgets belonging to a specific User? Is that a request sent to the UsersController or the WidgetsController? Which of the 7 RESTful actions does it use?

Is one of those situations where I'd create a custom action? I was under the impression that custom actions are supposed to be rare, but this seems like a pretty common use case.

Thanks!

A: 

You are finding the design process difficult because you are trying to design your site based around an URL space when you should be designing your content documents instead.

Here are skeleton set of media types that would address your requirements.

Media Type: application/vnd.yourcompany.collections+xml

<Collections>
  <Widgets href="http://yoursite.com/{9BCCD309-644C-4fb8-A35E-A8B5E6AC4AE8}"/&gt;
  <Users href="http://yoursite.com/{BE57DC2D-8FE7-45e3-9362-AF5F607D62B6}"/&gt;
</Collections>

Media Type: application/vnd.yourcompany.Widgets+xml

<Widgets>
  <Widget href="http://yoursite.com/{4A7B5583-5D09-4cf3-9781-1084977769C0}"/&gt;
  <Widget href="http://yoursite.com/{0D6A72E8-6088-462c-A97A-70BC43E25475}"/&gt;
</Widgets>

Media Type: application/vnd.yourcompany.Users+xml

<Users>
  <User href="http://yoursite.com/{6321D95E-7EDB-46b8-9430-AB57EA067B06}"/&gt;
  <User href="http://yoursite.com/{0D6A72E8-6088-462c-A97A-70BC43E25475}"/&gt;
</Users>

Media Type: application/vnd.yourcompany.Widget+xml

<Widget>
  <Property1>99</Property1>
  <Property2>A Description</Property2>
  <UsersOfWidget href="http://yoursite.com/{26995C10-CA1D-4f1f-9065-2246A8426DA7}"/&gt;
</Widget>

Media Type: application/vnd.yourcompany.User+xml

<User>
  <Name>Joe Smith</User> 
  <WidgetsOwnedByUser href="http://yoursite.com/{D718A2E6-6ADD-4d6e-A1E7-6DA68EDE0BD3}"/&gt;
</User>

Obviously this set of media types is only one of many potential solutions to your problem. The issue I want to draw your attention to is that that the URL is largely irrelevant. How the documents inter-relate is important. When you look at it this way, it is not difficult to distinguish between the Widgets used by a User and the Users who use a Widget.

Now, how you map this to a set of Rails controllers is a whole other matter. The problem is not that it is difficult to design a RESTful solution, it is just Rails doesn't seem to map very naturally. Not that I have much experience with Rails, so take that for what it is worth. I believe that you should have one controller per resource and the fact that Rails tries to squeeze the list of a resource and the resource itself into a single controller is a mistake. Customers and Customer are two different resources in my opinion.

Edit: Possible set of Urls that might link these resources.

/Widgets
/Users
/Widget/1
/User/99
/Widget/1/UsersOfWidget
/User/99/WidgetsOwned

I would create a controller for each of these endpoints.

Darrel Miller
Thanks for you reply, but I've got NFI what you're talking about. You don't even seem to mention Widgets owned by Users? I was looking for an answer like "you should create a custom action on your controller" or "you should use the index action on your Widgets controller and do XYZ in it".
Stewart Johnson
:-) I changed the name of some of the elements in the last two documents to try and clarify. A convention that I use in XML documents returned from RESTful interfaces is that if an element has an href then I can follow that link to retrieve a complete representation of that resource. Sorry, I didn't realize it was that unclear. I guess I have spent too much time looking at documents like this.
Darrel Miller
I sort of knew you were looking for a "how does this work in Rails" but what I'm trying to get at is that thinking in terms of URLs and actions on controllers is going to make it really hard when you hit the harder issues in designing RESTful interfaces. I'll show you the URLs that I would use to model this problem, from that it may be obvious how to do it in Rails.
Darrel Miller
Another problem with Rails' "RESTful routing" is that it seems to be able to nest URIs in various ways, even if they come out to the same resource - a violation of REST.
Wahnfrieden
Instead of "/Widget/1/UsersOfWidget" Wouldn't "/Users?Widget=1" possibly be more appropriate? Uses the same /Users URI for a subset of the same resource.
Wahnfrieden
@Wahnfrieden Yeah, using a query parameter would probably be clearer. But like we keep saying, it really doesn't matter if your media types are done properly :-)
Darrel Miller
The URI representation doesn't matter (along as it doesn't violate whatever standard being used, HTTP in this case) but if I recall correctly you should only have one URI per view of resource, which applies here since it's just a subset and not a different media type.
Wahnfrieden
I think the key word there is SHOULD. I don't believe it is a MUST. I do recall it being a hotly debated topic ;-)
Darrel Miller
+2  A: 

The url for the list of widgets belonging to a user foo would look like this:

/users/foo/widgets

You then have a choice of how to do your URLs for each of those widgets. This is possible:

/users/foo/widgets/bar

But I prefer this:

/widgets/bar

Your routes would look like this:

map.resources :users, :has_many => :widgets, :shallow => true
map.resources :widgets, :has_many => :users, :shallow => true

(This is from memory, I may have screwed up one or more details)

The controller method that handles /user/foo/widgets is the index action of the WidgetController. It tests for the existence of the user_id parameter and restricts the widgets returned based on that. (Or retrieves the foo user and sets @widgets to @user.widgets.)

Update: There's a good overview of nested routing that answers my original question in the Rails Guides.

Update 2 Oh yes, I meant to link to some documentation as well.

wombleton
Ah that makes a lot of sense. I wasn't aware of the :shallow option for map.resources. Thanks!
Stewart Johnson
Spot on, but the relationship here is "has_and_belongs_to_many" since one user can have many widgets, and a widget can belong to many users.
askegg
You'd probably pair has_many and has_many :through relationships to something like a `UserWidget` (lame name) instead of a `habtm` I would have thought.
wombleton
Actually I had intended that a User :has_many Widgets and a Widget :belongs_to a User.
Stewart Johnson
Pretty URIs have nothing to do with REST.
Wahnfrieden
@Wahnfrieden -- and my question wasn't about pretty URLs, it was about controllers and actions.
Stewart Johnson
+1  A: 

I think Darrel is trying to educate you on REST in general, as he says worrying too much about URL design could be a sign that you are not really designing RESTfully. For example, as Darrel shows, your User representation could carry the links to the Widgets that he/she owns (or a link to a URL that performs a search for those widgets own by the user).