Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 806
Craig Otis Avatar asked Oct 19 '22 01:10

Craig Otis


1 Answers

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.

like image 181
Artur Smirnov Avatar answered Oct 21 '22 18:10

Artur Smirnov