backbone-associations-rails 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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: