Feathers: Como obter associações

Criado em 8 ago. 2016  ·  30Comentários  ·  Fonte: feathersjs/feathers

Então, desenvolvi um aplicativo com a estrutura de penas. Eu tenho um gancho anexado a um serviço de banco de dados que desejo acessar dados de outro serviço. Isso é possível e fácil? Quero que esse serviço possa acessar vários arquivos neDB. Como eu faço isso?

'use strict';

module.exports = function(options) {
  return function(hook) {

    // access other db files here and get data

    hook.data = {
        putDataHere: newData
    };
  };
};

Comentários muito úteis

sim. Dê uma olhada no exemplo de como buscar itens relacionados . Basicamente, em seu gancho você pode acessar outro serviço via hook.app.service('servicename') e chamar o método que você precisa. Para que o gancho aguarde, você pode retornar uma promessa (que se resolve com o objeto hook ), que é convenientemente o que todos os métodos de serviço já fazem:

module.exports = function(options) {
  return function(hook) {
    return hook.app.service('otherservice').find({
      query: { name: 'David' }
    }).then(page => {
      const davids = page.data;

      hook.data.davids = davids;
      // Or you can replace all the data with
      hook.data = { davids };

      // IMPORTANT: always return the `hook` object in the end
      return hook;
    });
  };
};

Todos 30 comentários

sim. Dê uma olhada no exemplo de como buscar itens relacionados . Basicamente, em seu gancho você pode acessar outro serviço via hook.app.service('servicename') e chamar o método que você precisa. Para que o gancho aguarde, você pode retornar uma promessa (que se resolve com o objeto hook ), que é convenientemente o que todos os métodos de serviço já fazem:

module.exports = function(options) {
  return function(hook) {
    return hook.app.service('otherservice').find({
      query: { name: 'David' }
    }).then(page => {
      const davids = page.data;

      hook.data.davids = davids;
      // Or you can replace all the data with
      hook.data = { davids };

      // IMPORTANT: always return the `hook` object in the end
      return hook;
    });
  };
};

@daffl Muito obrigado. Achei que seria assim tão fácil, mas não consegui encontrar na documentação, devo ter perdido.

Ok, então esta é uma chamada de busca que eu tenho antes do gancho (apenas se você precisar dessa informação)

Estou recebendo apenas 25, mas existem centenas. Se eu remover o limite, recebo apenas 5 itens.

O que está acontecendo e como faço para obter tudo?

module.exports = function(options) {
  return function(hook) {

    const dataSet = hook.params.query.dataSet;
    const userVector = hook.params.query.userVector;

    return hook.app.service('data').find({
      query: { dataSet: dataSet, $limit:2000 }
    }).then(page => {

      // lots of magic here

      //console.log(page.data);

      hook.result = page.data;

      return hook;
    });
  };
};

Depende de qual opção você definiu para paginate.max na configuração. É uma proteção para que alguém não possa simplesmente solicitar milhões de registros remotamente. Em todos os adaptadores mais recentes, você pode desligar a paginação para obter todos os registros assim :

return hook.app.service('data').find({
      paginate: false,
      query: { dataSet: dataSet }
    }).then(data => {
      // data is an array
    });

Quando faço isso, obtenho 0 registros. Quando removo a paginação: falso, obtenho 25.

npm ls feathers-nedb mostra a v2.4.1? Caso contrário, você terá que atualizar seu package.json acordo.

└── [email protected]

Acabei de verificar que paginate: false funciona com o seguinte exemplo:

const feathers = require('feathers');
const rest = require('feathers-rest');
const socketio = require('feathers-socketio');
const hooks = require('feathers-hooks');
const nedb = require('feathers-nedb');
const bodyParser = require('body-parser');
const handler = require('feathers-errors/handler');
const NeDB = require('nedb');

// A Feathers app is the same as an Express app
const app = feathers();
const db = new NeDB({
  filename: './data/messages.db',
  autoload: true
});

// Parse HTTP JSON bodies
app.use(bodyParser.json());
// Parse URL-encoded params
app.use(bodyParser.urlencoded({ extended: true }));
// Register hooks module
app.configure(hooks());
// Add REST API support
app.configure(rest());
// Configure Socket.io real-time APIs
app.configure(socketio());
// Register our memory "users" service
app.use('/todos', nedb({
  Model: db,
  paginate: {
    default: 20,
    max: 50
  }
}));
// Register a nicer error handler than the default Express one
app.use(handler());

const promises = [];

for(let i = 0; i < 700; i++) {
  promises.push(app.service('todos').create({ text: `Item #${i}`}));
}

Promise.all(promises).then(function() {
  app.service('todos').find({
    paginate: false,
    query: {}
  }).then(data => console.log(data));
});

// Start the server
app.listen(3333);

Estou recebendo 700 itens registrados no console. Você acidentalmente adicionou paginate: false à consulta em vez dos parâmetros principais?

Isso é o que eu fiz:

'use strict';

module.exports = function(options) {
  return function(hook) {

    const dataSet = hook.params.query.dataSet;
    const userVector = hook.params.query.userVector;

    return hook.app.service('data').find({
      paginate: false,
      query: { dataSet: dataSet, $limit:2000 }
    }).then(page => {

      // lots of magic here
      console.log(page.data);

      hook.result = page.data;

      return hook;
    });
  };
};

Ainda obtém 0 resultados e 25 com a variável de paginação ausente e 5 resultados sem limite.

No entanto, consegui funcionar com isto:

'use strict';

module.exports = function(options) {
  return function(hook) {

    const dataSet = hook.params.query.dataSet;
    const userVector = hook.params.query.userVector;

    return hook.app.service('data').find({
      paginate: false,
      query: { dataSet: dataSet }
    }).then(page => {

      // lots of magic here
      console.log(page);

      hook.result = page;

      return hook;
    });
  };
};

Como você pode ver, os resultados não estão mais em page.data, mas apenas na variável de página.

Lição aprendida :)

Oh e obrigado @daffl por sua ajuda.

Do lado do cliente, não funciona. (a variável paginate: false, ).

Apenas query é passado entre o cliente e o servidor. Eu tentaria evitar que um cliente solicitasse todas as entradas (se seu banco de dados tiver um milhão de registros, ele matará ambos, o servidor e o cliente). Se não houver maneira de contornar isso, você terá que mapear um parâmetro de consulta especial (por exemplo, $paginate ) de params.query em params em um gancho no servidor:

app.service('data').before({
  find(hook) {
    if(hook.params.query && hook.params.query.$paginate) {
      hook.params.paginate = hook.params.query.$paginate === 'false' || hook.params.query.$paginate === false;
      delete hook.params.query.$paginate;
    }
  }
});

Eu entendo o aviso de não permitir a paginação no lado do cliente, no entanto, a ferramenta que estou fazendo é apenas para eu executar localmente. Obrigado novamente por tudo.

@daffl Olá, estou usando o código que você postou para recuperar as postagens de um usuário.

module.exports = function(options) {
  return function(hook) {
    // hook.getPosts = true;
    const id = hook.result.id;
    console.log("id");

    return hook.app.service('posts').find({
      paginate: false,
      query: { postedBy: id }
    }).then(function(posts){
      console.log("posts");
      // Set the posts on the user property
      hook.result.posts = posts.data;
      // Always return the hook object or `undefined`
      return hook;
    });
  };
};

Mas isso faz com que o servidor entre em um loop infinito e o aplicativo de nó finalmente travar.

Estou usando o MySQL como meu banco de dados conectado pelo sequelize.

O que estou fazendo errado?

Atualizar

Eu tinha configurado um gancho semelhante para postagens para preencher o campo do usuário com base no campo postedBy . Assim, ao que parece, o gancho do usuário acionaria o gancho posterior que, por sua vez, acionaria o gancho do usuário original e o loop continuaria resultando em um loop infinito e estouro de memória.

Qualquer ideia de como preencher apenas um item relacionado superficial, ou seja, o gancho puxaria apenas os itens relacionados apenas no primeiro nível.

@daffl Sua ideia é incrível, embora o código não funcione.
Tive que alterar hook.paginate para hook.params.paginate para fazê-lo funcionar.
E eu fiz uma pequena alteração para que você possa enviar o que quiser.
então você pode $ paginate: {value: false} para desativar a paginação ou
$ paginate: {value: {default: 100, max: 2000}} para substituir a paginação

app.service('data').before({ find(hook) { if(hook.params.query.$paginate) { hook.params.paginate = hook.params.query.$paginate.value; delete hook.params.query.$paginate; } } });

Mais uma coisa a se considerar é que, quando a paginação está desabilitada, os dados não estão em res.data, mas em si mesmo.

@ fortunes-technology Você está certo. Eu atualizei meu trecho de código. Isso deve funcionar:

app.service('data').before({
  find(hook) {
    if(hook.params.query && hook.params.query.$paginate) {
      hook.params.paginate = hook.params.query.$paginate === 'false' || hook.params.query.$paginate === false;
      delete hook.params.query.$paginate;
    }
  }
});

Eu acho que se $ paginate estiver definido como falso (que é o ponto deste gancho, para desabilitá-lo ...), o if também será falso. Pode ser

app.service('data').before({
  find(hook) {
    if(hook.params.query && hook.params.query.hasOwnProperty('$paginate')) {
      hook.params.paginate = hook.params.query.$paginate === 'false' || hook.params.query.$paginate === false;
      delete hook.params.query.$paginate;
    }
  }
});

Olá. Alguém poderia me dizer se os serviços de penas têm a funcionalidade findOne ou como implementá-la? Obrigado!
cc @daffl

A findOne é apenas find com $limit de 1:

app.service('myservice').find({ query: { name: 'test', $limit: 1 } })
 .then(page => page.data[0])
 .then(result => console.log('Got', result);

@daffl Eu perguntei porque o mongoose tem findOne e eu sugeri que ele não procure em toda a coleção de resto se algo já foi encontrado. Então, pensei em findOne como algo mais inteligente do que pesquisar com limit = 1 ... É por isso que perguntei.

Normalmente faço isso para uma localização rápida:

const employee = (await context.app.service('employees').find({ query: { userId: user.id }, paginate: false }))[0];

Acabei de criar um plugin para um método .findOne() : penas-findone

Não consegui encontrar nenhum exemplo de como adicionar a solução alternativa $ paginate para o cliente. Então, criei um hook before que é executado a partir do arquivo app.hooks.js com algumas modificações:

module.exports = function () {
  return context => {
    if (context.params.query && hook.params.query.hasOwnProperty('$paginate')) {
      context.params.paginate = context.params.query.$paginate === 'false' || context.params.query.$paginate === false;
      delete context.params.query.$paginate;
    }
  };
};

Como você escreveria um teste de unidade ou integração para isso?

então, se eu tiver o seguinte no meu gancho

const records = getItems(context);
records.authors = await context.app.service('users') .find({ query: { _id: { $in: records.authorIds } } }, context.params);

como faço para "simular" o context.app.service em meu teste de unidade?

preciso adicioná-lo ao meu objeto contextBefore?

contextBefore = { type: 'before', params: { provider: 'socketio', user: { _id: 1 } }, data: { _id: 1, }, };

estou usando o teste de unidade gerado automaticamente pelo gerador de penas plus.
https://generator.feathers-plus.com/get-started/#unit -test-for-a-hook

ou devo usar um teste de integração em vez disso?

Eu segui a rota da zombaria há algum tempo. Eu acho que é uma ideia muito ruim. Você eventualmente terá que zombar mais e mais das penas. Não há nenhuma garantia de que suas simulações funcionem como as penas e você pode obter falsos negativos, como eu.

Instanciar um aplicativo Feathers é muito rápido, então use Feathers em vez de simulações. É assim que se faz a abordagem do teste cli +.

Escrevi um artigo que se refere a isso. https://medium.com/feathers-plus/automatic-tests-with-feathers-plus-cli-4844721a29cf

obrigado eddyy, eu vi esse, ótimo artigo, realmente ajudou.

Eu estava tendo dificuldades em como adicionar o aplicativo ao meu objeto de contexto, mas acabei descobrindo, eu acho!

    const app = feathers();

    contextBefore = {
      type: 'before',
      params: { provider: 'socketio', user: { _id: 1 } },
      data: {
        _id: 1,
      },
      app,
    };

Então eu tive que alterar o teste para que ele usasse async.

it('patch test', async () => {
    contextBefore.app.use('users', {
      async find() {
        return { data: [ { expectedResultObj1 }, { expectedResultObj2 } ] };
      }
    });
    contextBefore.data = {
      _id: 1,
    };
    assert(true);

    await hookToTest()(contextBefore);

    assert.deepEqual(contextBefore.data, {
      _id: 1,
      expectedResultObject1,
      expectedResultObject2,
    });
  });

Essa é a maneira certa de fazer isso ou existe uma maneira melhor?

Parece bom

@ fortunes-technology Você está certo. Eu atualizei meu trecho de código. Isso deve funcionar:

app.service('data').before({
  find(hook) {
    if(hook.params.query && hook.params.query.$paginate) {
      hook.params.paginate = hook.params.query.$paginate === 'false' || hook.params.query.$paginate === false;
      delete hook.params.query.$paginate;
    }
  }
});

pode usar a consulta $ limit: null para ignorar também

Esta página foi útil?
0 / 5 - 0 avaliações