-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathexamples.js
399 lines (346 loc) · 13 KB
/
examples.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
/**
* These are just a few basic examples of how to use backbone-dynamodb.
* These examples are written as nodeunit tests. To run this script use:
*
* nodeunit examples.js
*
*
* IMPORTANT: Before running these examples, create the following DynamoDB table in your account.
*
* Table Name Primary hash key Primary range key
* AtomicCounters id (string)
* Contacts id (number)
* MyEvents calendarId (number) date (string)
*/
var _ = require( 'underscore' ),
moment = require( 'moment' ),
data = require( './example-data' ),
Backbone = require( './backbone-dynamodb' ),
atomicCounter = require( 'dynamodb-atomic-counter' );
_.mixin( require( 'underscore.deferred' ) );
/**
* backbone-dynamodb uses AWS-SDK. Visit the following page for details on how to configure AWS-SDK:
* http://docs.aws.amazon.com/AWSJavaScriptSDK/guide/node-configuring.html
*
* You can also manually configure it using the `config` object.
*/
Backbone.DynamoDB.config.update({ region: 'us-east-1' });
var CONTACTS_DATA,
EVENTS_DATA = data.events();
var Contact = Backbone.DynamoDB.Model.extend({
// No need to overwrite idAttribute or hashAttribute. Use the default: 'id'.
/**
* The name of the table is automatically determined based on the urlRoot property.
* In this case, the name of the table would be "Contacts".
*/
urlRoot: '/contacts',
/**
* Use [dynamodb-atomic-counter](https://www.npmjs.com/package/dynamodb-atomic-counter)
* to generate auto-incremented IDs.
*/
newKey: function(options) {
return atomicCounter.increment( this._tableName() );
},
});
var Contacts = Backbone.DynamoDB.Collection.extend({
/**
* The name of the table is automatically determined based on the url property.
* In this case, the name of the table would be "Contacts".
*/
url: '/contacts',
model: Contact
});
var Event = Backbone.DynamoDB.Model.extend({
/**
* The name of the primary hash key attribute.
* If a model has a range attribute, make sure to use `hashAttribute` instead of `idAttribute`.
*/
hashAttribute: 'calendarId',
/**
* The name of the primary range key attribute.
*/
rangeAttribute: 'date',
/**
* Using `tableName`, instead of `urlRoot`, to specify the "exact" name of the table
*/
tableName: 'MyEvents',
initialize: function (attributes, options) {
/**
* Use the helper method `setAttributeType` to ensure that the `attachments` attribute
* is an instance of `Backbone.DynamoDB.Collection`.
*/
this.setAttributeType( 'attachments', Backbone.DynamoDB.Collection );
}
});
var Events = Backbone.DynamoDB.Collection.extend({
model: Event,
/**
* Using `tableName`, instead of `urlRoot`, to specify the "exact" name of the table
*/
tableName: 'MyEvents'
});
exports[ 'Auto-increment IDs using dynamodb-atomic-counter.' ] = function (test) {
var count = 19,
/**
* Wrap `test.done` inside another function so that it only gets called "after"
* all contacts have been saved.
*/
done = _.after( count, function() { test.done(); } );
test.expect( count );
// Generate random contacts data
data.contacts().done(function (DATA) {
CONTACTS_DATA = DATA; // An array of randonmly generated contacts
/**
* Save only the first 19 contacts in DATA.
* The last contact in the array is left unsaved for the next test/example.
*/
_.chain( DATA ).first( count ).each(function (attributes) {
var contact = new Contact( attributes );
/**
* Save the contact to DynamoDB.
* Since it's a new contact (doesn't have an id), newKey is called right before
* saving it, which uses dynamodb-atomic-counter to generate an auto-incremented ID.
*/
contact.save().done(function (changedAttributes, options) {
// changedAttributes would be something like: { id: 1 }
test.ok( true );
}).fail(function (response, options) {
test.ok( false, 'Failed to save model to DynamoDB: ' + JSON.stringify( response.error ) + '.' );
}).always( done );
});
});
};
var lastContactId;
exports[ 'Manually assigning an ID.' ] = function (test) {
var attributes = _.last( CONTACTS_DATA ),
contact = new Contact( attributes );
test.expect( 1 );
/**
* Use the existing `newKey` method to generate an ID.
* The id attribute can be set manually in any other way, it doesn't necessarily have to be this way.
*/
contact.newKey().then(function (contactId) {
// Set the id attribute and `save` it
return contact.save({ id: contactId }); // Returns a promise
}).done(function (changedAttributes, options) {
// No id attribute was generated during the saving process so changedAttributes would be an empty object {}
test.ok( true );
lastContactId = contact.id;
}).fail(function (response, options) {
test.ok( false, 'An error has occurred: ' + JSON.stringify( response.error ) + '.' );
}).always(function () {
test.done();
});
};
exports[ 'Saving models with a number as hash key and a date as range key.' ] = function (test) {
var events = new Events( EVENTS_DATA ),
done = _.after( events.length, function() { test.done(); } );
test.expect( events.length );
events.each(function (event) {
event.save().done(function (changedAttributes, options) {
test.ok( true );
}).fail(function (response, options) {
test.ok( false, 'Failed to save model to DynamoDB: ' + JSON.stringify( response.error ) + '.' );
}).always( done );
});
};
exports[ 'Fetch a single model using ConsistentRead.' ] = function (test) {
var contact = new Contact({ id: lastContactId }),
/**
* Use ConsistentRead.
* All attributes inside `options.dynamodb` are added to the actual DynamoDB request parameters.
*/
options = {
dynamodb: {
ConsistentRead: true
}
};
test.expect( 1 );
contact.fetch( options ).done(function (dynamoDbItem, options) {
var actualAvatar = contact.get( 'avatar' ),
actualAttributes = contact.omit( 'id', 'avatar' ),
expected = _.last( CONTACTS_DATA ),
expectedAttributes = _.omit( expected, 'avatar' ),
/**
* Check if all the attributes (except 'id' and 'avatar') are equal.
*/
equalAttributes = _.isEqual( expectedAttributes, actualAttributes ),
/**
* If there's an avatar (instance of Buffer), check that they're equal.
*/
equalAvatars = expected.avatar ? expected.avatar.equals( actualAvatar ) : !contact.has( 'avatar' );
test.ok( equalAttributes && equalAvatars, 'The received attributes don\'t match the expected attributes.' );
}).fail(function (response, options) {
test.ok( false, 'Failed to fetch model: ' + JSON.stringify( response.error ) + '.' );
}).always(function () {
test.done();
});
};
exports[ 'Query using KeyConditions.' ] = function (test) {
var dummyEvent = new Event(),
pastEvents = new Events(),
/**
* Query for all "past" events in calendar number 1
*/
dynamoDbParams = {
/**
* KeyConditions can be a single condition object, for instance the following
* would query all events with a calendarId of 1:
* KeyConditions: dummyEvent.condition( 'calendarId', 'EQ', 1 )
*
* KeyConditions can also be an array of conditions, as shown here.
*/
KeyConditions: [
dummyEvent.condition( 'calendarId', 'EQ', 1 ),
dummyEvent.condition( 'date', 'LT', new Date() )
],
ConsistentRead: true // optional
};
test.expect( 1 );
pastEvents.query( dynamoDbParams ).done(function (dynamoDbItems, options) {
var now = moment(),
expected = _.filter(EVENTS_DATA, function (event) {
return event.calendarId === 1 && now.isAfter( event.date );
});
test.equal( expected.length, pastEvents.length, 'Wrong number of models. Expected: ' + expected.length + '. Actual: ' + pastEvents.length + '.' );
}).fail(function (response, options) {
test.ok( false, 'An error occurred during the Query request: ' + JSON.stringify( response.error ) + '.' );
}).always(function () {
test.done();
});
};
exports[ 'Query using KeyConditionExpression.' ] = function (test) {
var dummyEvent = new Event(),
futureEvents = new Events(),
/**
* Query for all future events in calendar number 2
*
* All ExpressionAttributeValues, with the exception of `Date` values, are automatically converted.
* For instance, { ':id': 2 } becomes { ':id': { N: '2' } }
*
* Date values must be manually serialized as shown here.
*/
dynamoDbParams = {
KeyConditionExpression: 'calendarId = :id AND #d > :now',
ExpressionAttributeNames: {
'#d': 'date'
},
ExpressionAttributeValues: {
':id': 2,
':now': dummyEvent.serializeDate( 'date', new Date() )
},
ConsistentRead: true // optional
};
test.expect( 1 );
futureEvents.query( dynamoDbParams ).done(function (dynamoDbItems, options) {
var now = moment(),
expected = _.filter(EVENTS_DATA, function (event) {
return event.calendarId === 2 && now.isBefore( event.date );
});
test.equal( expected.length, futureEvents.length, 'Wrong number of models. Expected: ' + expected.length + '. Actual: ' + futureEvents.length + '.' );
}).fail(function (response, options) {
test.ok( false, 'An error occurred during the Query request: ' + JSON.stringify( response.error ) + '.' );
}).always(function () {
test.done();
});
};
exports[ 'Scan using ScanFilter.' ] = function (test) {
var contacts = new Contacts(),
dummyContact = new Contact(),
/**
* Scan for all contacts where `isMale` equals `false`.
* ScanFilter can be a single condition, as shown here, or an array of conditions.
*/
dynamoDbParams = {
ScanFilter: dummyContact.condition( 'isMale', 'EQ', false )
};
test.expect( 1 );
contacts.scan( dynamoDbParams ).done(function (dynamoDbItems, options) {
var expected = _.where( CONTACTS_DATA, { isMale: false } );
test.equal( expected.length, contacts.length, 'Wrong number of models. Expected: ' + expected.length + '. Actual: ' + contacts.length + '.' );
}).fail(function (response, options) {
test.ok( false, 'An error occurred during the Scan request: ' + JSON.stringify( response.error ) + '.' );
}).always(function () {
test.done();
});
};
exports[ 'Scan using FilterExpression.' ] = function (test) {
var contacts = new Contacts(),
/**
* Scan for all contacts where `isMale` equals `true`
* Note that { ':val': true } is automatically converted to { ':val': { BOOL: 'true' } }.
* Any `Date` values must be manually serialized.
*/
dynamoDbParams = {
FilterExpression: 'isMale = :val',
ExpressionAttributeValues: { ':val': true }
};
test.expect( 1 );
contacts.scan( dynamoDbParams ).done(function (dynamoDbItems, options) {
var expected = _.where( CONTACTS_DATA, { isMale: true } );
test.equal( expected.length, contacts.length, 'Wrong number of models. Expected: ' + expected.length + '. Actual: ' + contacts.length + '.' );
}).fail(function (response, options) {
test.ok( false, 'An error occurred during the Scan request: ' + JSON.stringify( response.error ) + '.' );
}).always(function () {
test.done();
});
};
exports[ 'Delete a single model.' ] = function (test) {
var contact = new Contact({ id: lastContactId });
test.expect( 1 );
contact.destroy().done(function (response, options) {
test.ok( true );
}).fail(function (response, options) {
test.ok( false, 'Failed to delete model: ' + JSON.stringify( response.error ) + '.' );
}).always(function () {
test.done();
});
};
var _key;
exports[ 'Saving a model with a nested collection.' ] = function (test) {
var eventAttributes = _.last( EVENTS_DATA ),
/**
* Use the avatar in CONTACTS_DATA to generate an array of objects with two attributes:
* fileName (string)
* data (Buffer)
*/
attachmentsArray = _.chain( CONTACTS_DATA ).pluck( 'avatar' ).compact().first( 3 ).map(function (buffer) {
return {
fileName: 'image.jpg',
data: buffer
};
}).value(),
attachments = new Backbone.DynamoDB.Collection( attachmentsArray ),
/**
* evant contains the attributes: calendarId, date, title, and attachments (a Collection)
*/
event = new Event( _.extend( eventAttributes, { attachments: attachments } ) );
test.expect( 1 );
// Save the key attributes for the next example/test
_key = event.pick( 'calendarId', 'date' );
event.save().done(function (changedAttributes, options) {
test.ok( true );
}).fail(function (response, options) {
test.ok( false, 'Failed to save model to DynamoDB: ' + JSON.stringify( response.error ) + '.' );
}).always(function () {
test.done();
});
};
exports[ 'Fetching a model with a nested collection.' ] = function (test) {
// Use the _key from the previous example/test to fetch the model
var event = new Event( _key );
test.expect( 1 );
event.fetch({
dynamodb: {
ConsistentRead: true
}
}).done(function (dynamoDbItem, options) {
var attachments = event.get( 'attachments' );
// Check that the attachments attributes is an instance of Backbone.DynamoDB.Collection
test.ok( attachments instanceof Backbone.DynamoDB.Collection, 'The "attachments" attribute is not the expected type.' );
}).fail(function (response, options) {
test.ok( false, 'Error occurred when fetching model: ' + JSON.stringify( response.error ) + '.' );
}).always(function () {
test.done();
});
};