Skip to content

Support for nested resource endpoints #40

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
jmdobry opened this issue Mar 29, 2014 · 30 comments
Closed

Support for nested resource endpoints #40

jmdobry opened this issue Mar 29, 2014 · 30 comments
Assignees
Milestone

Comments

@jmdobry
Copy link
Member

jmdobry commented Mar 29, 2014

No description provided.

@mattatcha
Copy link

How do you think something like this would work?

DS.defineResource({
    name: 'post'
});
DS.defineResource({
    parent: 'post',
    name: 'comment'
});

// GET http://localhost/post/10/comment
DS.findAll('comment', {postId: 10})
    .then(function(comments){
        // Do something here
    });

// GET http://localhost/post/10/comment/1
DS.find('comment', {postId: 10, commentId: 1})
    .then(function(comment){
        // Do something here
    });

@jmdobry
Copy link
Member Author

jmdobry commented Apr 2, 2014

@MattAitchison I see what you're getting at. However, your example seems to preclude the idea that GET http://localhost/comment/1 could be valid in addition to GET http://localhost/post/10/comment/1. I think angular-data would need to be capable to doing both. To extend your example:

DS.defineResource({
    name: 'post'
});
DS.defineResource({
    parent: 'post',
    name: 'comment'
});

// GET http://localhost/post/10/comment
DS.findAll('comment', { postId: 10 });

// GET http://localhost/comment
DS.findAll('comment');

// GET http://localhost/post/10/comment/1
DS.find('comment', 1, { postId: 10 });

// GET http://localhost/comment/1
DS.find('comment', 1);

In addition to nested resources, I'd like also to in general support belongsTo, hasOne, and hasMany relationships. The two seem related, so the implementation of one would affect the implementation of the other.

Once I've wrapped up relations in Reheat, I will move on to this.

@mattatcha
Copy link

I just thought of it real quick. I didn't think about that part.

I do like your example too. That was one of the other ways I was thinking about it.

I have no clue how you wanted to implement relations but I have thought about it a little. Maybe they could work like they do in reheat. So you could do something like DS.find('post', 1, {with: 'comments'});. This could then form a url like http://localhost/post/1?with=comments. The main problem I see with this is that it would require support on the server side. So maybe angular-data could make two requests to the server. One to http://localhost/post/1 which should give all the comment IDs and then another to get all the comments.

Maybe I am just making the relations more complicated than they need to be though. I haven't used any libraries for them before.

@AlJohri
Copy link

AlJohri commented Apr 3, 2014

@MattAitchison, @jmdobry Can you check out the way relations are done in ngActiveResource

https://github.com/FacultyCreative/ngActiveResource
and check out this issue about how their implementation is not ENTIRELY working out for me
FacultyCreative/ngActiveResource#40

@Patrik-Lundqvist
Copy link

Any news on this now that relations has been worked on?

@kentcdodds
Copy link
Contributor

Would probably be something good to include in the wiki. I haven't done anything like this so I wont be able to help here, but thought I'd give this suggestion :-)

@jmdobry
Copy link
Member Author

jmdobry commented Aug 20, 2014

@kentcdodds I seem to be missing something. Put what on the wiki? What suggestion?

@jmdobry
Copy link
Member Author

jmdobry commented Aug 20, 2014

@kentcdodds I looked at the wiki and realized that the workaround you suggested a while back is irrelevant now that support for relations has been added.

@jmdobry jmdobry self-assigned this Aug 20, 2014
@jmdobry jmdobry added this to the 1.0.0-beta.1 milestone Aug 20, 2014
@kentcdodds
Copy link
Contributor

Perhaps it's been documented elsewhere now, but I just that that since @Patrik-Lundqvist was asking it may not be easy to find. Feel free to remove my out of date wiki entry :-)

@jmdobry
Copy link
Member Author

jmdobry commented Aug 20, 2014

Relations are implemented and documented, but this nested resources issue is about something slightly different. It's about being able to handle nested resource endpoints, like /posts/43/comment/2, which I have not tackled yet.

@jmdobry jmdobry changed the title Support for nested resources Support for nested resource endpoints Aug 20, 2014
@jmdobry
Copy link
Member Author

jmdobry commented Aug 20, 2014

@Patrik-Lundqvist A solution is in the works

@jmdobry
Copy link
Member Author

jmdobry commented Aug 20, 2014

My current implementation is this:

Nested Resource Endpoints

Add parent: true to a belongsTo relationship to activate nested resource endpoints for the resource. Angular-data will attempt to find the appropriate key in order to build the url. If the parent key cannot be found then angular-data will resort to a non-nested url unless you manually provide the id of the parent.

Example:

DS.defineResource({
  name: 'comment',
  relations: {
    belongsTo: {
      post: {
        parent: true,
        localKey: 'postId',
        localField: 'post'
      }
    }
  }
});

// The comment isn't in the data store yet, so angular-data wouldn't know 
// what the id of the parent "post" would be, so we pass it in manually
DS.find('comment', 5, { parentKey: 4 }); // GET /post/4/comment/5

// vs 

DS.find('comment', 5); // GET /comment/5

DS.inject('comment', { id: 1, postId: 2 });

// We don't have to provide the parentKey here
// because angular-data found it in the comment
DS.update('comment', 1, { content: 'stuff' }); // PUT /post/2/comment/1

// If you don't want the nested for just one of the calls then
// you can do the following:
DS.update('comment', 1, { content: 'stuff' }, { nested: false ); // PUT /comment/1

@Patrik-Lundqvist
Copy link

Looks great! I'll try it out

@jmdobry
Copy link
Member Author

jmdobry commented Aug 20, 2014

I think it should work with findAll, updateAll, and destroyAll, but I haven't tested it yet.

@Patrik-Lundqvist
Copy link

Just tested DS.update with nested set to false but it didn't work.
I noticed the options parameter wasn't set for the update function here

Should be:

resourceConfig.getEndpoint(id, options)

jmdobry added a commit that referenced this issue Aug 21, 2014
@jmdobry
Copy link
Member Author

jmdobry commented Aug 21, 2014

@Patrik-Lundqvist Good catch. This is all on master now. I'll have to flesh out the tests a little more.

@Patrik-Lundqvist
Copy link

I ran into some problem with the current implementation where I have minimized my path nesting. Lets take this example: /orgs/{org_id}/customers/{customer_id}/projects/{project_id}

In order to not have complicated paths when retrieving projects and its child resources I have minimized my paths as described here. For instance, /customers/1/projects gets all projects which belongs to customer 1. Right now its only possible to get the full resource path or do requests to /projects.

@jmdobry
Copy link
Member Author

jmdobry commented Aug 21, 2014

Okay, so in addition to

  • /orgs/{org_id}
  • /orgs/{org_id}/customers/{customer_id}
  • /orgs/{org_id}/customers/{customer_id}/projects/{project_id}

You also want to be able able to do just /customers/{customer_id}/projects/{project_id} correct?

@Patrik-Lundqvist
Copy link

Exactly, and also get/post projects with /customers/{customer_id}/projects

@jmdobry
Copy link
Member Author

jmdobry commented Aug 21, 2014

Yeah, the current implementation isn't that flexible. I'll have to rework
it.
On Aug 21, 2014 8:31 AM, "Patrik Lundqvist" [email protected]
wrote:

Exactly, and also get/post projects with /customers/{customer_id}/projects


Reply to this email directly or view it on GitHub
#40 (comment).

@Patrik-Lundqvist
Copy link

Alright! By the way, maybe nested resources could be used when loading relations also. So that DS.loadRelations('customer', customer, ['project']) would send a request to:
/customers/{customer_id}/projects instead of /projects?customerId={customer_id}

@jmdobry
Copy link
Member Author

jmdobry commented Aug 21, 2014

The new implementation should take care of that.

jmdobry added a commit that referenced this issue Aug 21, 2014
@jmdobry
Copy link
Member Author

jmdobry commented Aug 21, 2014

Now:

Nested Resource Endpoints

Add parent: true to a belongsTo relationship to activate nested resource endpoints for the resource. Angular-data will attempt to find the appropriate key in order to build the url. If the parent key cannot be found then angular-data will resort to a non-nested url unless you manually provide the id of the parent.

Example:

DS.defineResource({
  name: 'comment',
  relations: {
    belongsTo: {
      post: {
        parent: true,
        localKey: 'postId',
        localField: 'post'
      }
    }
  }
});

// The comment isn't in the data store yet, so angular-data wouldn't know 
// what the id of the parent "post" would be, so we pass it in manually
DS.find('comment', 5, { params: { postId: 4 } }); // GET /post/4/comment/5

// vs 

DS.find('comment', 5); // GET /comment/5

DS.inject('comment', { id: 1, postId: 2 });

// We don't have to provide the parentKey here
// because angular-data found it in the comment
DS.update('comment', 1, { content: 'stuff' }); // PUT /post/2/comment/1

// If you don't want the nested for just one of the calls then
// you can do the following:
DS.update('comment', 1, { content: 'stuff' }, { params: { postId: false } }); // PUT /comment/1

@jmdobry
Copy link
Member Author

jmdobry commented Aug 21, 2014

@Patrik-Lundqvist So in your case you would now do something like:

DS.update('project', 5, { some: 'stuff' }, { params: { org_id: false } });
// If project "5" isn't already in the data store
DS.update('project', 5, { some: 'stuff' }, { params: { org_id: false, customer_id: 6 } });

And you should get PUT /customers/6/projects/5 with {"some":"stuff"} as the payload.

@Patrik-Lundqvist
Copy link

Works great, very nice! But how will the parent property handle multiple parents? Lets say that a user both belongs to an organization and a team which should make both of these paths valid:

  • /orgs/{org_id}/users
  • /teams/{team_id}/users

The second path would be called when loading relations for a team.

@jmdobry
Copy link
Member Author

jmdobry commented Aug 21, 2014

I feel like I'm going down the rabbit hole...Let me think about this one.

@jmdobry
Copy link
Member Author

jmdobry commented Aug 23, 2014

@Patrik-Lundqvist I'm going to consider this issue complete enough for the beta. Any other potential enhancements should go into their own issue.

@demisx
Copy link

demisx commented Apr 11, 2015

This doesn't appear to work with findAll

DS.defineResource({
      name: 'category',
      endpoint: '/categories',
      relations: {
        belongsTo: {
          section: {
            parent: true,
            localKey: 'secId',
            localField: 'section'
          }
        }
      }

Then calling:

Category.findAll({ params: { secId: 1 } }) 

Produces this URL:

 http://dev.api.dimaslist.org:3000/v1/categories?params=%7B%22secId%22:1%7D

Expected URL:

 http://dev.api.dimaslist.org:3000/v1/sections/1/categories

@jmdobry
Copy link
Member Author

jmdobry commented Apr 11, 2015

@demisx You need to do Category.findAll({ secId: 1 }) or Category(null, { params: { secId: 1 } })

@demisx
Copy link

demisx commented Apr 11, 2015

👍 That worked!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants