Skip to content

Commit

Permalink
[BUGFIX release] Refactor Model.reopen to use mixins
Browse files Browse the repository at this point in the history
This fixes bugs where users import Model form 'ember-data/model' and it was missing required methods

(cherry picked from commit b8e393c)

Conflicts:
	addon/-private/system/model/model.js
	addon/-private/system/relationships/belongs-to.js
	addon/-private/system/relationships/has-many.js
  • Loading branch information
bmac committed Jan 18, 2016
1 parent b40a079 commit 02bdc54
Show file tree
Hide file tree
Showing 9 changed files with 250 additions and 236 deletions.
1 change: 0 additions & 1 deletion addon/-private/system/debug.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
@module ember-data
*/

import "ember-data/-private/system/debug/debug-info";
import DebugAdapter from "ember-data/-private/system/debug/debug-adapter";

export default DebugAdapter;
6 changes: 2 additions & 4 deletions addon/-private/system/debug/debug-info.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Model from "ember-data/model";
import Ember from "ember";

Model.reopen({
export default Ember.Mixin.create({

/**
Provides info about the model for debugging purposes
Expand Down Expand Up @@ -63,5 +63,3 @@ Model.reopen({
};
}
});

export default Model;
226 changes: 226 additions & 0 deletions addon/-private/system/model/attr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import Ember from 'ember';
import { assert } from "ember-data/-private/debug";


var get = Ember.get;
var Map = Ember.Map;

/**
@module ember-data
*/

/**
@class Model
@namespace DS
*/

export const AttrClassMethodsMixin = Ember.Mixin.create({
/**
A map whose keys are the attributes of the model (properties
described by DS.attr) and whose values are the meta object for the
property.
Example
```app/models/person.js
import DS from 'ember-data';
export default DS.Model.extend({
firstName: attr('string'),
lastName: attr('string'),
birthday: attr('date')
});
```
```javascript
import Ember from 'ember';
import Person from 'app/models/person';
var attributes = Ember.get(Person, 'attributes')
attributes.forEach(function(meta, name) {
console.log(name, meta);
});
// prints:
// firstName {type: "string", isAttribute: true, options: Object, parentType: function, name: "firstName"}
// lastName {type: "string", isAttribute: true, options: Object, parentType: function, name: "lastName"}
// birthday {type: "date", isAttribute: true, options: Object, parentType: function, name: "birthday"}
```
@property attributes
@static
@type {Ember.Map}
@readOnly
*/
attributes: Ember.computed(function() {
var map = Map.create();

this.eachComputedProperty((name, meta) => {
if (meta.isAttribute) {
assert("You may not set `id` as an attribute on your model. Please remove any lines that look like: `id: DS.attr('<type>')` from " + this.toString(), name !== 'id');

meta.name = name;
map.set(name, meta);
}
});

return map;
}).readOnly(),

/**
A map whose keys are the attributes of the model (properties
described by DS.attr) and whose values are type of transformation
applied to each attribute. This map does not include any
attributes that do not have an transformation type.
Example
```app/models/person.js
import DS from 'ember-data';
export default DS.Model.extend({
firstName: attr(),
lastName: attr('string'),
birthday: attr('date')
});
```
```javascript
import Ember from 'ember';
import Person from 'app/models/person';
var transformedAttributes = Ember.get(Person, 'transformedAttributes')
transformedAttributes.forEach(function(field, type) {
console.log(field, type);
});
// prints:
// lastName string
// birthday date
```
@property transformedAttributes
@static
@type {Ember.Map}
@readOnly
*/
transformedAttributes: Ember.computed(function() {
var map = Map.create();

this.eachAttribute((key, meta) => {
if (meta.type) {
map.set(key, meta.type);
}
});

return map;
}).readOnly(),

/**
Iterates through the attributes of the model, calling the passed function on each
attribute.
The callback method you provide should have the following signature (all
parameters are optional):
```javascript
function(name, meta);
```
- `name` the name of the current property in the iteration
- `meta` the meta object for the attribute property in the iteration
Note that in addition to a callback, you can also pass an optional target
object that will be set as `this` on the context.
Example
```javascript
import DS from 'ember-data';
var Person = DS.Model.extend({
firstName: attr('string'),
lastName: attr('string'),
birthday: attr('date')
});
Person.eachAttribute(function(name, meta) {
console.log(name, meta);
});
// prints:
// firstName {type: "string", isAttribute: true, options: Object, parentType: function, name: "firstName"}
// lastName {type: "string", isAttribute: true, options: Object, parentType: function, name: "lastName"}
// birthday {type: "date", isAttribute: true, options: Object, parentType: function, name: "birthday"}
```
@method eachAttribute
@param {Function} callback The callback to execute
@param {Object} [binding] the value to which the callback's `this` should be bound
@static
*/
eachAttribute(callback, binding) {
get(this, 'attributes').forEach((meta, name) => {
callback.call(binding, name, meta);
});
},

/**
Iterates through the transformedAttributes of the model, calling
the passed function on each attribute. Note the callback will not be
called for any attributes that do not have an transformation type.
The callback method you provide should have the following signature (all
parameters are optional):
```javascript
function(name, type);
```
- `name` the name of the current property in the iteration
- `type` a string containing the name of the type of transformed
applied to the attribute
Note that in addition to a callback, you can also pass an optional target
object that will be set as `this` on the context.
Example
```javascript
import DS from 'ember-data';
var Person = DS.Model.extend({
firstName: attr(),
lastName: attr('string'),
birthday: attr('date')
});
Person.eachTransformedAttribute(function(name, type) {
console.log(name, type);
});
// prints:
// lastName string
// birthday date
```
@method eachTransformedAttribute
@param {Function} callback The callback to execute
@param {Object} [binding] the value to which the callback's `this` should be bound
@static
*/
eachTransformedAttribute(callback, binding) {
get(this, 'transformedAttributes').forEach((type, name) => {
callback.call(binding, name, type);
});
}
});


export const AttrInstanceMethodsMixin = Ember.Mixin.create({
eachAttribute(callback, binding) {
this.constructor.eachAttribute(callback, binding);
}
});
16 changes: 15 additions & 1 deletion addon/-private/system/model/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ import Ember from 'ember';
import { assert, deprecate } from "ember-data/-private/debug";
import { PromiseObject } from "ember-data/-private/system/promise-proxies";
import Errors from "ember-data/-private/system/model/errors";
import DebuggerInfoMixin from 'ember-data/-private/system/debug/debug-info';
import { BelongsToMixin } from 'ember-data/-private/system/relationships/belongs-to';
import { HasManyMixin } from 'ember-data/-private/system/relationships/has-many';
import { DidDefinePropertyMixin, RelationshipsClassMethodsMixin, RelationshipsInstanceMethodsMixin } from 'ember-data/-private/system/relationships/ext';
import { AttrClassMethodsMixin, AttrInstanceMethodsMixin } from 'ember-data/-private/system/model/attr';

/**
@module ember-data
Expand Down Expand Up @@ -848,4 +853,13 @@ if (Ember.setOwner) {
});
}

export default Model;
Model.reopenClass(RelationshipsClassMethodsMixin);
Model.reopenClass(AttrClassMethodsMixin);

export default Model.extend(
DebuggerInfoMixin,
BelongsToMixin,
DidDefinePropertyMixin,
RelationshipsInstanceMethodsMixin,
HasManyMixin,
AttrInstanceMethodsMixin);
3 changes: 1 addition & 2 deletions addon/-private/system/relationships/belongs-to.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Ember from 'ember';
import { assert, warn } from "ember-data/-private/debug";
import Model from 'ember-data/model';
import normalizeModelName from "ember-data/-private/system/normalize-model-name";

/**
Expand Down Expand Up @@ -138,7 +137,7 @@ export default function belongsTo(modelName, options) {
These observers observe all `belongsTo` relationships on the record. See
`relationships/ext` to see how these observers get their dependencies.
*/
Model.reopen({
export const BelongsToMixin = Ember.Mixin.create({
notifyBelongsToChanged: function(key) {
this.notifyPropertyChange(key);
}
Expand Down
7 changes: 3 additions & 4 deletions addon/-private/system/relationships/ext.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
typeForRelationshipMeta,
relationshipFromMeta
} from "ember-data/-private/system/relationship-meta";
import Model from "ember-data/model";
import EmptyObject from "ember-data/-private/system/empty-object";

var get = Ember.get;
Expand Down Expand Up @@ -98,7 +97,7 @@ var relationshipsByNameDescriptor = Ember.computed(function() {
@class Model
@namespace DS
*/
Model.reopen({
export const DidDefinePropertyMixin = Ember.Mixin.create({

/**
This Ember.js hook allows an object to be notified when a property
Expand Down Expand Up @@ -157,7 +156,7 @@ Model.reopen({
extensively.
*/

Model.reopenClass({
export const RelationshipsClassMethodsMixin = Ember.Mixin.create({

/**
For a given relationship name, returns the model type of the relationship.
Expand Down Expand Up @@ -596,7 +595,7 @@ Model.reopenClass({

});

Model.reopen({
export const RelationshipsInstanceMethodsMixin = Ember.Mixin.create({
/**
Given a callback, iterates over each of the relationships in the model,
invoking the callback with the name of each relationship and its relationship
Expand Down
4 changes: 2 additions & 2 deletions addon/-private/system/relationships/has-many.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import Ember from 'ember';
import { assert } from "ember-data/-private/debug";
import Model from "ember-data/model";
import normalizeModelName from "ember-data/-private/system/normalize-model-name";
import isArrayLike from "ember-data/-private/system/is-array-like";

Expand Down Expand Up @@ -146,6 +145,7 @@ export default function hasMany(type, options) {
return relationship.getRecords();
},
set: function(key, records) {
var Model = require('ember-data/model').default;
assert("You must pass an array of records to set a hasMany relationship", isArrayLike(records));
assert(`All elements of a hasMany relationship must be instances of DS.Model, you passed ${Ember.inspect(records)}`, (function() {
return Ember.A(records).every((record) => Model.detectInstance(record) );
Expand All @@ -159,7 +159,7 @@ export default function hasMany(type, options) {
}).meta(meta);
}

Model.reopen({
export const HasManyMixin = Ember.Mixin.create({
notifyHasManyAdded: function(key) {
//We need to notifyPropertyChange in the adding case because we need to make sure
//we fetch the newly added record in case it is unloaded
Expand Down
Loading

0 comments on commit 02bdc54

Please sign in to comment.