Skip to content
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

How to retrieve records for a many to many relationship? #16

Closed
lionelrudaz opened this issue Feb 24, 2016 · 19 comments
Closed

How to retrieve records for a many to many relationship? #16

lionelrudaz opened this issue Feb 24, 2016 · 19 comments

Comments

@lionelrudaz
Copy link
Contributor

Hi guys,

Sorry for the questions again ;-)

I have an Intent model that can have many Entities. Entities can also belong to many Intents. Classical many to many relationship.

In my services, I'd like to be able to retrieve the Entities related to an Intent. I want to create a service that responds to an endpoint like "/api/v1/intents/1/entities"

My idea is to create a service called IntentEntity and match it with my Entity model. Now, how is it possible to limit the results to the ones that match the intentId parameter defined in the URL? I mean, there's no intentId attribute in the entities table in my database.

Let me know if you need more information.

Thanks in advance for your help.

Cheers,

Lionel

@daffl
Copy link
Member

daffl commented Feb 24, 2016

I am not too familiar with the more complex side of Sequelize but looking at the Relations/Associations docs, once you associated an m:n relationship you could just register a service that gets the related entities:

app.use('/api/v1/intents/:intentId/entities', {
  find(params) {
    const id = params.intentId;
    return app.service('intents').get(id)
      .then(intent => intent.getEntities());
  },

  setup(app) {
    this.app = app;
  }
});

@ekryski
Copy link
Member

ekryski commented Feb 24, 2016

@lionelrudaz or you can just do that same call to the intents service in a before hook of a entities service. We have found that you rarely need to nest routes like that and instead should have them flatter and use something like an intentId. See http://docs.feathersjs.com/middleware/routing.html#nested-routes.

So you'd have:

function getIntents() {
  return function(hook) {
    return Prmise(function(resolve, reject) {
      hook.app.service('intents').get(hook.params.query.intentId).then(result => {
         hook.params.intent = result;
         resolve(hook);
      }).catch(reject);
    };
  };
}

app.use('intents', service());
app.use('intent_entites', service());

app.service('intent_entities').before({
  find: [
    getIntents()
  ]
})

That should more or less do it.

@ekryski
Copy link
Member

ekryski commented Feb 24, 2016

I'm going to close this as both of those options should work. @lionelrudaz can you report back to let us know if one of those works or not? If not, we can re-open and dig deeper.

@ekryski ekryski closed this as completed Feb 24, 2016
@lionelrudaz
Copy link
Contributor Author

Thanks both of you for your answers.

I tried both options, can't make work either of them.

@daffl, theoretically, everything should work as you suggest. The thing is that I believe the relationship is somehow unknown at this step. I get the following error:

TypeError: intent.getEntities is not a function

Here's how I set the relationships up in services/index.js:

/* jshint esversion: 6 */

import language from './language';
import conversation from './conversation';
import message from './message';
import category from './category';
import intent from './intent';
import entity from './entity';
import intententity from './intent-entity';

import Sequelize from 'sequelize';

export default function() {
  const app = this;

  const sequelize = new Sequelize(app.get('postgres'), {
    dialect: 'postgres',
    logging: console.log
  });

  app.set('sequelize', sequelize);

  app.configure(language);
  app.configure(message);
  app.configure(conversation);
  app.configure(category);
  app.configure(intent);
  app.configure(entity);
  app.configure(intententity);

  let models = sequelize.models;
  models.intent.belongsToMany(models.entity, {through: 'IntentEntity'});
  models.entity.belongsToMany(models.intent, {through: 'IntentEntity'});

  sequelize.sync();
}

I imagine that on IntentEntity service, the relationship is unknown. I tried to move the relationship before configuring IntentEntity service, but that doesn't change anything.

Any ideas?

@ekryski: your solution will work as a charm with a 1:n relationship. The thing is that I don't have an intentId column in my Entities model, so that can't work like that, unless I'm missing a point. But working with flat URLs can work as well, no problem. It doesn't change the root question: how can I make a query with a join on the relationship table?

Let me know if you need anything else.

@ekryski ekryski reopened this Feb 24, 2016
@lionelrudaz
Copy link
Contributor Author

Hi guys,

I tried a few other workarounds, but I'm unable to make that work. Am I the only one in that situation? Haven't you been in front of many-to-many relationships? I can't find any example that helped me so far.

Thanks again for your help.

Cheers,

Lionel

@daffl
Copy link
Member

daffl commented Mar 1, 2016

For me not with Sequelize. Did you get some test code working with just the plain model definitions as described in the Sequelize documentation?

@ekryski
Copy link
Member

ekryski commented Mar 1, 2016

@lionelrudaz I'll try and cook up a demo app to see if I can get this sorted. In all honesty I haven't run into many-to-many relationships all that often.

@lionelrudaz
Copy link
Contributor Author

@daffl I can try again with a very simple application to see if it behaves correctly. From that part, I think that the association is not yet available when I work in the services. I don't know if it helps.

@ekryski Do you want a link to my app? Maybe it can help.

@ekryski
Copy link
Member

ekryski commented Mar 1, 2016

@lionelrudaz sure that would help.

@lionelrudaz
Copy link
Contributor Author

@ekryski
Copy link
Member

ekryski commented Mar 1, 2016

@lionelrudaz took a quick look. Overall looks really good! Makes me really happy to see how someone else is using Feathers. I'll take a more detailed look a little later today just in the middle of working on authentication right now. I think we should be able to solve this pretty easily.

@lionelrudaz
Copy link
Contributor Author

No problem.

Your new example really helps building beautiful apps from the ground up. I just modified the structure to add a models folder, because I found that cleaner.

Let me know if I can be from any help.

@lionelrudaz
Copy link
Contributor Author

I worked a bit this evening. I believe the key is that associations aren't existing when I do this in my intent entity service:

app.use('/api/v1/intents/:intentId/entities', {
    find(params) {
      const id = params.intentId;
      return app.service('api/v1/intents').get(id)
        .then(intent => intent.getEntities());
    }
  });

It still tells me that there's no getEntities method for intent. My conclusion is that the intent service provides a model instance and that this model instance doesn't know the relationship with Entity.

I tried to move a bit the logic, by loading models from an index.js file in my models folder. In parallel, I removed the connection with the DB in the services. I changed the intent, entity and intent entity services to use the models that I instantiate from the models module.

I also added a new IntentEntity model to take care of the relationship.

I'm still at the same stage, so I don't know if you have any clue.

I uploaded my work in a new branch: https://github.com/lionelrudaz/wellnow-node/tree/many-to-many

Let me know if you need more information.

@lionelrudaz
Copy link
Contributor Author

OK, I think I made it.

My idea: load all models with index.js in the models folder. Set the associations right there.

Then, for the particular services using a many-to-many relationship, I can do this:

app.use('/api/v1/intents/:intentId/entities', {
    find(params) {
      const id = params.intentId;
      return models.entity.findAll({
        include: [
           { model: models.intent, where: { id: id }}
        ]
      });
    },
    create(data, params) {
      const id = params.intentId;
      return models.entity.create(data).then(function(entity) {
        entity.addIntent(id);
        return entity;
      });
    }
  });

Can you briefly tell me it's not horrible and that I can proceed with the next services that have these kinds of associations?

My app is updated there: https://github.com/lionelrudaz/wellnow-node

Thanks in advance

@ekryski
Copy link
Member

ekryski commented Mar 2, 2016

@lionelrudaz Woot! If it's working then it definitely looks good to me. 😄 Nice work.

I'm busy working on the news Feathers website so sorry I didn't really have time to play around with Many-to-many relationships.

@lionelrudaz
Copy link
Contributor Author

No problem, totally get it. All the best for the new website. I close then

@lopezjurip
Copy link

On client side, params.intentId is undefined.

const io = require('socket.io-client');
const feathers = require('feathers/client');
const hooks = require('feathers-hooks');
const socketio = require('feathers-socketio/client');
const authentication = require('feathers-authentication/client');

const socket = io('http://localhost:3030');
const client = feathers()
  .configure(hooks())
  .configure(socketio(socket))
  .configure(authentication());

client.service('/api/v1/intents/:intentId/entities').find({ intentId: 1 })
  .then(entities => console.log(entities))
  .catch(err => console.error(err));

But with server-side feathersjs app instance, the param intentId is passed correctly.

@daffl
Copy link
Member

daffl commented Apr 17, 2016

Can you create a new issue for that? This is kind of a bug (because only params.query is passed between client and server) but I am not sure how to solve that yet. A workaround is to map it to params.query.intendId.

@lopezjurip
Copy link

Issue: feathersjs/feathers#304

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

No branches or pull requests

4 participants