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:

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

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.

Ryan Bigg (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?

<snip>

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.

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

Jon Garvin wrote:
<snip>

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

<snip>

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?

<snip>

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.

<snip>

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

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

Ryan Bigg (Radar) wrote:

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

<snip>
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 Bigg (Radar) wrote:

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.

<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?

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.

Ryan Bigg (Radar) wrote:

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

<snip>
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 Bigg (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.

<snip>
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.

/goals/1/users/3/milestones/

Ryan Bigg (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.

<snip>
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.

Ryan Bigg (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.

<snip>
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.

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 Bigg (Radar) wrote:
<snip>

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.

<snip>

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.

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

AndyV wrote:

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

<snip>Can you come up with a real use case for your

application in which you will need to provide the information in this
way?

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

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.

<snip>

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 Fernandez.

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

R