简体   繁体   中英

Required properties (constructor args) in Ember.Object instances

In Ember, let's say I have an object called FoodStuff that has a few properties:

export default Ember.Object.extend({
    name: null,     // REQUIRED: 'Slice of Apple Pie'
    calories: null, // OPTIONAL: int: eg. 250
    category: null, // REQUIRED: 'Pastry'
    rating: null    // OPTIONAL: int: 1-5
});

How can I write a 'constructor' in Ember, requiring that the 'name' and 'category' properties be provided at instantiation-time?

Angular seems to approach this with fairly straightforward syntax:

.factory('User', function (Organisation) {

  /**
   * Constructor, with class name
   */
  function User(firstName, lastName, role, organisation) {
    // Public properties, assigned to the instance ('this')
    this.firstName = firstName;
    ...

Angular model objects with JavaScript classes

Does Ember have something similar? Currently all my classes are as seen at the top, with a bunch of initially null properties that might or might not be properly set by the caller. At build time (I'm using ember-cli ) I would like for changes in constructor requirements to be caught downstream by the ember build phase with JSHint.

As far as I know, there is no native way to do this in Ember. But there's nothing impossible! You can tweak Ember a bit to handle the case. Just add an initializer:

/initializers/extend-ember.js :

import Ember from 'ember';

export function initialize() {

  Ember.Object.reopen({

    /**
     * @prop {Array} - array of required properties
     */
    requiredAttrs: [],

    /**
     * Validates existance of required properties
     *
     * @param {String} attr - attribute name
     * @param {*} value - value of the property
     * @throws {Error} in case when required property is not set
     */
    _validateExistance(attr, value) {
      if (this.requiredAttrs.contains(attr) && typeof value === "undefined") {
        throw new Error("Attribute " + attr + " can't be empty!");
      }
    },

    /**
     * Sets value of a property and validates existance of required properties
     *
     * @override 
     */
    set(key, value) {
      this._validateExistance(key, value);
      return this._super(key, value);
    }

  });

  Ember.Object.reopenClass({

    /**
     * Creates object instance and validates existance of required properties
     *
     * @override
     */
    create(attrs) {
      let obj = this._super(attrs);
      if (attrs) {
        obj.requiredAttrs.forEach((key) => {
          obj._validateExistance(key, attrs[key]);
        });
      }
      return obj;
    }

  });

}

export default {
  name: 'extend-ember',
  initialize: initialize
};

Then you can use requiredAttrs property on any class to define which properties are required. It will throw an exception if you try to create an instance with empty required properties or if you try to set an empty value to required property.

let MyModel = Ember.Object.extend({
  prop1: null,
  prop2: null,
  requiredAttrs: ['prop2']
});

let ChildModel = MyModel.extend({
  prop3: null,
  requiredAttrs: ['prop2', 'prop3']
});

// throws exception
let obj1 = MyModel.create({
  prop1: 'val1'
});

// no exception
let obj2 = MyModel.create({
  prop2: 'val2'
});

// throws exception
obj2.set('prop2', undefined);

// throws exception
let obj3 = ChildModel.create({
  prop3: 'val3'
});

// no exception
let obj4 = ChildModel.create({
  prop2: 'val2',
  prop3: 'val3'
});

It will also work on DS.Model and other Ember entities out of the box, since they all extend Ember.Object .

To have instantiation-time (runtime) checks, you want to override the init method:

init() {
    this._super();
    Ember.assert('Name cannot be empty', name);
    // ...
}

For them to be caught at build time, I'm not sure of a good way to do that. You can use some kind of tool to add static typing to Javascript (Typescript and Flow come to mind), which might be able to force those properties when instantiating those objects. You could also write a custom tool (like an ESLint plugin) to check for that, but that's probably more difficultly than what it's worth. You're probably best off with a runtime solution and decent test coverage.


Here's an example of having your own create() method.

SomeClass.reopenClass({
    create(name, category, calories = null, rating = null) {
        // Validate your properties
        Ember.assert('Name cannot be empty', name);
        // ...

        // Create the object
        var obj = this._super({ name, category, calories, rating });

        // Do any other setup/validation

        // Return the object
        return obj;
    }
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM