JSData load relations without opts

122 views Asked by At

When eagerly loading relations through a mapper, the opts argument is passed down to the loaded relations. This breaks the api in my case. For instance:

storyMapper.findAll({ title: 'foobar' }, { with: ['user'] });

This results in two requests:

GET /stories?title=foobar
GET /users?title=foobar

I might be missing something, but I would expect the defined relations to be used so that the story is loaded first, it's userId field read, and the second query be something like

GET /users/<the id>

Or at least

GET /users?where=<id in <the id>>

So my question is; can i change the behavior of this or do I need to use loadRelations on each story after it has loaded?


Code samples:

// user schema

import { Schema } from 'js-data';

export const user = new Schema({
    $schema: 'http://json-schema.org/draft-04/schema#',
    title: 'User',
    description: 'Schema for User records',
    type: 'object',
    properties: {
        _id: { type: 'string' },
        username: { type: 'string' },
        email: { type: 'string' },
        password: { type: 'string' },
    },

    required: ['username', 'email', 'password'],
});

// story schema

import { Schema } from 'js-data';

export const story = new Schema({
    $schema: 'http://json-schema.org/draft-04/schema#',
    title: 'Story',
    description: 'Schema for Story records',
    type: 'object',
    properties: {
        _id: { type: 'string' },
        title: { type: 'string', default: '' },
        userId: { type: ['string', 'null'] },
        username: { type: ['string', 'null'] },
    },
    required: ['title'],
});

// user mapper

this.store.defineMapper('user', {
    mapperClass: ObservableMapper,
    recordClass: User,
    endpoint: 'users',
    idAttribute: '_id',
    schema: schemas.user,
    relations: relations.user,
})

// story mapper

this.store.defineMapper('story', {
    mapperClass: ObservableMapper,
    recordClass: Story,
    endpoint: 'storys',
    idAttribute: '_id',
    schema: schemas.story,
    relations: relations.story,
})

// user relations

export const user = {
    hasMany: {
        world: {
            foreignKey: 'userId',
            localField: 'worlds',
        },
    },
};

// story relations

export const world = {
    belongsTo: {
        user: {
            foreignKey: 'userId',
            localField: 'user',
        },
    },
};

Sample data returned from GET /stories?title=foobar:

{
  "_id": "546e53dcedee82d542000003",
  "userId": "526e8617964fd22d2b000001",
  "username": "Someone",
  "title": "Lorem Ipsum"
}
1

There are 1 answers

0
jdobry On BEST ANSWER

You're missing the other side of the User-Story relation:

// user relations
export const user = {
  hasMany: {
    story: {
      foreignKey: 'userId',
      localField: 'stories'
    },
    world: {
      foreignKey: 'userId',
      localField: 'worlds'
    }
  }
};

Now, when you actually make the request you have two options:

Option 1 - Multiple requests

This requires that your server understands the "where" querystring parameter:

store.findAll('story', { title: 'foobar' }, { with: ['user'] })

Here's a plunker that demonstrates: https://plnkr.co/edit/UCFJNg?p=preview

The plunker example makes two requests:

  • GET /stories?title=foobar
  • GET /users?title=foobar&where={"_id":{"in":[123,234]}}

Option 2 - A single request

This requires that your server understands the "with" querystring parameter:

store.findAll('story', { title: 'foobar' }, { params: { with: ['user'] } })

Here's a plunker that demonstrates: https://plnkr.co/edit/M6quP4?p=preview

The plunker example makes just one request, and expects the users to be embedded within the stories in the server's response:

  • GET /stories?with=user&title=foobar

Note

This is a quirk of the HTTP adapter. For all other adapters, using the with option works as you'd expect, and you don't have to mess with the params option.