REST - Nested Resources

Hi

I am trying to use RESTful design practices, but I have areas of the
design which seem to suggest that I should have more than one level of
nesting. Now, all of the reading I have done, tells me that this is a
very bad idea, so I was wondering if the esteemed memebers of the list
can help me out.

One example:

There are many goals.
Each goal has many milestones.
Each milestone belongs to one user.

The UI is going to make fairly heavy use of AJAX to update elements, and
that will lead to situations where I need to get the following:

All Milestones for a particular user and a particular goal.

Now, if I go crazy with the nesting, I would end up with this as a url:

/goal/goal_id/user/user_id/milestones

Can anyone suggest RESTful alternatives, that won’t violate the best
practice of only one level of nesting?

Thanks in Advance

Rory

Rory McKinley wrote:

There are many goals.
/goal/goal_id/user/user_id/milestones

Can anyone suggest RESTful alternatives, that won’t violate the best
practice of only one level of nesting?

Thanks in Advance

Rory

No matter how you setup your routes, the milestones controller is going
to be the entry point, and it’s going to need a :user_id and :goal_id in
params (unless you’re going to also allow just one or the other).
You’re only RESTfulish route options that I can think of are…

  1. /goals/#/milestones?user_id=#
  2. /users/#/milestones?goal_id=#
  3. /users/#/goals/#/milestones
  4. /goals/#/users/#/milestones

If the typical use of this application would be users looking at their
own milestones, then opt 2 is probably what I would go with. Then
users/#/milestones could be a list of all of that user’s milestones,
and they could then choose a ‘goal’ to filter the list. That also
opens the door for option 1, so that someone could see all of the
milestones for a goal, and filter them by user.

What I think it boils down to is which seems to make the most sense
based on how the data will be visualized. I’ve occasionally used deeply
nested routes for certain actions, but only in cases where it really
made sense (at least to me) to do so. All of the above 4 options are
technically legal, so… it’s really just a matter of preference.

Note however that I used the plural versions of ‘user’ and ‘goal’ in
my sample routes. Using plural names in your routes and controllers is
(to the best of my knowledge) standard and recommended convention.

http://www.5valleys.com/

http://www.workingwithrails.com/person/8078

Surely you don’t need to know all three objects at the same time. If you
cared so much about restful routing you would simply reference the other
objects through the relationships.

Can you explain why you need to reference all three at the same time?

On Fri, May 16, 2008 at 2:33 AM, Jon G. [email protected]
wrote:

One example:
Now, if I go crazy with the nesting, I would end up with this as a url:

If the typical use of this application would be users looking at their
technically legal, so… it’s really just a matter of preference.


Ryan B.

Feel free to add me to MSN and/or GTalk as this email.

Ryan B. (Radar) wrote:

Surely you don’t need to know all three objects at the same time. If you
cared so much about restful routing you would simply reference the other
objects through the relationships.

Can you explain why you need to reference all three at the same time?

For the purposes of satisfying the UI requirements, I, at some point,
have to fetch a subset of a goal’s milestones. This subset will be all
those milestones for a particular goal and are being worked on by a
particular user (note that this is not necessarily the user that is
currently logged in).

To satisfy this, without knowing in advance the milestone ids, I have to
pass the milestone controller both a goal id and a user id. The
milestone belongs to both a goal and an user.

If you can tell me of a way that I can do this without passing both of
those parameters in an ajax request, I would be extremely grateful.

Jon G. wrote:

  1. /goals/#/milestones?user_id=#
  2. /users/#/milestones?goal_id=#
  3. /users/#/goals/#/milestones
  4. /goals/#/users/#/milestones

I think (1) and (2) will probably be the way I will need to go. I don;t
have the Rails/REST experience so I will rather err on the side of
something that is easier to maintain.

Although, wouldn’t (1)and (2) be somewhat less self-documenting than (3)
and (4)? Thereby losing some of the advantages of REST. Or am I missing
the point?

> Note however that I used the *plural* versions of 'user' and 'goal' in > my sample routes. Using plural names in your routes and controllers is > (to the best of my knowledge) standard and recommended convention.

Apologies that was a haste-induced error on my part.

Thanks for the help - it cleared a lot of things up.

Ryan B. (Radar) wrote:

Can you show me your models so I can get a clearer understanding of the
layout? Thanks. Just the relationships.

class User < ActiveRecord::Base has_many :goals has_many :milestones

class Milestone < ActiveRecord::Base
belongs_to :goal
belongs_to :user

class Goal < ActiveRecord::Base
belongs_to :user
belongs_to :category
has_many :milestones

here ya go

map.resources :goals do |goal|
goal.resources :milestones
end

map.resources :milestones

map.resources :users do |user|
user.resources :goals
user.resources :milestones
end

That should be all you need to do.

Ryan B. (Radar) wrote:

That should be all you need to do.
<I am assuming that for the above, the additional parameter required -
e.g. user_id in the case of /goals/#/milestones, would be passed as a
get parameter? I.e. /goals/#/milestones?user_id=#

Is that correct?

Can you show me your models so I can get a clearer understanding of the
layout? Thanks. Just the relationships.

Because a goal belongs to a user you can get the user by doing
@goal.user,
you don’t need to pass in the user_id variable.

On Fri, May 16, 2008 at 4:22 PM, Rory McKinley
[email protected]
wrote:

user.resources :milestones


Ryan B.

Feel free to add me to MSN and/or GTalk as this email.

Ryan B. (Radar) wrote:

Because a goal belongs to a user you can get the user by doing

Ah.

Apologies - that, I had forgotten to mention - the user that owns the
goal is not necessarily the user that owns the milestone:

E.g. Goal 1 belongs to User 1

Milestone 1 belongs to Goal 1 and is being worked on by User 2 and hence
belongs to User 2
Milestone 2 belongs to Goal 1 and is being worked on by User 3 and hence
belongs to User 3
Milestone 3 belongs to Goal 1 and is being worked on by User 1 and hence
belongs to User 1

In terms of filtering the milestones, the user that works on the
milestone is the user that I am interested in.

Sorry about that :wink:

And a milestone is assigned to a user, so you can go to
/goals/1/milestones/2 and then do @milestone.user to get the user.

Ryan B. (Radar) wrote:

And a milestone is assigned to a user, so you can go to
/goals/1/milestones/2 and then do @milestone.user to get the user.

But what if I want to request "all goal 1's milestones that belong to user 3"?

If I don’t pass a user_id parameter to goals/1/milestones, I then have
to filter out the milestones that have user_id = 3 client-side using JS.

My problem is not so much getting the result set, it’s setting up the
routing so that I can make the request in a RESTful manner.

On May 16, 12:58 pm, Rory McKinley [email protected]
wrote:

Ryan B. (Radar) wrote:

And a milestone is assigned to a user, so you can go to
/goals/1/milestones/2 and then do @milestone.user to get the user.

But what if I want to request "all goal 1's milestones that belong to user 3"?

For this, you need to have a conditions parameters in your GET REST
call like this:

GET /goals/1/milestones.xml?condditions=user_id=3
Now in your index method, do this:
params[:conditions] = params[:conditions] + '“AND
(#{params[:conditions]})”

@milestones = MileStone.find(:all, :conditions =>
params[:conditions])

And that’s it.

However, watch out for the SQL injection attacks! make your code safe
I’ve just demonstrated the idea.

/goals/1/users/3/milestones/

On Fri, May 16, 2008 at 5:28 PM, Rory McKinley
[email protected]
wrote:

to filter out the milestones that have user_id = 3 client-side using JS.

My problem is not so much getting the result set, it’s setting up the
routing so that I can make the request in a RESTful manner.


Ryan B.

Feel free to add me to MSN and/or GTalk as this email.

Ugh my god that is ugly!

My way is much simpler, but requires 2 level deep nested routing which
shouldn’t be used unless it’s an extreme case, like this one.

Ryan B. (Radar) wrote:

My way is much simpler, but requires 2 level deep nested routing which
shouldn’t be used unless it’s an extreme case, like this one.

Jon’s mail earlier in the thread, showed some ways that it can be done
without the second level of nesting (which I was trying to avoid)

Dont avoid it if it’s neccessary, the world will not hate you for it.

On Fri, May 16, 2008 at 9:03 PM, Rory McKinley
[email protected]
wrote:


Ryan B.

Feel free to add me to MSN and/or GTalk as this email.

AndyV wrote:

But what if I want to request “all goal 1’s milestones that belong to
user 3”?
Can you come up with a real use case for your
application in which you will need to provide the information in this
way?

Sadly, I have an actual use case for such a model :wink:

opinions of some, this is fully RESTful.

Thanks - I hadn’t thought it through fully - but what you are saying
makes sense - an approach as suggested above would definitely produce
much cleaner code.

Which leads me to a few more questions:

  1. Can I map a resource both as a nested resource as in the
    user/milestones example as well as just /milestones? Or is that crazy
    talk? :slight_smile:
  2. Is it RESTful to pass more than one filter parameter? For instance
    just have /milestones and pass both goal_id and user_id? Although that
    would make the resulting controller code a lot uglier…
  3. Can anybody recommend a good advanced reference on rails and rest?
    Most of the examples I have found have either been very basic or just
    touched on the advanced stuff. Currently about the best resource I have
    found is The Rails Way by Obie F…

Thanks to everyone who has helped so far… I am learning a stack.

R

But what if I want to request “all goal 1’s milestones that belong to
user 3”?

Do you want to do that? What I mean by the question is that you
really need to think through the business that you are trying to
model. Often we can get so distracted by the theoretical
possibilities that we never finish with the practical realities of the
tasks at hand. Can you come up with a real use case for your
application in which you will need to provide the information in this
way?

Can you frame the question in a different way that better maps to your
general solution? For example, you might be able to conceive of
this theoretical question of “all goal 1’s milestones that belong to
user 3” instead as “which of user 3’s milestones are specifically
related to goal 1”. Since your most common use case is (likely) the
need to show the milestones for a particular (logged in) user the
rephrased question might lead you to think of goal 1 becoming a
filter on the user_milestones_path routes. The UI might provide the
context for identifying the user and goal and then you’d build up
user_milestones_path(@user, :goal_id=>@goal.id). Contrary to the
opinions of some, this is fully RESTful.

MilestonesController:
def index
@user = User.find_by_id params[:user_id]
@milestones = params[:goal_id] ?
@user.milestones.find(:all, :conditions=>{:goal_id=>params[:goal_id]}) :
@user.milestones


end