backbone-associations-rails 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -4,7 +4,7 @@ This gem vendors the [Backbone-associations](https://github.com/dhruvaray/backbo
4
4
 
5
5
  ## Dependencies
6
6
 
7
- ```backbone-associations``` requires Backbone.js >= 0.9.2 and Underscore.js >= 1.3.1
7
+ ```backbone-associations``` requires Backbone.js >= 0.9.2 and Underscore.js >= 1.3.3
8
8
 
9
9
  ## Installation
10
10
 
@@ -1,7 +1,7 @@
1
1
  module Backbone
2
2
  module Associations
3
3
  module Rails
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.0'
5
5
  end
6
6
  end
7
7
  end
@@ -1,222 +1,356 @@
1
1
  //
2
- // Backbone-associations.js 0.2.0
2
+ // Backbone-associations.js 0.3.0
3
3
  //
4
4
  // (c) 2012 Dhruva Ray, Jaynti Kanani
5
- //
6
- // Backbone-associations may be freely distributed under the MIT license; see the accompanying LICENSE.txt.
7
- //
8
- // Depends on [Backbone](https://github.com/documentcloud/backbone) (and thus on [Underscore](https://github.com/documentcloud/underscore/) as well)
9
- //
5
+ // Backbone-associations may be freely distributed under the MIT license;
6
+ // see the accompanying LICENSE.txt.
7
+ // Depends on [Backbone](https://github.com/documentcloud/backbone)
8
+ // and [Underscore](https://github.com/documentcloud/underscore/) as well.
10
9
  // A complete [Test & Benchmark Suite](../test/test-suite.html) is included for your perusal.
11
10
 
12
11
  // Initial Setup
13
12
  // --------------
14
- (function() {
15
- // The top-level namespace. All public Backbone classes and modules will be attached to this.
16
- // Exported for the browser and CommonJS.
17
- var _, Backbone;
18
- if ( typeof window === 'undefined' ) {
19
- _ = require( 'underscore' );
20
- Backbone = require( 'backbone' );
21
- exports = module.exports = Backbone;
22
- }
23
- else {
13
+ (function () {
14
+ "use strict";
15
+
16
+ // The top-level namespace. All public Backbone classes and modules will be attached to this.
17
+ // Exported for the browser and CommonJS.
18
+ var _, Backbone, BackboneModel, defaultEvents, AssociatedModel;
19
+ if (typeof require !== 'undefined') {
20
+ _ = require('underscore');
21
+ Backbone = require('backbone');
22
+ exports = module.exports = Backbone;
23
+ } else {
24
24
  _ = window._;
25
- Backbone = window.Backbone;
26
- exports = window;
27
- }
25
+ Backbone = window.Backbone;
26
+ }
27
+ // Create local reference `Model` prototype.
28
+ BackboneModel = Backbone.Model.prototype;
29
+ // Built-in Backbone `events`.
30
+ defaultEvents = ["change", "add", "remove", "reset", "destroy", "sync", "error", "sort", "request"];
31
+
28
32
  // Backbone.AssociatedModel
29
33
  // --------------
30
34
 
31
- //Add `Many` and `One` relations to Backbone Object
32
- Backbone.Many = "Many";
33
- Backbone.One = "One";
34
- //Define `AssociatedModel` (Extends Backbone.Model)
35
- Backbone.AssociatedModel = Backbone.Model.extend({
36
- //Define relations with Associated Model
37
- relations: undefined,
38
- //Set a hash of model attributes on the object,
39
- //firing `"change"` unless you choose to silence it.
40
- //It maintains relations between models during the set operation. It also bubbles up child events to the parent.
41
- set: function( key, value, options ) {
42
- var attributes,processedRelations,tbp;
43
- // Duplicate backbone's behavior to allow separate key/value parameters, instead of a single 'attributes' object.
44
- if ( _.isObject( key ) || key == null ) {
35
+ //Add `Many` and `One` relations to Backbone Object.
36
+ Backbone.Many = "Many";
37
+ Backbone.One = "One";
38
+ // Define `AssociatedModel` (Extends Backbone.Model).
39
+ AssociatedModel = Backbone.AssociatedModel = Backbone.Model.extend({
40
+ // Define relations with Associated Model.
41
+ relations:undefined,
42
+ // Define `Model` property which can keep track of already fired `events`,
43
+ // and prevent redundant event to be triggered in case of circular model graph.
44
+ _proxyCalls:undefined,
45
+ // Set a hash of model attributes on the object,
46
+ // fire Backbone `event` with options.
47
+ // It maintains relations between models during the set operation.
48
+ // It also bubbles up child events to the parent.
49
+ set:function (key, value, options) {
50
+ var attributes, processedRelations, tbp, attr;
51
+ // Duplicate backbone's behavior to allow separate key/value parameters,
52
+ // instead of a single 'attributes' object.
53
+ if (_.isObject(key) || key == null) {
45
54
  attributes = key;
46
55
  options = value;
47
- }
48
- else {
56
+ } else {
49
57
  attributes = {};
50
- attributes[ key ] = value;
51
- }
58
+ attributes[key] = value;
59
+ }
52
60
  // Extract attributes and options.
53
61
  options || (options = {});
54
- if(!attributes) return;
55
- if (options.unset) for (attr in attributes) attributes[attr] = void 0;
56
- //Check for existence of relations in this model
57
- if(this.relations){
58
- //Iterate over `this.relations` and `set` model and collection values
59
- _.each(this.relations ,function(relation){
60
- if (attributes[relation.key] != undefined ){
61
- //Get value of attribute with relation key in `val`
62
- var val = getValue(attributes,relation.key);
63
- //Get class if relation is stored as a string
64
- relation.relatedModel && _.isString(relation.relatedModel) && (relation.relatedModel = eval(relation.relatedModel));
65
- relation.collectionType && _.isString(relation.collectionType) && (relation.collectionType = eval(relation.collectionType));
66
- //Track reference change of associated model for `change:attribute` event
67
- var refChanged = false;
68
- //If `relation` defines model as associated collection...
69
- if(relation.type === Backbone.Many){
70
- //`relation.collectionType` should be instance of `Backbone.Collection`
71
- if(relation.collectionType && !relation.collectionType.prototype instanceof Backbone.Collection){
72
- throw new Error( 'collectionType must inherit from Backbone.Collection' );
73
- }
74
- //Create new `Backbone.Collection` with `collectionType`
75
- if(!this.attributes[relation.key]){
76
- this.attributes[relation.key] = relation.collectionType ? new relation.collectionType() : this._createCollection(relation.relatedModel);
77
- refChanged = true;
62
+ if (!attributes) return this;
63
+ if (options.unset) for (attr in attributes) attributes[attr] = void 0;
64
+
65
+ if (this.relations) {
66
+ // Iterate over `this.relations` and `set` model and collection values
67
+ // if `relations` are available.
68
+ _.each(this.relations, function (relation) {
69
+ var relationKey = relation.key, relatedModel = relation.relatedModel,
70
+ collectionType = relation.collectionType,
71
+ val, relationOptions, data;
72
+ if (attributes[relationKey]) {
73
+ //Get value of attribute with relation key in `val`.
74
+ val = _.result(attributes, relationKey);
75
+ // Get class if relation is stored as a string.
76
+ relatedModel && _.isString(relatedModel) && (relatedModel = eval(relatedModel));
77
+ collectionType && _.isString(collectionType) && (collectionType = eval(collectionType));
78
+ // Merge in `options` specific to this relation.
79
+ relationOptions = relation.options ? _.extend({}, relation.options, options) : options;
80
+ // If `relation.type` is `Backbone.Many`,
81
+ // create `Backbone.Collection` with passed data and perform Backbone `set`.
82
+ if (relation.type === Backbone.Many) {
83
+ // `collectionType` of defined `relation` should be instance of `Backbone.Collection`.
84
+ if (collectionType && !collectionType.prototype instanceof Backbone.Collection) {
85
+ throw new Error('collectionType must inherit from Backbone.Collection');
78
86
  }
79
- // Get all models if `val` is already instanceof `Backbone.Collection`
80
- if(val instanceof Backbone.Collection){
81
- val = val.models;
82
- }
83
- //Resetting new Collection with new value and options
84
- this.attributes[relation.key].reset(val,options);
85
- }
86
- //If `relation` defines model as associated model...
87
- else if(relation.type == Backbone.One && relation.relatedModel){
88
- //Create New `Backbone.Model` using `relation.relatedModel` if `attributes` is not null
89
- if(!this.attributes[relation.key]){
90
- //If `val` is already instance of `AssociatedModel`, reserve `relation.key` for `Backbone.Model.prototype.set`
91
- if(val instanceof Backbone.AssociatedModel) return;
92
- this.attributes[relation.key] = new relation.relatedModel();
93
- refChanged = true;
87
+
88
+ if (val instanceof Backbone.Collection) {
89
+ BackboneModel.set.call(this, relationKey, val, relationOptions);
90
+ } else if (!this.attributes[relationKey]) {
91
+ // If `attributes` has no property present,
92
+ // create `Collection` having `relation.collectionType` as type and
93
+ // `relation.Model` as model reference, and perform Backbone `set`.
94
+ data = collectionType ? new collectionType() : this._createCollection(relatedModel);
95
+ data.add(val, relationOptions);
96
+ BackboneModel.set.call(this, relationKey, data, relationOptions);
97
+ } else {
98
+ this.attributes[relationKey].reset(val, relationOptions);
94
99
  }
95
- //If the new attributes is a smaller subset, then use the default values for that attribute - if available.
96
- else{
97
- var opt = {};
98
- var defaults = getValue(this.attributes[relation.key], 'defaults');
99
- _.each(this.attributes[relation.key].attributes,function(value,key){
100
- !_.has(val,key) && (opt[key] = (defaults ? defaults[key] : void 0));
101
- });
102
- this.attributes[relation.key].set(opt,{silent:true});
103
- }
104
- //Set `val` to model with options
105
- this.attributes[relation.key].set(val,options);
100
+
101
+ } else if (relation.type === Backbone.One && relatedModel) {
102
+ // If passed data is not instance of `Backbone.AssociatedModel`,
103
+ // create `AssociatedModel` and perform backbone `set`.
104
+ data = val instanceof AssociatedModel ? val : new relatedModel(val);
105
+ BackboneModel.set.call(this, relationKey, data, relationOptions)
106
106
  }
107
- //Add proxy events to respective parents
108
- this.attributes[relation.key].off("all");
109
- this.attributes[relation.key].on("all",function(){return this.trigger.apply(this,arguments);},this);
110
- //If reference has changed, trigger `change:attribute` event
111
- refChanged && this.trigger('change:'+relation.key,options);
112
- //Create a local `processedRelations` array to store the relation key which has been processed.
113
- //We cannot use `this.relations` because if there is no value defined for `relation.key`, it will not get processed by either Backbone `set` or the `AssociatedModel` set
114
- !processedRelations && (processedRelations=[]);
115
- if(_.indexOf(processedRelations,relation.key)===-1){
116
- processedRelations.push(relation.key);
117
- }
118
- }
119
- },this);
120
- };
121
- if(processedRelations){
122
- //Find attributes yet to be processed - `tbp`
107
+
108
+ // Add proxy events to respective parents.
109
+ // Only add callback if not defined.
110
+ if (!this.attributes[relationKey]._proxyCallback) {
111
+ this.attributes[relationKey]._proxyCallback = function () {
112
+ var args = arguments,
113
+ opt = args[0].split(":"),
114
+ eventType = opt[0],
115
+ eventObject = args[1],
116
+ indexEventObject = -1,
117
+ _proxyCalls = this.attributes[relationKey]._proxyCalls,
118
+ eventPath,
119
+ eventAvailable;
120
+
121
+ // Change the event name to a fully qualified path.
122
+ if (_.contains(defaultEvents, eventType)) {
123
+ if (opt && _.size(opt) > 1) {
124
+ eventPath = opt[1];
125
+ }
126
+ // Find the specific object in the collection which has changed.
127
+ if (this.attributes[relationKey] instanceof Backbone.Collection && "change" === eventType) {
128
+ indexEventObject = _.indexOf(this.attributes[relationKey].models, eventObject);
129
+ }
130
+ // Manipulate `eventPath`.
131
+ eventPath = relationKey + (indexEventObject !== -1 ?
132
+ "[" + indexEventObject + "]" : "") + (eventPath ? "." + eventPath : "");
133
+ args[0] = eventType + ":" + eventPath;
134
+
135
+ if (_proxyCalls) {
136
+ // If event has been already triggered as result of same source `eventPath`,
137
+ // no need to re-trigger event to prevent cycle.
138
+ eventAvailable = _.find(_proxyCalls, function (value, eventKey) {
139
+ // `event` ends with eventKey.
140
+ var d = eventPath.length - eventKey.length;
141
+ return eventPath.indexOf(eventKey, d) !== -1;
142
+ });
143
+ if (eventAvailable) {
144
+ return this;
145
+ }
146
+ } else {
147
+ _proxyCalls = this.attributes[relationKey]._proxyCalls = {};
148
+ }
149
+ // Add `eventPath` in `_proxyCalls` to keep track of already triggered `event`.
150
+ _proxyCalls[eventPath] = true;
151
+ }
152
+
153
+ // Bubble up event to parent `model` with new changed arguments.
154
+ this.trigger.apply(this, args);
155
+
156
+ // Remove `eventPath` from `_proxyCalls`,
157
+ // if `eventPath` and `_proxCalls` are available,
158
+ // which allow event to be triggered on for next operation of `set`.
159
+ if (eventPath && _proxyCalls) {
160
+ delete _proxyCalls[eventPath];
161
+ }
162
+ return this;
163
+ };
164
+ this.attributes[relationKey].on("all", this.attributes[relationKey]._proxyCallback, this);
165
+ }
166
+
167
+ // Create a local `processedRelations` array to store the relation key which has been processed.
168
+ // We cannot use `this.relations` because if there is no value defined for `relationKey`,
169
+ // it will not get processed by either `BackboneModel` `set` or the `AssociatedModel` `set`.
170
+ !processedRelations && (processedRelations = []);
171
+ if (_.indexOf(processedRelations, relationKey) === -1) {
172
+ processedRelations.push(relationKey);
173
+ }
174
+ }
175
+ }, this);
176
+ }
177
+ if (processedRelations) {
178
+ // Find attributes yet to be processed - `tbp`.
123
179
  tbp = {};
124
- for(var key in attributes){
125
- if(_.indexOf(processedRelations,key)===-1){
126
- tbp[key] = attributes[key];
180
+ for (attr in attributes) {
181
+ if (_.indexOf(processedRelations, attr) === -1) {
182
+ tbp[attr] = attributes[attr];
127
183
  }
128
184
  }
129
- }
130
- //Set all `attributes` to `tbp`
131
- else{
185
+ } else {
186
+ // Set all `attributes` to `tbp`.
132
187
  tbp = attributes;
133
- }
134
- //Returns results for `Backbone.Model.prototype.set`
135
- return Backbone.Model.prototype.set.call( this, tbp , options);
136
- },
137
- //Returns New `collection` of type `relation.relatedModel`
138
- _createCollection: function(type) {
139
- var collection;
140
- if ( type && type.prototype instanceof Backbone.AssociatedModel ) {
141
- //Creates new `Backbone.Collection` and defines model class
188
+ }
189
+ // Return results for `BackboneModel.set`.
190
+ return BackboneModel.set.call(this, tbp, options);
191
+ },
192
+ // Returns New `collection` of type `relation.relatedModel`.
193
+ _createCollection:function (type) {
194
+ var collection, relatedModel = type;
195
+ _.isString(relatedModel) && (relatedModel = eval(relatedModel));
196
+ // Creates new `Backbone.Collection` and defines model class.
197
+ if (relatedModel && relatedModel.prototype instanceof AssociatedModel) {
142
198
  collection = new Backbone.Collection();
143
- collection.model = type;
144
- }
145
- else{
146
- throw new Error( 'type must inherit from Backbone.AssociatedModel' );
199
+ collection.model = relatedModel;
200
+ } else {
201
+ throw new Error('type must inherit from Backbone.AssociatedModel');
147
202
  }
148
203
  return collection;
149
204
  },
150
- // `trigger` the event for `Associated Model`
151
- trigger : function(){
152
- //Check & Add `visited` tag to prevent event of cycle
153
- if(!this.visited){
154
- // mark as `visited`
205
+ // Has the model changed. Traverse the object hierarchy to compute dirtyness.
206
+ hasChanged:function (attr) {
207
+ var isDirty, relation, attrValue, i, dirtyObjects;
208
+ // To prevent cycles, check if this node is visited.
209
+ if (!this.visitedHC) {
210
+ this.visitedHC = true;
211
+ isDirty = BackboneModel.hasChanged.apply(this, arguments);
212
+ if (!isDirty && this.relations) {
213
+ // Go down the hierarchy to see if anything has `changed`.
214
+ for (i = 0; i < this.relations.length; ++i) {
215
+ relation = this.relations[i];
216
+ attrValue = this.attributes[relation.key];
217
+ if (attrValue) {
218
+ if (attrValue instanceof Backbone.Collection) {
219
+ dirtyObjects = attrValue.filter(function (m) {
220
+ return m.hasChanged() === true;
221
+ });
222
+ _.size(dirtyObjects) > 0 && (isDirty = true);
223
+ } else {
224
+ isDirty = attrValue.hasChanged && attrValue.hasChanged();
225
+ }
226
+ if (isDirty) {
227
+ break;
228
+ }
229
+ }
230
+ }
231
+ }
232
+ delete this.visitedHC;
233
+ }
234
+ return !!isDirty;
235
+ },
236
+ // Returns a hash of the changed attributes.
237
+ changedAttributes:function (diff) {
238
+ var delta, relation, attrValue, changedCollection, i;
239
+ // To prevent cycles, check if this node is visited.
240
+ if (!this.visited) {
241
+ this.visited = true;
242
+ delta = BackboneModel.changedAttributes.apply(this, arguments);
243
+ if (this.relations) {
244
+ for (i = 0; i < this.relations.length; ++i) {
245
+ relation = this.relations[i];
246
+ attrValue = this.attributes[relation.key];
247
+ if (attrValue) {
248
+ if (attrValue instanceof Backbone.Collection) {
249
+ changedCollection = _.filter(attrValue.map(function (m) {
250
+ return m.changedAttributes();
251
+ }), function (m) {
252
+ return !!m;
253
+ });
254
+ if (_.size(changedCollection) > 0) {
255
+ delta[relation.key] = changedCollection;
256
+ }
257
+ } else if (attrValue instanceof AssociatedModel && attrValue.hasChanged()) {
258
+ delta[relation.key] = attrValue.toJSON();
259
+ }
260
+ }
261
+ }
262
+ }
263
+ delete this.visited;
264
+ }
265
+ return !delta ? false : delta;
266
+ },
267
+ // Returns the hash of the previous attributes of the graph.
268
+ previousAttributes:function () {
269
+ var pa, attrValue, pattrValue, pattrJSON;
270
+ // To prevent cycles, check if this node is visited.
271
+ if (!this.visited) {
155
272
  this.visited = true;
156
- Backbone.Model.prototype.trigger.apply(this,arguments);
157
- //delete `visited` tag to allow trigger for next `set` operation
273
+ pa = BackboneModel.previousAttributes.apply(this, arguments);
274
+ if (this.relations) {
275
+ _.each(this.relations, function (relation) {
276
+ attrValue = this.attributes[relation.key];
277
+ pattrValue = pa[relation.key];
278
+ pattrJSON = pattrValue ? pattrValue.toJSON() : undefined;
279
+ if (pattrValue && pattrValue == attrValue) {
280
+ if (attrValue instanceof AssociatedModel) {
281
+ pa[relation.key] = attrValue.previousAttributes();
282
+ } else if (attrValue instanceof Backbone.Collection) {
283
+ pa[relation.key] = attrValue.map(function (m) {
284
+ return m.previousAttributes();
285
+ });
286
+ }
287
+ } else {
288
+ if (pattrValue)
289
+ pa[relation.key] = pattrJSON;
290
+ }
291
+ }, this);
292
+ }
158
293
  delete this.visited;
159
294
  }
160
- return this;
295
+ return pa;
296
+ },
297
+ // Return the previous value of the passed in attribute.
298
+ previous:function (attr) {
299
+ return this.previousAttributes()[attr];
161
300
  },
162
- //The JSON representation of the model.
163
- toJSON : function(){
164
- var json;
165
- if(!this.visited){
301
+ // The JSON representation of the model.
302
+ toJSON:function (options) {
303
+ var json, aJson;
304
+ if (!this.visited) {
166
305
  this.visited = true;
167
- //Get json representation from `Backbone.Model.prototype.toJSON`
168
- json = Backbone.Model.prototype.toJSON.apply( this, arguments );
169
- //If `this.relations` is defined, iterate through each `relation` and added it's json representation to parents' json representation
170
- if(this.relations){
171
- _.each(this.relations ,function(relation){
306
+ // Get json representation from `BackboneModel.toJSON`.
307
+ json = BackboneModel.toJSON.apply(this, arguments);
308
+ // If `this.relations` is defined, iterate through each `relation`
309
+ // and added it's json representation to parents' json representation.
310
+ if (this.relations) {
311
+ _.each(this.relations, function (relation) {
172
312
  var attr = this.attributes[relation.key];
173
- if(attr){
174
- aJson = attr.toJSON();
175
- json[relation.key] = _.isArray(aJson)?_.compact(aJson):aJson;
313
+ if (attr) {
314
+ aJson = attr.toJSON(options);
315
+ json[relation.key] = _.isArray(aJson) ? _.compact(aJson) : aJson;
176
316
  }
177
- },this);
317
+ }, this);
178
318
  }
179
319
  delete this.visited;
180
320
  }
181
321
  return json;
182
322
  },
183
- //deep `clone` the model.
184
- clone : function(){
185
- var cloneObj;
186
- if(!this.visited){
323
+ // Deep `clone` the model.
324
+ clone:function () {
325
+ var cloneObj, newCollection;
326
+ if (!this.visited) {
187
327
  this.visited = true;
188
- //Get shallow clone from `Backbone.Model.prototype.clone`
189
- cloneObj = Backbone.Model.prototype.clone.apply( this, arguments );
190
- //If `this.relations` is defined, iterate through each `relation` and `clone`
191
- if(this.relations){
192
- _.each(this.relations ,function(relation){
193
- if(this.attributes[relation.key]){
328
+ // Get shallow clone from `BackboneModel.clone`.
329
+ cloneObj = BackboneModel.clone.apply(this, arguments);
330
+ // If `this.relations` is defined, iterate through each `relation` and `clone`.
331
+ if (this.relations) {
332
+ _.each(this.relations, function (relation) {
333
+ if (this.attributes[relation.key]) {
194
334
  var sourceObj = cloneObj.attributes[relation.key];
195
- if(sourceObj instanceof Backbone.Collection){
196
- //Creates new `collection` using `relation`
197
- var newCollection = relation.collectionType ? new relation.collectionType() : this._createCollection(relation.relatedModel);
198
- //Added each `clone` model to `newCollection`
199
- sourceObj.each(function(model){
200
- var mClone = model.clone()
201
- mClone && newCollection.add(mClone);
202
- });
335
+ if (sourceObj instanceof Backbone.Collection) {
336
+ // Creates new `collection` using `relation`.
337
+ newCollection = relation.collectionType ? new relation.collectionType() :
338
+ this._createCollection(relation.relatedModel);
339
+ // Added each `clone` model to `newCollection`.
340
+ sourceObj.each(function (model) {
341
+ var mClone = model.clone();
342
+ mClone && newCollection.add(mClone);
343
+ });
203
344
  cloneObj.attributes[relation.key] = newCollection;
204
- }
205
- else if(sourceObj instanceof Backbone.Model){
345
+ } else if (sourceObj instanceof Backbone.Model) {
206
346
  cloneObj.attributes[relation.key] = sourceObj.clone();
207
347
  }
208
348
  }
209
- },this);
349
+ }, this);
210
350
  }
211
351
  delete this.visited;
212
352
  }
213
353
  return cloneObj;
214
- }
215
- });
216
- // Duplicate Backbone's behavior. To get a value from a Backbone object as a property
217
- // or as a function.
218
- var getValue = function(object, prop) {
219
- if (!(object && object[prop])) return null;
220
- return _.isFunction(object[prop]) ? object[prop]() : object[prop];
221
- };
222
- })();
354
+ }
355
+ });
356
+ })();
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: backbone-associations-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors: