dynamoid 1.0.0 → 1.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9a8130b39add7870e0217be2f31fc55808f02be5
4
- data.tar.gz: 73f7caa9ae29e070fcae4deb87461f7a004d0470
3
+ metadata.gz: d03723c3178362472031077f90ef4d732c9f6a59
4
+ data.tar.gz: d5255b0e842355e872056f10c2619f9c9609e96a
5
5
  SHA512:
6
- metadata.gz: 1e73c399b586b8c264f1b19dcb99e121705a82aaea2efcf68b4b250d1b35d30065ddd3c2fa213c6226260063dd8afd3beb6cd78a6ff7c1130fe92486544392ec
7
- data.tar.gz: 89d9baede7e7b40c1e15ffa358b84199c8b5cdc9ea8fb855083da2b63651b1f8304b4afe845c0f7b9b1771a9957e0e0a57881663ad40d67542a0aaa9175605d7
6
+ metadata.gz: de28cab30c3bd935e588f481986fddc5ad25e46a40a0e5a9675524e71e112f67333f2a663b8951253742b33e655ec556381834acf122c9cb484829c68beb48dc
7
+ data.tar.gz: 2514d74830f6f8fa4f427eb0ddbed13eec808aaa437a7cac7dc1772a8eb4592dbb1d35551b426cad71ed0e693e6c9d2d532f7d2d1949f72c59c6712136582615
@@ -0,0 +1,21 @@
1
+ # 1.1.0
2
+
3
+ * Added support for optimistic locking on delete (PR #29, sumocoder)
4
+ * upgrade concurrent-ruby requirement to 1.0 (PR #31, keithmgould)
5
+
6
+ # 1.0.0
7
+
8
+ * Add support for AWS SDK v2.
9
+ * Add support for custom class type for fields.
10
+ * Remove partitioning support.
11
+ * Remove support for Dynamoid's (pseudo)indexes, now that DynamoDB offers
12
+ local and global indexes.
13
+ * Rename :float field type to :number.
14
+ * Rename Chain#limit to Chain#eval_limit.
15
+
16
+ Housekeeping:
17
+
18
+ * Switch from `fake_dynamo` for unit tests to DynamoDBLocal. This is the new authoritative
19
+ implementation of DynamoDB for testing, and it supports AWS SDK v2.
20
+ * Use Travis CI to auto-run unit tests on multiple Rubies.
21
+ * Randomize spec order.
@@ -364,6 +364,7 @@ Also, without contributors the project wouldn't be nearly as awesome. So many th
364
364
  Running the tests is fairly simple. In one window, run `bin/start_dynamodblocal`, and in the other, use `rake`.
365
365
 
366
366
  [![Build Status](https://travis-ci.org/Dynamoid/Dynamoid.svg)](https://travis-ci.org/Dynamoid/Dynamoid)
367
+ [![Coverage Status](https://coveralls.io/repos/Dynamoid/Dynamoid/badge.svg?branch=master&service=github)](https://coveralls.io/github/Dynamoid/Dynamoid?branch=master)
367
368
 
368
369
  ## Copyright
369
370
 
@@ -2,20 +2,20 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = "dynamoid"
5
- s.version = "1.0.0"
5
+ s.version = "1.1.0"
6
6
 
7
7
  # Keep in sync with README
8
- s.authors = %w(
9
- Josh Symonds
10
- Logan Bowers
11
- Craig Heneveld
12
- Anatha Kumaran
13
- Jason Dew
14
- Luis Arias
15
- Stefan Neculai
16
- Philip White
17
- Peeyush Kumar
18
- )
8
+ s.authors = [
9
+ 'Josh Symonds',
10
+ 'Logan Bowers',
11
+ 'Craig Heneveld',
12
+ 'Anatha Kumaran',
13
+ 'Jason Dew',
14
+ 'Luis Arias',
15
+ 'Stefan Neculai',
16
+ 'Philip White',
17
+ 'Peeyush Kumar',
18
+ ]
19
19
  s.description = "Dynamoid is an ORM for Amazon's DynamoDB that supports offline development, associations, querying, and everything else you'd expect from an ActiveRecord-style replacement."
20
20
  s.extra_rdoc_files = [
21
21
  "LICENSE.txt",
@@ -23,6 +23,7 @@ Gem::Specification.new do |s|
23
23
  ]
24
24
  # file list is generated with `git ls-files | grep -v -E -e '^spec/' -e '^\.' -e 'bin/'`
25
25
  s.files = %w(
26
+ CHANGELOG.md
26
27
  Gemfile
27
28
  LICENSE.txt
28
29
  README.markdown
@@ -62,12 +63,13 @@ Gem::Specification.new do |s|
62
63
 
63
64
  s.add_runtime_dependency(%q<activemodel>, ["~> 4"])
64
65
  s.add_runtime_dependency(%q<aws-sdk-resources>, ["~> 2"])
65
- s.add_runtime_dependency(%q<concurrent-ruby>, ["~> 0.9"])
66
+ s.add_runtime_dependency(%q<concurrent-ruby>, [">= 1.0"])
66
67
  s.add_development_dependency(%q<rake>, [">= 0"])
67
68
  s.add_development_dependency(%q<rspec>, ["~> 3"])
68
69
  s.add_development_dependency(%q<bundler>, [">= 0"])
69
70
  s.add_development_dependency(%q<yard>, [">= 0"])
70
71
  s.add_development_dependency(%q<github-markup>, [">= 0"])
71
72
  s.add_development_dependency(%q<pry>, [">= 0"])
73
+ s.add_development_dependency(%q<coveralls>, [">= 0"])
72
74
  end
73
75
 
@@ -65,7 +65,7 @@ module Dynamoid
65
65
  results = client.batch_get_item(
66
66
  request_items: request_items
67
67
  )
68
-
68
+
69
69
  ret = Hash.new([].freeze) # Default for tables where no rows are returned
70
70
  results.data[:responses].each do |table, rows|
71
71
  ret[table] = rows.collect { |r| result_item_to_hash(r) }
@@ -107,14 +107,14 @@ module Dynamoid
107
107
  read_capacity = options[:read_capacity] || Dynamoid::Config.read_capacity
108
108
  write_capacity = options[:write_capacity] || Dynamoid::Config.write_capacity
109
109
  range_key = options[:range_key]
110
-
110
+
111
111
  key_schema = [
112
112
  { attribute_name: key.to_s, key_type: HASH_KEY }
113
113
  ]
114
- key_schema << {
114
+ key_schema << {
115
115
  attribute_name: range_key.keys.first.to_s, key_type: RANGE_KEY
116
116
  } if(range_key)
117
-
117
+
118
118
  #TODO: Provide support for number and binary hash key
119
119
  attribute_definitions = [
120
120
  { attribute_name: key.to_s, attribute_type: 'S' }
@@ -122,10 +122,10 @@ module Dynamoid
122
122
  attribute_definitions << {
123
123
  attribute_name: range_key.keys.first.to_s, attribute_type: api_type(range_key.values.first)
124
124
  } if(range_key)
125
-
125
+
126
126
  client.create_table(table_name: table_name,
127
127
  provisioned_throughput: {
128
- read_capacity_units: read_capacity,
128
+ read_capacity_units: read_capacity,
129
129
  write_capacity_units: write_capacity
130
130
  },
131
131
  key_schema: key_schema,
@@ -144,9 +144,17 @@ module Dynamoid
144
144
  # @since 1.0.0
145
145
  #
146
146
  # @todo: Provide support for various options http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#delete_item-instance_method
147
- def delete_item(table_name, key, options = nil)
147
+ def delete_item(table_name, key, options = {})
148
+ range_key = options[:range_key]
149
+ conditions = options[:conditions]
148
150
  table = describe_table(table_name)
149
- client.delete_item(table_name: table_name, key: key_stanza(table, key, options && options[:range_key]))
151
+ client.delete_item(
152
+ table_name: table_name,
153
+ key: key_stanza(table, key, range_key),
154
+ expected: expected_stanza(conditions)
155
+ )
156
+ rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException => e
157
+ raise Dynamoid::Errors::ConditionalCheckFailedException, e
150
158
  end
151
159
 
152
160
  # Deletes an entire table from DynamoDB.
@@ -231,7 +239,7 @@ module Dynamoid
231
239
  # @todo: Provide support for various options http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#put_item-instance_method
232
240
  def put_item(table_name, object, options = nil)
233
241
  item = {}
234
-
242
+
235
243
  object.each do |k, v|
236
244
  next if v.nil? || (v.respond_to?(:empty?) && v.empty?)
237
245
  item[k.to_s] = v
@@ -342,10 +350,10 @@ module Dynamoid
342
350
  def scan(table_name, scan_hash, select_opts = {})
343
351
  limit = select_opts.delete(:limit)
344
352
  batch = select_opts.delete(:batch_size)
345
-
353
+
346
354
  request = { table_name: table_name }
347
355
  request[:limit] = batch || limit if batch || limit
348
- request[:scan_filter] = scan_hash.reduce({}) do |memo, kvp|
356
+ request[:scan_filter] = scan_hash.reduce({}) do |memo, kvp|
349
357
  memo[kvp[0].to_s] = {
350
358
  attribute_value_list: [kvp[1]],
351
359
  # TODO: Provide support for all comparison operators
@@ -369,7 +377,7 @@ module Dynamoid
369
377
  end
370
378
  end
371
379
  end
372
-
380
+
373
381
 
374
382
  #
375
383
  # Truncates all records in the given table
@@ -381,7 +389,7 @@ module Dynamoid
381
389
  table = describe_table(table_name)
382
390
  hk = table.hash_key
383
391
  rk = table.range_key
384
-
392
+
385
393
  scan(table_name, {}, {}).each do |attributes|
386
394
  opts = {range_key: attributes[rk.to_sym] } if rk
387
395
  delete_item(table_name, attributes[hk], opts)
@@ -393,7 +401,7 @@ module Dynamoid
393
401
  end
394
402
 
395
403
  protected
396
-
404
+
397
405
  STRING_TYPE = "S".freeze
398
406
  NUM_TYPE = "N".freeze
399
407
  BOOLEAN_TYPE = "B".freeze
@@ -418,7 +426,7 @@ module Dynamoid
418
426
  key[table.range_key.to_s] = range_key if range_key
419
427
  key
420
428
  end
421
-
429
+
422
430
  #
423
431
  # @param [Hash] conditions Conditions to enforce on operation (e.g. { :if => { :count => 5 }, :unless_exists => ['id']})
424
432
  # @return an Expected stanza for the given conditions hash
@@ -426,20 +434,20 @@ module Dynamoid
426
434
  def expected_stanza(conditions = nil)
427
435
  expected = Hash.new { |h,k| h[k] = {} }
428
436
  return expected unless conditions
429
-
437
+
430
438
  conditions[:unless_exists].try(:each) do |col|
431
439
  expected[col.to_s][:exists] = false
432
440
  end
433
441
  conditions[:if].try(:each) do |col,val|
434
442
  expected[col.to_s][:value] = val
435
443
  end
436
-
444
+
437
445
  expected
438
446
  end
439
-
447
+
440
448
  HASH_KEY = "HASH".freeze
441
449
  RANGE_KEY = "RANGE".freeze
442
-
450
+
443
451
  #
444
452
  # New, semi-arbitrary API to get data on the table
445
453
  #
@@ -448,7 +456,7 @@ module Dynamoid
448
456
  table_cache[table_name] = Table.new(client.describe_table(table_name: table_name).data)
449
457
  end
450
458
  end
451
-
459
+
452
460
  #
453
461
  # Converts a hash returned by get_item, scan, etc. into a key-value hash
454
462
  #
@@ -457,35 +465,35 @@ module Dynamoid
457
465
  item.each { |k,v| r[k.to_sym] = v }
458
466
  end
459
467
  end
460
-
468
+
461
469
  #
462
470
  # Represents a table. Exposes data from the "DescribeTable" API call, and also
463
471
  # provides methods for coercing values to the proper types based on the table's schema data
464
472
  #
465
473
  class Table
466
474
  attr_reader :schema
467
-
475
+
468
476
  #
469
477
  # @param [Hash] schema Data returns from a "DescribeTable" call
470
478
  #
471
479
  def initialize(schema)
472
480
  @schema = schema[:table]
473
481
  end
474
-
482
+
475
483
  def range_key
476
484
  @range_key ||= schema[:key_schema].find { |d| d[:key_type] == RANGE_KEY }.try(:attribute_name)
477
485
  end
478
-
486
+
479
487
  def range_type
480
- range_type ||= schema[:attribute_definitions].find { |d|
488
+ range_type ||= schema[:attribute_definitions].find { |d|
481
489
  d[:attribute_name] == range_key
482
490
  }.try(:fetch,:attribute_type, nil)
483
491
  end
484
-
492
+
485
493
  def hash_key
486
494
  @hash_key ||= schema[:key_schema].find { |d| d[:key_type] == HASH_KEY }.try(:attribute_name).to_sym
487
495
  end
488
-
496
+
489
497
  #
490
498
  # Returns the API type (e.g. "N", "S") for the given column, if the schema defines it,
491
499
  # nil otherwise
@@ -500,31 +508,31 @@ module Dynamoid
500
508
  schema[:item_count]
501
509
  end
502
510
  end
503
-
511
+
504
512
  #
505
- # Mimics behavior of the yielded object on DynamoDB's update_item API (high level).
513
+ # Mimics behavior of the yielded object on DynamoDB's update_item API (high level).
506
514
  #
507
515
  class ItemUpdater
508
516
  attr_reader :table, :key, :range_key
509
-
517
+
510
518
  def initialize(table, key, range_key = nil)
511
519
  @table = table; @key = key, @range_key = range_key
512
520
  @additions = {}
513
521
  @deletions = {}
514
522
  @updates = {}
515
523
  end
516
-
524
+
517
525
  #
518
- # Adds the given values to the values already stored in the corresponding columns.
519
- # The column must contain a Set or a number.
526
+ # Adds the given values to the values already stored in the corresponding columns.
527
+ # The column must contain a Set or a number.
520
528
  #
521
- # @param [Hash] vals keys of the hash are the columns to update, vals are the values to
529
+ # @param [Hash] vals keys of the hash are the columns to update, vals are the values to
522
530
  # add. values must be a Set, Array, or Numeric
523
531
  #
524
532
  def add(values)
525
533
  @additions.merge!(values)
526
534
  end
527
-
535
+
528
536
  #
529
537
  # Removes values from the sets of the given columns
530
538
  #
@@ -538,19 +546,19 @@ module Dynamoid
538
546
  #
539
547
  # Replaces the values of one or more attributes
540
548
  #
541
- def set(values)
549
+ def set(values)
542
550
  @updates.merge!(values)
543
551
  end
544
-
552
+
545
553
  #
546
554
  # Returns an AttributeUpdates hash suitable for passing to the V2 Client API
547
555
  #
548
556
  def to_h
549
557
  ret = {}
550
-
558
+
551
559
  @additions.each do |k,v|
552
- ret[k.to_s] = {
553
- action: ADD,
560
+ ret[k.to_s] = {
561
+ action: ADD,
554
562
  value: v
555
563
  }
556
564
  end
@@ -569,7 +577,7 @@ module Dynamoid
569
577
 
570
578
  ret
571
579
  end
572
-
580
+
573
581
  ADD = "ADD".freeze
574
582
  DELETE = "DELETE".freeze
575
583
  PUT = "PUT".freeze
@@ -171,8 +171,8 @@ module Dynamoid
171
171
  end
172
172
 
173
173
  #
174
- # update!() will increment the lock_version if the table has the column, but will not check it. Thus, a concurrent save will
175
- # never cause an update! to fail, but an update! may cause a concurrent save to fail.
174
+ # update!() will increment the lock_version if the table has the column, but will not check it. Thus, a concurrent save will
175
+ # never cause an update! to fail, but an update! may cause a concurrent save to fail.
176
176
  #
177
177
  #
178
178
  def update!(conditions = {}, &block)
@@ -216,7 +216,21 @@ module Dynamoid
216
216
  # @since 0.2.0
217
217
  def delete
218
218
  options = range_key ? {:range_key => dump_field(self.read_attribute(range_key), self.class.attributes[range_key])} : {}
219
+
220
+ # Add an optimistic locking check if the lock_version column exists
221
+ if(self.class.attributes[:lock_version])
222
+ conditions = {:if => {}}
223
+ conditions[:if][:lock_version] =
224
+ if changes[:lock_version].nil?
225
+ self.lock_version
226
+ else
227
+ changes[:lock_version][0]
228
+ end
229
+ options[:conditions] = conditions
230
+ end
219
231
  Dynamoid.adapter.delete(self.class.table_name, self.hash_key, options)
232
+ rescue Dynamoid::Errors::ConditionalCheckFailedException
233
+ raise Dynamoid::Errors::StaleObjectError.new(self, 'delete')
220
234
  end
221
235
 
222
236
  # Dump this object's attributes into hash form, fit to be persisted into the datastore.
@@ -268,14 +282,14 @@ module Dynamoid
268
282
  end
269
283
  end
270
284
  end
271
-
285
+
272
286
  # Persist the object into the datastore. Assign it an id first if it doesn't have one.
273
287
  #
274
288
  # @since 0.2.0
275
289
  def persist(conditions = nil)
276
290
  run_callbacks(:save) do
277
291
  self.hash_key = SecureRandom.uuid if self.hash_key.nil? || self.hash_key.blank?
278
-
292
+
279
293
  # Add an exists check to prevent overwriting existing records with new ones
280
294
  if(new_record?)
281
295
  conditions ||= {}
metadata CHANGED
@@ -1,31 +1,22 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dynamoid
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
- - Josh
8
- - Symonds
9
- - Logan
10
- - Bowers
11
- - Craig
12
- - Heneveld
13
- - Anatha
14
- - Kumaran
15
- - Jason
16
- - Dew
17
- - Luis
18
- - Arias
19
- - Stefan
20
- - Neculai
21
- - Philip
22
- - White
23
- - Peeyush
24
- - Kumar
7
+ - Josh Symonds
8
+ - Logan Bowers
9
+ - Craig Heneveld
10
+ - Anatha Kumaran
11
+ - Jason Dew
12
+ - Luis Arias
13
+ - Stefan Neculai
14
+ - Philip White
15
+ - Peeyush Kumar
25
16
  autorequire:
26
17
  bindir: bin
27
18
  cert_chain: []
28
- date: 2015-11-25 00:00:00.000000000 Z
19
+ date: 2015-12-30 00:00:00.000000000 Z
29
20
  dependencies:
30
21
  - !ruby/object:Gem::Dependency
31
22
  name: activemodel
@@ -59,16 +50,16 @@ dependencies:
59
50
  name: concurrent-ruby
60
51
  requirement: !ruby/object:Gem::Requirement
61
52
  requirements:
62
- - - "~>"
53
+ - - ">="
63
54
  - !ruby/object:Gem::Version
64
- version: '0.9'
55
+ version: '1.0'
65
56
  type: :runtime
66
57
  prerelease: false
67
58
  version_requirements: !ruby/object:Gem::Requirement
68
59
  requirements:
69
- - - "~>"
60
+ - - ">="
70
61
  - !ruby/object:Gem::Version
71
- version: '0.9'
62
+ version: '1.0'
72
63
  - !ruby/object:Gem::Dependency
73
64
  name: rake
74
65
  requirement: !ruby/object:Gem::Requirement
@@ -153,6 +144,20 @@ dependencies:
153
144
  - - ">="
154
145
  - !ruby/object:Gem::Version
155
146
  version: '0'
147
+ - !ruby/object:Gem::Dependency
148
+ name: coveralls
149
+ requirement: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ type: :development
155
+ prerelease: false
156
+ version_requirements: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
156
161
  description: Dynamoid is an ORM for Amazon's DynamoDB that supports offline development,
157
162
  associations, querying, and everything else you'd expect from an ActiveRecord-style
158
163
  replacement.
@@ -163,6 +168,7 @@ extra_rdoc_files:
163
168
  - LICENSE.txt
164
169
  - README.markdown
165
170
  files:
171
+ - CHANGELOG.md
166
172
  - Gemfile
167
173
  - LICENSE.txt
168
174
  - README.markdown
@@ -213,7 +219,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
213
219
  version: '0'
214
220
  requirements: []
215
221
  rubyforge_project:
216
- rubygems_version: 2.4.5.1
222
+ rubygems_version: 2.5.1
217
223
  signing_key:
218
224
  specification_version: 4
219
225
  summary: Dynamoid is an ORM for Amazon's DynamoDB