Skip to content

Commit

Permalink
Add a findModel method to allow customization of the matching for c…
Browse files Browse the repository at this point in the history
…reation/updates by `findOrCreate`.
  • Loading branch information
PaulUithol committed Jan 15, 2014
1 parent a4f3774 commit 8371089
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 16 deletions.
29 changes: 20 additions & 9 deletions backbone-relational.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -395,8 +395,8 @@
* @param {String|Number|Object|Backbone.RelationalModel} item * @param {String|Number|Object|Backbone.RelationalModel} item
*/ */
find: function( type, item ) { find: function( type, item ) {
var id = this.resolveIdForItem( type, item ); var id = this.resolveIdForItem( type, item ),
var coll = this.getCollection( type ); coll = this.getCollection( type );


// Because the found object could be of any of the type's superModel // Because the found object could be of any of the type's superModel
// types, only return it if it's actually of the type asked for. // types, only return it if it's actually of the type asked for.
Expand Down Expand Up @@ -1306,7 +1306,7 @@
// Find (or create) a model for each one that is to be fetched // Find (or create) a model for each one that is to be fetched
var created = []; var created = [];
models = _.map( idsToFetch, function( id ) { models = _.map( idsToFetch, function( id ) {
var model = Backbone.Relational.store.find( rel.relatedModel, id ); var model = rel.relatedModel.findModel( id );


if ( !model ) { if ( !model ) {
var attrs = {}; var attrs = {};
Expand Down Expand Up @@ -1677,7 +1677,7 @@
this._superModel.inheritRelations(); this._superModel.inheritRelations();
if ( this._superModel.prototype.relations ) { if ( this._superModel.prototype.relations ) {
// Find relations that exist on the '_superModel', but not yet on this model. // Find relations that exist on the '_superModel', but not yet on this model.
var inheritedRelations = _.select( this._superModel.prototype.relations || [], function( superRel ) { var inheritedRelations = _.filter( this._superModel.prototype.relations || [], function( superRel ) {
return !_.any( this.prototype.relations || [], function( rel ) { return !_.any( this.prototype.relations || [], function( rel ) {
return superRel.relatedModel === rel.relatedModel && superRel.key === rel.key; return superRel.relatedModel === rel.relatedModel && superRel.key === rel.key;
}, this ); }, this );
Expand All @@ -1695,9 +1695,9 @@


/** /**
* Find an instance of `this` type in 'Backbone.Relational.store'. * Find an instance of `this` type in 'Backbone.Relational.store'.
* - If `attributes` is a string or a number, `findOrCreate` will just query the `store` and return a model if found. * A new model is created with `attributes` (unless `options.create` is explicitly set to `false`) if no match is found.
* - If `attributes` is a string or a number, `findOrCreate` will query the `store` and return a model if found.
* - If `attributes` is an object and is found in the store, the model will be updated with `attributes` unless `options.update` is `false`. * - If `attributes` is an object and is found in the store, the model will be updated with `attributes` unless `options.update` is `false`.
* Otherwise, a new model is created with `attributes` (unless `options.create` is explicitly set to `false`).
* @param {Object|String|Number} attributes Either a model's id, or the attributes used to create or update a model. * @param {Object|String|Number} attributes Either a model's id, or the attributes used to create or update a model.
* @param {Object} [options] * @param {Object} [options]
* @param {Boolean} [options.create=true] * @param {Boolean} [options.create=true]
Expand All @@ -1710,8 +1710,9 @@
var parsedAttributes = ( _.isObject( attributes ) && options.parse && this.prototype.parse ) ? var parsedAttributes = ( _.isObject( attributes ) && options.parse && this.prototype.parse ) ?
this.prototype.parse( _.clone( attributes ) ) : attributes; this.prototype.parse( _.clone( attributes ) ) : attributes;


// Try to find an instance of 'this' model type in the store // If specified, use a custom `find` function to match up existing models to the given attributes.
var model = Backbone.Relational.store.find( this, parsedAttributes ); // Otherwise, try to find an instance of 'this' model type in the store
var model = this.findModel( parsedAttributes );


// If we found an instance, update it with the data in 'item' (unless 'options.merge' is false). // If we found an instance, update it with the data in 'item' (unless 'options.merge' is false).
// If not, create an instance (unless 'options.create' is false). // If not, create an instance (unless 'options.create' is false).
Expand All @@ -1733,7 +1734,7 @@


/** /**
* Find an instance of `this` type in 'Backbone.Relational.store'. * Find an instance of `this` type in 'Backbone.Relational.store'.
* - If `attributes` is a string or a number, `find` will just query the `store` and return a model if found. * - If `attributes` is a string or a number, `find` will query the `store` and return a model if found.
* - If `attributes` is an object and is found in the store, the model will be updated with `attributes` unless `options.update` is `false`. * - If `attributes` is an object and is found in the store, the model will be updated with `attributes` unless `options.update` is `false`.
* @param {Object|String|Number} attributes Either a model's id, or the attributes used to create or update a model. * @param {Object|String|Number} attributes Either a model's id, or the attributes used to create or update a model.
* @param {Object} [options] * @param {Object} [options]
Expand All @@ -1745,6 +1746,16 @@
options || ( options = {} ); options || ( options = {} );
options.create = false; options.create = false;
return this.findOrCreate( attributes, options ); return this.findOrCreate( attributes, options );
},

/**
* A hook to override the matching when updating (or creating) a model.
* The default implementation is to look up the model by id in the store.
* @param {Object} attributes
* @returns {Backbone.RelationalModel}
*/
findModel: function( attributes ) {
return Backbone.Relational.store.find( this, attributes );
} }
}); });
_.extend( Backbone.RelationalModel.prototype, Backbone.Semaphore ); _.extend( Backbone.RelationalModel.prototype, Backbone.Semaphore );
Expand Down
75 changes: 68 additions & 7 deletions index.html
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
<li><a href="#RelationalModel-build">build</a></li> <li><a href="#RelationalModel-build">build</a></li>
<li><a href="#RelationalModel-findOrCreate">findOrCreate</a></li> <li><a href="#RelationalModel-findOrCreate">findOrCreate</a></li>
<li><a href="#RelationalModel-find">find</a></li> <li><a href="#RelationalModel-find">find</a></li>
<li><a href="#RelationalModel-findModel">findModel</a></li>
</ul> </ul>
<ul> <ul>
<li><a href="#RelationalModel-events"><strong>Catalog of Events</strong></a></li> <li><a href="#RelationalModel-events"><strong>Catalog of Events</strong></a></li>
Expand Down Expand Up @@ -140,13 +141,18 @@ <h1>
Backbone-relational is hosted on <a href="https://github.com/PaulUithol/Backbone-relational">GitHub</a>, Backbone-relational is hosted on <a href="https://github.com/PaulUithol/Backbone-relational">GitHub</a>,
and is available under the <a href="https://github.com/PaulUithol/Backbone-relational/blob/master/LICENSE.txt">MIT license</a>. and is available under the <a href="https://github.com/PaulUithol/Backbone-relational/blob/master/LICENSE.txt">MIT license</a>.
</p> </p>
<p>
Thanks go out to <a href="http://progressiveplanning.com">Progressive Planning</a> for allowing me the time to
work on Backbone-relational!
</p>
</section> </section>


<section id="downloads"> <section id="downloads">
<h2> <h2>
Downloads &amp; Dependencies Downloads &amp; Dependencies
<span style="padding-left: 7px; font-size:11px; font-weight: normal;" class="interface">(Right-click, and use <span style="padding-left: 7px; font-size:11px; font-weight: normal;" class="interface">
"Save As")</span> (Right-click, and use "Save As")
</span>
</h2> </h2>


<table> <table>
Expand Down Expand Up @@ -854,7 +860,7 @@ <h4 class="code">
In this case, you should call <q>setup</q> manually after defining your subclass CoffeeScript-style. For example: In this case, you should call <q>setup</q> manually after defining your subclass CoffeeScript-style. For example:
</p> </p>
<p class="warning"> <p class="warning">
Note: this is a static method. It operate on the model type itself, not on an instance. Note: this is a static method. It operates on the model type itself, not on an instance of it.
</p> </p>
</section> </section>


Expand All @@ -878,7 +884,7 @@ <h4 class="code">
Create an instance of a model, taking into account what submodels have been defined. Create an instance of a model, taking into account what submodels have been defined.
</p> </p>
<p class="warning"> <p class="warning">
Note: this is a static method. It operate on the model type itself, not on an instance. Note: this is a static method. It operates on the model type itself, not on an instance.
</p> </p>
</section> </section>


Expand Down Expand Up @@ -907,7 +913,7 @@ <h4 class="code">
as a function (<q>this</q> will not point to a model), instead of as a method.</dd> as a function (<q>this</q> will not point to a model), instead of as a method.</dd>
</dl> </dl>
<p class="warning"> <p class="warning">
Note: this is a static method. It operate on the model type itself, not on an instance. Note: this is a static method. It operates on the model type itself, not on an instance of it.
</p> </p>
</section> </section>


Expand All @@ -924,7 +930,62 @@ <h4 class="code">
Accepts the same options as <q>findOrCreate</q> (except for <q>create</q>). Accepts the same options as <q>findOrCreate</q> (except for <q>create</q>).
</p> </p>
<p class="warning"> <p class="warning">
Note: this is a static method. It operate on the model type itself, not on an instance. Note: this is a static method. It operates on the model type itself, not on an instance of it.
</p>
</section>

<section id="RelationalModel-findModel">
<h4 class="code">
findModel
<code>relationalModel.findModel(attributes&lt;string|number|object&gt;)</code>
</h4>
<p>
Returns: <q>Backbone.RelationalModel</q> A model instance.
</p>
<p>
A hook to override the matching when updating (or creating) a model.
The default implementation is to look up the model by id in the store:
<q>return Backbone.Relational.store.find( this, attributes );</q>
</p>
<p>
Custom behavior is useful in cases where (a collection of) nested data gets saved to the server.
Consider saving the following model:
</p>

<pre class="language-javascript"><code class="language-javascript runnable" data-setup="#example-zoo"><!--
-->var zoo = new Zoo( { id: 1, name: 'Artis', animals: [
{ species: 'Giraffe' },
{ species: 'Camel' }
] } );

alert( JSON.stringify( zoo.toJSON(), null, 4 ) );
</code></pre>
<p>
Normally, whatever you use as server-side logic will respond by creating two animals, and assigning them
an id. The response will be used by Backbone-relational to update existing models.
However, updating a model starts by looking up the local model with the same id; and in this case,
Backbone-relational does not know which local models corresponds to which created animal with an id.
A simple fix in this case would be to add a fallback option for the matching by using the animal's
species. Do note you'd want to use a more robust method usually, such as using a new model's <q>cid</q>.
</p>
<pre class="language-javascript"><code class="language-javascript runnable" data-setup="#example-zoo"><!--
-->Zoo.findModel = function( attributes ) {
// Try to find an instance of 'this' model type in the store
var model = Backbone.Relational.store.find( this, attributes );

if ( !model && _.isObject( attributes ) ) {
var coll = Backbone.Relational.store.getCollection( this );

model = coll.find( function( m ) {
return m.species === attributes.species;
});
}

return model;
};
</code></pre>
<p class="warning">
Note: this is a static method. It operates on the model type itself, not on an instance of it.
</p> </p>
</section> </section>


Expand Down Expand Up @@ -1182,7 +1243,7 @@ <h4>0.8.6
</li> </li>
<li> <li>
<a href="https://github.com/PaulUithol/Backbone-relational/pull/380"><q>#380</q></a>: <a href="https://github.com/PaulUithol/Backbone-relational/pull/380"><q>#380</q></a>:
AFix <q>pop</q> on an empty collection. Fix <q>pop</q> on an empty collection.
</li> </li>
<li> <li>
<a href="https://github.com/PaulUithol/Backbone-relational/pull/370"><q>#370</q></a>: <a href="https://github.com/PaulUithol/Backbone-relational/pull/370"><q>#370</q></a>:
Expand Down

0 comments on commit 8371089

Please sign in to comment.