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

The power of mixins #1264

Closed
fabien opened this issue Apr 1, 2015 · 14 comments
Closed

The power of mixins #1264

fabien opened this issue Apr 1, 2015 · 14 comments
Assignees

Comments

@fabien
Copy link
Contributor

fabien commented Apr 1, 2015

I'm just throwing this in here to fuel some much needed discussion regarding the state of mixins, and what they could do for Loopback. I'm already using them extensively in my projects, and they allow distinct traits to be easily shared amongst models, and to be configured as needed.

When I see something like this #1260 - I see a problem that just begs for a Mixin to be implemented. Given the right infrastructure like the new operation hooks, there's a lot you can do.

Most of this has been available since loopbackio/loopback-datasource-juggler#201 ... but there's a pending discussion here: strongloop/loopback-boot#79

This is a bit from an model I've been using in one of my projects, post.js:

function setup(app) {
    this.defineProperty('info', 'MetaData');

    this.hasMany('Note', { as: 'notes', scope: { order: 'createdAt' } });
    this.referencesMany('Employee', { as: 'authors' });

    this.scope('galleries', { where: { type: 'Gallery' } });

    this.mixin('ObjectId');
    this.mixin('Protect', { properties: ['slug', 'type'] });
    this.mixin('Alias', { property: 'slug', from: 'title' });
    this.mixin('Transform', { property: 'type', filters: ['urlify', 'classify'] });
    this.mixin('TimeStamps');
    this.mixin('Storage', { as: 'attachments', references: true, relation: { 
        foreignKey: 'attachmentIds', scope: { order: 'filename' } } 
    });
    this.mixin('Includes');
    this.mixin('Reorder', { rel: 'attachments' });
    this.mixin('Constraint', { rel: 'notes', action: 'delete' });
    this.mixin('Versions', { attributes: ['title', 'body'], locale: true });
    this.mixin('Search', { index: 'main' });
    this.mixin('Expose', { scope: { order: 'slug' } });

    this.mixin('Render');
    this.mixin('Forms', { exclude: ['createdAt', 'updatedAt'] });

    this.mixin('Virtuals', { methods: {
        summary: function() {
            return 'Summary: ' + this.title;
        }
    } });
};

module.exports = function(Post) {
    Post.on('attached', setup.bind(Post));
};

And the post.json:

{
  "name": "Post",
  "base": "PersistedModel",
  "properties": {
    "type": {
      "type": "string",
      "required": true,
      "default": "Post",
      "search": {
        "index": "not_analyzed"
      }
    },
    "title": {
      "type": "string",
      "required": true,
      "search": {
        "raw": true
      }
    },
    "body": {
      "type": "string",
      "required": true
    },
    "active": {
      "type": "boolean",
      "default": true
    }
  },
  "validations": [],
  "relations": {
    "sections": {
      "type": "embedsMany",
      "model": "Section"
    }
  },
  "acls": [],
  "methods": []
}

Note that there's no pseudo code in there - this all works, and has been tested extensively.

Alternatively, you can declare mixins in your post.json under the mixins key like this:

{
  "name": "Post",
  "base": "PersistedModel",
  "properties": { ... },
  "mixins": {
    "Protect": { "properties": ["slug", "type"] },
    "Transform": [
       { "property": "name", "filters": ["capitalize"] },
       { "property": "type", "filters": ["urlify", "classify"] }
    ]
  }
}
@fabien
Copy link
Contributor Author

fabien commented Apr 1, 2015

@seriousben
Copy link
Contributor

We would also benefit a lot from this!

Would also push for a bigger ecosystem around loopback and make model code modularizable.

@robotiko
Copy link

robotiko commented Apr 1, 2015

Simply GREAT.
Now can easily extend loopback with simple and powerful (and shareable :D ) modules to fit any need not just mainstream.

Thanks!

@ritch
Copy link
Member

ritch commented Apr 1, 2015

@fabien I like the idea... but this is one of those things that is easy to get wrong. I do appreciate an implemenation that has been used extensively (thats why I think we should start with what you've been using). I'd like to get some usage out of it myself before adopting the programming model. Is it possible extract your mixin implementation into a module? I'd like to incubate something like this outside of core. That way we don't run into similar issues we did with other large feature additions (cough...intent hooks...cough)...

@fabien
Copy link
Contributor Author

fabien commented Apr 1, 2015

@ritch this is actually already in core (it lives in juggler mostly), unless I misunderstood your question (about extracting this, so you can use it yourself). It's very lightweight, with few moving parts.

@fabien
Copy link
Contributor Author

fabien commented Apr 2, 2015

I also would like to point out that LB mixins are more or less a direct port of Mongoose's plugin system: http://mongoosejs.com/docs/plugins.html - what's currently missing is the wonderful eco-system that grew around it: https://www.npmjs.com/search?q=mongoose

@ritch
Copy link
Member

ritch commented Apr 29, 2015

I also would like to point out that LB mixins are more or less a direct port of Mongoose's plugin system

Cool.

what's currently missing is the wonderful eco-system that grew around it: https://www.npmjs.com/search?q=mongoose

This ^^^^^ ... but in order for us to get there we need to make it easy to create mixins. Perhaps you could open source some genereic mixins you've created @fabien ?

In the short term I think we could start with a loopback-example-mixin (modeled after our other examples).

/cc @superkhau

@fabien
Copy link
Contributor Author

fabien commented Apr 29, 2015

@ritch sure, it might take some time to write isolated tests and things like that, but that shouldn't be a problem once we create a mixin project template (generator?) with such a setup.

Note that I would like to preserve the low-level ability of just defining mixins as part of the app itself, instead of forcing 1 mixin = 1 npm module/package.

@ritch
Copy link
Member

ritch commented Apr 29, 2015

Note that I would like to preserve the low-level ability of just defining mixins as part of the app itself

Sure... but the real draw (IMO, and what interests me) is mixins reusable across apps. Though I don't see that preventing us from supporting per app mixins.

@bajtos
Copy link
Member

bajtos commented Apr 30, 2015

The way how loopback-boot & loopback-workspace work are designed now, the layout "1 mixin = 1 npm module" is not supported well. Meaning that you can't make an npm module exporting a single module.exports = mixin(model, options). The tooling is build primarily for "1 npm module = 0-N mixins/models/middleware" scenario, see the two project-layout options below.

Option 1) root-facet layout (requires strongloop/generator-loopback#75)

mixins/
  time-stamps.js
  ...
package.json

Option 2) common/client/server layout

common/
  mixins/
    time-stamps.js
    ...
server/
  mixins/
    uptime.js
    ...
package.json

These two layout should be eventually supported by loopback-workspace & slc loopback.

Of course, user can decide to user any other layout, including an npm module exporting a single module.exports = mixin(model, options), as long as they are ok with partial or no support by standard loopback tooling.

@wprater
Copy link

wprater commented Oct 29, 2015

is there more config required for the syntax mentioned in the first post to work? in order for my mixin (plugin) to work, I need to define it in the LDL file for the model.

@emresebat
Copy link

Hi @fabien

I'm trying to add a default scope in a mixin without success.

Did you create a mixin which contains a defaultScope? I need to add this via mixin

"scope": {
    "include": [
      {
        "relation": "children",
        "scope": {
          "where": {
            "parentId": {
              "neq": null
            }
          }
        }
      }
    ]
  }

@stale stale bot added the stale label Aug 23, 2017
@stale stale bot closed this as completed Sep 6, 2017
@jbcpollak
Copy link

@emresebat - I know this is an old ticket, did you ever find a solution?

@emresebat
Copy link

@jbcpollak sorry to tell but no, I gave up eventually. I found loopback to be a good alternative for API development in the beginning and still use it for production apps but I think there are some points that needs to be fixed.

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

No branches or pull requests

9 participants