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

Associations Suggestion #415

Open
chunlampang opened this issue Apr 14, 2023 · 0 comments
Open

Associations Suggestion #415

chunlampang opened this issue Apr 14, 2023 · 0 comments

Comments

@chunlampang
Copy link

chunlampang commented Apr 14, 2023

Here is an example to enhance $include for feathers-sequelize@7.0

export class MyService extends SequelizeService {
  
  /**
   * abstract function for defind associations
   * @param {*} model current model
   * @param {*} models all models inside the database
   * @see {@link https://sequelize.org/docs/v6/core-concepts/assocs/ Associations}
   * @example 
   * $include = {
   *   ["alias"]: {
   *     $required: true,
   *     $select: ["field_a", "field_b"],
   *     $sort: { "lastupdate": -1 },
   *     project_id: project_id,
   *     lastupdate: { $gte: startDate, $lte: endDate },
   *     $include: {
   *       ["alias"]: { ...nested },
   *     }
   *   },
   * }
   * @example $include = ["alias"]
   */
  defindAssociations(model, models) { 
    // model.hasMany(models['other_table'], { as: "items", foreignKey: "item_id", targetKey: "id" });
  }

  setup(app, path) {
    this.defindAssociations(this.Model, this.Model.sequelize.models);

    const service = app.service(path);
    service.publish((data, context) => app.channel(path)); // publish all events

    // no pagination service "/all"
    app.use(path + '/all', {
      main: this,
      async find(params) {
        const data = await this.main.find({ query: params.query, paginate: false });
        return { total: data.length, data };
      }
    });
  }

  filterQuery(params) {
    if (params.query.$include) {
      params.include = params.query.$include; // define for this.paramsToAdapter
      // remove $include for parent call
      params.query = { ...params.query };
      delete params.query.$include;
    }
    return super.filterQuery(params);
  }

  paramsToAdapter(id, params, associations = this.Model.associations) {
    const include = params.include;

    let q = super.paramsToAdapter(id, params);

    if (include) {
      q.raw = false; // format result
      q.include = this.parseInclude(associations, include);

      for (let item of q.include) {
        if (item.order) {
          for (let o of item.order) {
            q.order.push([item.as, ...o]);
          }
          delete item.order;
        }
      }
    }

    return q;
  }

  parseInclude(associations, $include) {
    const include = [];

    if (Array.isArray($include)) {
      for (const tableAlias of $include) {
        if (!associations[tableAlias])
          throw new BadRequest(`Unknown relation "${tableAlias}" in $include`);

        const { target, as } = associations[tableAlias];
        const iOpt = {
          model: target,
          required: false,
          as: as,
        };

        include.push(iOpt);
      }
    } else {
      for (const tableAlias in $include) {
        if (!associations[tableAlias])
          throw new BadRequest(`Unknown relation "${tableAlias}" in $include`);

        const { target, as } = associations[tableAlias];
        let { $required, $include: child$include, ...query } = $include[tableAlias];

        let id = this.options.id;
        this.options.id = target.primaryKeyAttribute; // fool super.paramsToAdapter() use target primary key, as it auto appends the id to attributes
        const q = this.paramsToAdapter(null, { query, paginate: false, include: child$include }, target.associations);
        this.options.id = id; // undo change id

        const iOpt = {
          model: target,
          required: !!$required,
          as: as,
          attributes: q.attributes,
          where: q.where,
          include: q.include,
          order: q.order,
        };

        include.push(iOpt);
      }
    }

    return include;
  }
}
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

1 participant