Skip to content

Associations Suggestion #415

@chunlampang

Description

@chunlampang

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions