23

I'd like to reopen Ember or Ember Data framework classes. Using Ember CLI, where is the right place to put these so that they get initialized property? Here's an example of something I'd like to do:

import DS from 'ember-data';

DS.Model.reopen({
  rollback: function() {
    this._super();
    // do some additional stuff
  }
});
Johnny Oshika
  • 54,741
  • 40
  • 181
  • 275

3 Answers3

22

I think the best way to execute modules that have side effects would be to create an initializer. Something like this:

// app/initializers/modify-model.js
import DS from 'ember-data';

let alreadyRun = false;

export default {
    name: 'modify-model',
    initialize() {
        if (alreadyRun) {
            return;
        } else {
            alreadyRun = true;
        }

        DS.Model.reopen({
            // ...
        });
    }
};

Initializers are automatically run by Ember-CLI, so there's no need to call them yourself.

EDIT: As Karim Baaba pointed out, it's possible for initializers to run more than once. For an easy way around that, I've included an alreadyRun flag.

Jacob van Lingen
  • 8,989
  • 7
  • 48
  • 78
GJK
  • 37,023
  • 8
  • 55
  • 74
  • This seems too hacky. @KarimBaaba's solution seems cleaner. – blueFast Jan 04 '16 at 15:19
  • Karim Baaba's solution only works for certain scenarios, as not all framework classes are used directly. It also forces you to either create new aliases or forgo the built-in aliases. I'm not saying it's a bad solution, but there are numerous scenarios where it either wouldn't work or would be very cumbersome. In general I agree with you - I hate modifying library functionality. – GJK Jan 04 '16 at 15:35
18

Using an initializers is sufficient but isn't a good practice for writing tests as they're ran multiple times.

Here is an example of how to reopen the text field view to clear the input when focusIn is triggered app/overrides/textfield.js:

import Ember from 'ember';

export default Ember.TextField.reopen({
  focusIn: function(evt) {
    this._super(evt);
    this.set('value', '');
  }
});

app/app.js

import './overrides/textfield';

The pattern is very simple and can easily be used for DS.Model

Karim Baaba
  • 259
  • 2
  • 6
  • 1
    Since you are just reopening TextField, and not creating a new one, I would say you do not need to export it, which makes things more clear. Then your import statement would just be: `import './overrides/textfield';`. The advantage is that you are not importing a symbol which you won't be using, which is confusing. See http://stackoverflow.com/a/34628888/647991 – blueFast Jan 06 '16 at 09:03
  • where would I put the `import './overrides/textfield'` if I'm working on an (internal) addon – roberkules Jan 16 '17 at 12:18
4

Export your content as an ES6 module:

import DS from 'ember-data';

export default DS.Model.reopen({
  rollback: function() {
    this._super();
    // do some additional stuff
  }
});

Put the file with your reopen content somewhere like app/custom/model.js, then import the file in app/app.js like this:

import SuperModel from './custom/model';

Now all your models have the custom code.

givanse
  • 14,503
  • 8
  • 51
  • 75