Skip to content

Commit

Permalink
Merge pull request #30 from firebase/embedded-records
Browse files Browse the repository at this point in the history
Embedded records. Fixes #26
  • Loading branch information
aputinski committed Mar 26, 2014
2 parents 29795cb + 8e74673 commit 8cea734
Show file tree
Hide file tree
Showing 14 changed files with 1,054 additions and 371 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@ As of version 0.2.0, `EmberFire.Object` and `EmberFire.Array` have been removed,
## Usage

To get started, simply create an instance of the
`DS.FirebaseAdapter` and `DS.FirebaseSerializer` in your app, like this:
`DS.FirebaseAdapter` in your app:

```js
App.ApplicationAdapter = DS.FirebaseAdapter.extend({
firebase: new Firebase('https://<my-firebase>.firebaseio.com')
});
App.ApplicationSerializer = DS.FirebaseSerializer.extend();
```

You can now interact with the data store as you normally would. For example,
Expand Down
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "emberfire",
"version": "1.0.0",
"version": "1.0.1",
"description": "Firebase bindings for Ember Data",
"main": ["./dist/emberfire.js"],
"dependencies": {
Expand Down
155 changes: 104 additions & 51 deletions dist/emberfire.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,29 +19,44 @@
The object is then converted to an Array using `Ember.keys`
*/
normalize: function(type, hash) {
var relationshipsByName = Ember.get(type, 'relationshipsByName');
var relationshipNames = Ember.get(type, 'relationshipNames');
// Check if the model contains any 'hasMany' relationships
if (Ember.isArray(relationshipNames.hasMany)) {
relationshipNames.hasMany.forEach(function(key) {
var relationship = relationshipsByName.get(key);
// Return the keys as an array
if (typeof hash[key] === 'object' && !Ember.isArray(hash[key])) {
type.eachRelationship(function(key, relationship) {
if (relationship.kind === 'hasMany') {
if (typeof hash[key] === 'object' && !Ember.isArray(hash[key]) && relationship.options.embedded !== true) {
hash[key] = Ember.keys(hash[key]);
}
else if (Ember.isArray(hash[key])) {
throw new Error('%@ relationship %@(\'%@\') must be a key/value map in Firebase. Example: { "%@": { "%@_id": true } }'.fmt(type.toString(), relationship.kind, relationship.type.typeKey, relationship.key, relationship.type.typeKey));
}
});
}
}
});
return this._super.apply(this, arguments);
},

/**
extractSingle
*/
extractSingle: function(store, type, payload) {
return this.normalize(type, payload);
var normalizedPayload = this.normalize(type, payload);
// Check for embedded records
type.eachRelationship(function(key, relationship) {
if (!Ember.isNone(payload[key]) && relationship.options.embedded === true) {
var embeddedKey;
var embeddedRecordPayload = normalizedPayload[key];
var records = [];
var record;
for (embeddedKey in embeddedRecordPayload) {
record = embeddedRecordPayload[embeddedKey];
if (record !== null && typeof record === 'object') {
record.id = embeddedKey;
}
records.push(record);
}
normalizedPayload[key] = Ember.keys(normalizedPayload[key]);
store.pushMany(relationship.type, records);
}
});
return normalizedPayload;
},

/**
Expand Down Expand Up @@ -70,6 +85,8 @@
*/
DS.FirebaseAdapter = DS.Adapter.extend(Ember.Evented, {

defaultSerializer: '-firebase',

/**
Endpoint paths can be customized by setting the Firebase property on the
adapter:
Expand Down Expand Up @@ -163,35 +180,26 @@
var ref = this._getRef(type);
var serializer = store.serializerFor(type);

function _gotChildValue(snapshot) {
// Update store, only if the promise is already resolved.
if (!resolved) {
return;
}
var obj = snapshot.val();
if (obj !== null && typeof obj === 'object') {
obj.id = snapshot.name();
}
store.push(type, serializer.extractSingle(store, type, obj));
}

return new Ember.RSVP.Promise(function(resolve, reject) {
var _handleError = function(err) {
if (!resolved) {
resolved = true;
Ember.run(null, reject, err);
}
};

// Only add listeners to a type once
if (Ember.isNone(self._findAllMapForType[type])) {
self._findAllMapForType[type] = true;
ref.on('child_added', _gotChildValue, _handleError);
ref.on('child_changed', _gotChildValue, _handleError);
ref.on('child_added', function(snapshot) {
if (!resolved) { return; }
self._handleChildValue(store, type, serializer, snapshot);
}, _handleError);
ref.on('child_changed', function(snapshot) {
if (!resolved) { return; }
self._handleChildValue(store, type, serializer, snapshot);
}, _handleError);
ref.on('child_removed', function(snapshot) {
if (!resolved) {
return;
}
if (!resolved) { return; }
if (store.hasRecordForId(type, snapshot.name())) {
store.deleteRecord(store.getById(type, snapshot.name()));
}
Expand Down Expand Up @@ -231,6 +239,7 @@
been successfully saved to Firebase.
*/
updateRecord: function(store, type, record) {
var self = this;
var json = record.serialize({ includeId: false });
var ref = this._getRef(type, record.id);

Expand All @@ -242,19 +251,36 @@
var ids = json[key];
if (Ember.isArray(ids)) {
ids.forEach(function(id) {
var relationshipRef = ref.child(key).child(id);
var relationshipRef = self._getRelationshipRef(ref, key, id);
var deferred = Ember.RSVP.defer();
promises.push(deferred.promise);
relationshipRef.set(true, function(error) {
if (error) {
if (typeof error === 'object') {
error.location = relationshipRef.toString();
var relatedRecord;
if (store.hasRecordForId(relationship.type, id)) {
relatedRecord = store.getById(relationship.type, id);
}
if (relationship.options.embedded === true && relatedRecord && relatedRecord.get('isDirty') === true) {
relationshipRef.update(relatedRecord.serialize(), function(error) {
if (error) {
if (typeof error === 'object') {
error.location = relationshipRef.toString();
}
Ember.run(null, deferred.reject, error);
} else {
Ember.run(null, deferred.resolve, error);
}
});
}
else if (relationship.options.embedded !== true && ((relatedRecord && relatedRecord.get('isDirty') === true) || !relatedRecord)) {
relationshipRef.update(true, function(error) {
if (error) {
if (typeof error === 'object') {
error.location = relationshipRef.toString();
}
Ember.run(null, deferred.reject, error);
} else {
Ember.run(null, deferred.resolve, error);
}
deferred.reject(error);
} else {
deferred.resolve();
}
});
});
}
});
}
// Remove the relationship from the json payload
Expand All @@ -266,9 +292,9 @@
promises.push(updateDeferred.promise);
ref.update(json, function(error) {
if (error) {
updateDeferred.reject(error);
Ember.run(null, updateDeferred.reject, error);
} else {
updateDeferred.resolve();
Ember.run(null, updateDeferred.resolve);
}
});
// Wait for the record and any relationships to resolve
Expand Down Expand Up @@ -300,16 +326,7 @@
},

/**
Determines a path fo a given type. To customize, override the method:
```js
DS.FirebaseAdapter.reopen({
pathForType: function(type) {
var decamelized = Ember.String.decamelize(type);
return Ember.String.pluralize(decamelized);
}
});
```
Determines a path fo a given type
*/
pathForType: function(type) {
var camelized = Ember.String.camelize(type);
Expand Down Expand Up @@ -339,6 +356,32 @@
return ref;
},

/**
Return a Firebase ref based on a relationship key and record id
*/
_getRelationshipRef: function(ref, key, id) {
return ref.child(key).child(id);
},

/**
Push a new child record into the store
@method _handleChildValue
@private
@param {Object} store
@param {Object} type
@param {Object} serializer
@param {Object} snapshot
*/
_handleChildValue: function(store, type, serializer, snapshot) {
var obj = snapshot.val();
// Only add an id if the item is an object
if (obj !== null && typeof obj === 'object') {
obj.id = snapshot.name();
}
store.push(type, serializer.extractSingle(store, type, obj));
},

/**
Keep track of what types `.findAll()` has been called for
so duplicate listeners aren't added
Expand All @@ -347,4 +390,14 @@

});

Ember.onLoad('Ember.Application', function(Application) {
Application.initializer({
name: 'firebase',
after: 'store',
initialize: function(container, application) {
application.register('serializer:-firebase', DS.FirebaseSerializer);
}
});
});

})();
2 changes: 1 addition & 1 deletion dist/emberfire.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions examples/blog/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,7 @@ <h2>Users</h2>
<script src="js/libs/ember-1.4.0.js"></script>
<script src="js/libs/ember-data.js"></script>

<script src="https://cdn.firebase.com/js/client/1.0.6/firebase.js">
<script src="https://cdn.firebase.com/js/simple-login/1.3.0/firebase-simple-login.js"></script>
<script src="js/libs/firebase.js"></script>
<script src="../../dist/emberfire.js"></script>

<script src="js/libs/moment.js"></script>
Expand Down
4 changes: 2 additions & 2 deletions examples/blog/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
firebase: new Firebase('https://emberfire-demo.firebaseio.com')
});

App.ApplicationSerializer = DS.FirebaseSerializer.extend();
//App.ApplicationSerializer = DS.FirebaseSerializer.extend();

App.Post = DS.Model.extend({
title: DS.attr('string'),
Expand Down Expand Up @@ -302,7 +302,7 @@
});

Ember.Handlebars.helper('markdown', function(value, options) {
return new Ember.Handlebars.SafeString(markdown.toHTML(value));
return new Ember.Handlebars.SafeString(window.markdown.toHTML(value));
});

})(window);
Loading

0 comments on commit 8cea734

Please sign in to comment.