dynamini 2.7.0 → 2.7.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7e1c5e5210674b1e75d93ca770697f796b9ca42e
4
- data.tar.gz: 4de485ac6fa5fad8375104f6e3049abf3a542d09
3
+ metadata.gz: 15802466753d1d5ac670e2d1649cecb5f4cf8334
4
+ data.tar.gz: b2c044abc94835728ab8b356be4f771ef3246f2c
5
5
  SHA512:
6
- metadata.gz: 141816813ba6319ebbede710503514cc1a88a55a2f915bc65e7b5d450e21b1ebd96e9df0b4fa6a1d0bf1e27e4f682d8f2eff3c13c87e824d8228dff608bfe1a2
7
- data.tar.gz: 49a6b6842b43c606b6e766709d11de5ca8ef0da0eee8039446a3d00a0b8f165ff6ac1aff5a0d88869b62d9a0dd886c00413f6be51802ebf2a844960db5644712
6
+ metadata.gz: a6224689aaedb675bbdb9ab84fddd710184666e6357c525affc799d4d5befc5c04a640d68785df8352e3282ea5c3a9d69bf187885656df60382a65aa89700abe
7
+ data.tar.gz: 09bccbb64058d707bb4cc72f7af7ee50ef1ea6daaa3ef172c869d29ec304d7eeaa6179ca269cf243341bd369d40a56b56396ee57edd3de473b3609da791a46c6
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dynamini (2.6.8)
4
+ dynamini (2.7.3)
5
5
  activemodel (>= 3, < 5.0)
6
6
  aws-sdk (~> 2)
7
7
 
@@ -101,4 +101,4 @@ DEPENDENCIES
101
101
  rspec (~> 3)
102
102
 
103
103
  BUNDLED WITH
104
- 1.12.5
104
+ 1.13.6
data/README.md CHANGED
@@ -40,10 +40,12 @@ Instance methods:
40
40
  We've included ActiveModel::Validations, so any validators will still work and be triggered by the save/create methods.
41
41
  There are also some new functions specific to DynamoDB's API:
42
42
 
43
- * find_or_nil(hash_key, range_key) - since ActiveRecord's find_by isn't applicable to noSQL, use this method if you want a .find that doesn't raise exceptions when the item doesn't exist
43
+ * find_or_nil(hash_key, range_key) - since ActiveRecord's find_by isn't applicable to noSQL, use this method if you want a .find that doesn't raise exceptions when the item doesn't exist
44
44
  * batch_find([keys]) - to retrieve multiple objects at once.
45
45
  * increment!({attribute1: amount, attribute2: amount}) - to update your record using DynamoDB's Atomic Counter functionality. (For more information, see http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithItems.html#WorkingWithItems.AtomicCounters )
46
46
  * add_to(attribute, value) - If you use this to modify your attribute, when saving, Dynamini will update that attribute with ADD instead of PUT. Your attribute must be handled as an addable type - :integer, :float, :array, :set, :date, or :time. (For more information on ADD actions, see http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_UpdateItem.html )
47
+ * delete_attribute(attribute) - Use this to delete an attribute from a record completely, rather than just blanking it or nullifying it.
48
+ * delete_attribute!(attrubute) - same as above but with an included save!
47
49
 
48
50
  ## Configuration
49
51
  In application.rb, or in initializers/dynamini.rb, include your AWS settings like so:
@@ -65,7 +67,7 @@ class Vehicle < Dynamini::Base
65
67
  set_table_name 'cars-dev' # must match the table name configured in AWS
66
68
  set_hash_key :model # defaults to :id if not set
67
69
  set_range_key :vin # must be set if your AWS table is configured with a range key
68
-
70
+
69
71
  # ...All the rest of your class methods, instance methods, and validators
70
72
  end
71
73
  ```
@@ -111,17 +113,17 @@ You can save arrays and sets to your Dynamini model. Optionally, you can have Dy
111
113
  ```ruby
112
114
  class Vehicle < Dynamini::Base
113
115
  set_hash_key :vin
114
- handle :parts, :array, of: :symbol # :of accepts all types except :set and :array
116
+ handle :parts, :array, of: :symbol # :of accepts all types except :set and :array
115
117
  handle :other_array, :array
116
118
  end
117
119
 
118
- car = Vehicle.new(vin: 'H3LL0')
120
+ car = Vehicle.create(vin: 'H3LL0')
119
121
  car.parts
120
122
  > []
121
123
 
122
124
  car.parts = ['wheel']
123
125
  car.parts
124
- > :wheel
126
+ > [:wheel]
125
127
 
126
128
  car.parts = ['wheel', 'brakes', 'seat']
127
129
  car.parts
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'dynamini'
3
- s.version = '2.7.0'
3
+ s.version = '2.7.3'
4
4
  s.summary = 'DynamoDB interface'
5
5
  s.description = 'Lightweight DynamoDB interface gem designed as
6
6
  a drop-in replacement for ActiveRecord.
@@ -0,0 +1,110 @@
1
+ module Dynamini
2
+ module Attributes
3
+
4
+ ADDABLE_TYPES = [:set, :array, :integer, :float, :time, :date]
5
+ DELETED_TOKEN = '__deleted__'
6
+
7
+ attr_reader :attributes
8
+
9
+ def assign_attributes(attributes)
10
+ attributes.each do |key, value|
11
+ write_attribute(key, value)
12
+ end
13
+ nil
14
+ end
15
+
16
+ def update_attribute(key, value, options = {})
17
+ write_attribute(key, value)
18
+ save!(options)
19
+ end
20
+
21
+ def update_attributes(attributes, options = {})
22
+ assign_attributes(attributes)
23
+ save!(options)
24
+ end
25
+
26
+ def add_to(attribute, value)
27
+ complain_about(attribute) unless self.class.handles[attribute]
28
+ old_value = read_attribute(attribute)
29
+ add_value = self.class.attribute_callback(TypeHandler::SETTER_PROCS, self.class.handles[attribute], value, true)
30
+ if ADDABLE_TYPES.include? self.class.handles[attribute][:format]
31
+ @attributes[attribute] ? @attributes[attribute] += add_value : @attributes[attribute] = add_value
32
+ else
33
+ complain_about(attribute)
34
+ end
35
+ record_change(attribute, old_value, add_value, 'ADD')
36
+ self
37
+ end
38
+
39
+ def delete_attribute(attribute)
40
+ old_value = read_attribute(attribute)
41
+ record_change(attribute, old_value, DELETED_TOKEN, 'DELETE')
42
+ @attributes.delete(attribute)
43
+ end
44
+
45
+ def delete_attribute!(attribute)
46
+ delete_attribute(attribute)
47
+ save!
48
+ end
49
+
50
+ private
51
+
52
+ def attribute_updates
53
+ changes.reduce({}) do |updates, (key, value)|
54
+ current_value = value[1]
55
+ updates[key] = { value: current_value, action: value[2] || 'PUT' }
56
+ updates
57
+ end
58
+ end
59
+
60
+ def method_missing(name, *args, &block)
61
+ if write_method?(name)
62
+ write_attribute(attribute_name(name), args.first)
63
+ elsif was_method?(name)
64
+ __was(name)
65
+ elsif read_method?(name)
66
+ read_attribute(name)
67
+ else
68
+ super
69
+ end
70
+ end
71
+
72
+ def attribute_name(name)
73
+ name[0..-2].to_sym
74
+ end
75
+
76
+ def read_method?(name)
77
+ name =~ /^([a-zA-Z][-_\w]*)[^=?]*$/
78
+ end
79
+
80
+ def write_method?(name)
81
+ name =~ /^([a-zA-Z][-_\w]*)=.*$/
82
+ end
83
+
84
+ def complain_about(attribute)
85
+ raise ArgumentError, "#{attribute.capitalize} is not handled as an addable type. Addable types are set, array, integer, float, time, and date."
86
+ end
87
+
88
+ def respond_to_missing?(name, include_private = false)
89
+ @attributes.keys.include?(name) || write_method?(name) || was_method?(name) || super
90
+ end
91
+
92
+ def write_attribute(attribute, new_value, change: true, **options)
93
+ old_value = read_attribute(attribute)
94
+ if (handle = self.class.handles[attribute.to_sym]) && !new_value.nil?
95
+ new_value = self.class.attribute_callback(TypeHandler::SETTER_PROCS, handle, new_value, change)
96
+ end
97
+ @attributes[attribute] = new_value
98
+ record_change(attribute, old_value, new_value, options[:action]) if change && new_value != old_value
99
+ end
100
+
101
+ def read_attribute(name)
102
+ value = @attributes[name]
103
+ if (handle = self.class.handles[name.to_sym])
104
+ value = handle[:options][:default] if value.nil?
105
+ value = self.class.attribute_callback(TypeHandler::GETTER_PROCS, handle, value, false) unless value.nil?
106
+ end
107
+ value
108
+ end
109
+ end
110
+ end
@@ -4,7 +4,7 @@ require_relative 'client_interface'
4
4
  require_relative 'dirty'
5
5
  require_relative 'increment'
6
6
  require_relative 'type_handler'
7
- require_relative 'adder'
7
+ require_relative 'attributes'
8
8
  require_relative 'errors'
9
9
 
10
10
  module Dynamini
@@ -18,9 +18,8 @@ module Dynamini
18
18
  include Dynamini::ClientInterface
19
19
  include Dynamini::Dirty
20
20
  include Dynamini::Increment
21
- include Dynamini::Adder
21
+ include Dynamini::Attributes
22
22
 
23
- attr_reader :attributes
24
23
  class_attribute :handles
25
24
 
26
25
  self.handles = {
@@ -93,23 +92,6 @@ module Dynamini
93
92
  hash_key == other.hash_key if other.is_a?(self.class)
94
93
  end
95
94
 
96
- def assign_attributes(attributes)
97
- attributes.each do |key, value|
98
- write_attribute(key, value)
99
- end
100
- nil
101
- end
102
-
103
- def update_attribute(key, value, options = {})
104
- write_attribute(key, value)
105
- save!(options)
106
- end
107
-
108
- def update_attributes(attributes, options = {})
109
- assign_attributes(attributes)
110
- save!(options)
111
- end
112
-
113
95
  def save(options = {})
114
96
  run_callbacks :save do
115
97
  @changes.empty? || (valid? && trigger_save(options))
@@ -176,59 +158,5 @@ module Dynamini
176
158
  key_hash[self.range_key] = handled_key(self.range_key, range_value) if self.range_key
177
159
  key_hash
178
160
  end
179
-
180
- def attribute_updates
181
- changes.reduce({}) do |updates, (key, value)|
182
- current_value = value[1]
183
- updates[key] = { value: current_value, action: value[2] || 'PUT' }
184
- updates
185
- end
186
- end
187
-
188
- def method_missing(name, *args, &block)
189
- if write_method?(name)
190
- write_attribute(attribute_name(name), args.first)
191
- elsif was_method?(name)
192
- __was(name)
193
- elsif read_method?(name)
194
- read_attribute(name)
195
- else
196
- super
197
- end
198
- end
199
-
200
- def attribute_name(name)
201
- name[0..-2].to_sym
202
- end
203
-
204
- def read_method?(name)
205
- name =~ /^([a-zA-Z][-_\w]*)[^=?]*$/
206
- end
207
-
208
- def write_method?(name)
209
- name =~ /^([a-zA-Z][-_\w]*)=.*$/
210
- end
211
-
212
- def respond_to_missing?(name, include_private = false)
213
- @attributes.keys.include?(name) || write_method?(name) || was_method?(name) || super
214
- end
215
-
216
- def write_attribute(attribute, new_value, change: true, **options)
217
- old_value = read_attribute(attribute)
218
- if (handle = self.class.handles[attribute.to_sym]) && !new_value.nil?
219
- new_value = self.class.attribute_callback(TypeHandler::SETTER_PROCS, handle, new_value, change)
220
- end
221
- @attributes[attribute] = new_value
222
- record_change(attribute, old_value, new_value, options[:action]) if change && new_value != old_value
223
- end
224
-
225
- def read_attribute(name)
226
- value = @attributes[name]
227
- if (handle = self.class.handles[name.to_sym])
228
- value = handle[:options][:default] if value.nil?
229
- value = self.class.attribute_callback(TypeHandler::GETTER_PROCS, handle, value, false) unless value.nil?
230
- end
231
- value
232
- end
233
161
  end
234
162
  end
@@ -229,11 +229,14 @@ module Dynamini
229
229
  def handle_updates(args, hash_key_value, range_key_value, attribute_hash)
230
230
  table = get_table(args[:table_name])
231
231
  args[:attribute_updates].each do |k, v|
232
- if v[:action] == 'ADD' && table[hash_key_value]
233
- # if record has been saved
232
+ if v[:action] == 'ADD' && table[hash_key_value] # if record has been saved, otherwise use a simple setter for add
234
233
  data = table[hash_key_value]
235
234
  data = (data[range_key_value] ||= {}) if range_key_value
236
235
  attribute_hash[k] = data[k] ? data[k] + v[:value] : v[:value]
236
+ elsif v[:action] == 'DELETE'
237
+ data = table[hash_key_value]
238
+ data = (data[range_key_value] ||= {}) if range_key_value
239
+ data.delete(k)
237
240
  else
238
241
  attribute_hash[k] = v[:value]
239
242
  end
@@ -0,0 +1,230 @@
1
+ require 'spec_helper'
2
+
3
+ describe Dynamini::Attributes do
4
+
5
+ class HandledModel < Dynamini::Base
6
+ handle :price, :float
7
+ handle :things, :set
8
+ handle :stuff, :array
9
+ handle :when, :date
10
+ handle :what_time, :time
11
+ handle :widget_count, :integer
12
+ handle :new_set, :set
13
+ handle :new_array, :array
14
+ end
15
+
16
+ let(:model_attributes) {
17
+ {
18
+ name: 'Widget',
19
+ price: 9.99,
20
+ id: 'abcd1234',
21
+ hash_key: '009',
22
+ things: Set.new([1,2,3]),
23
+ stuff: [4,5,6]
24
+ }
25
+ }
26
+
27
+ let(:model) { HandledModel.new(model_attributes) }
28
+
29
+ describe '#attributes' do
30
+ it 'should return all attributes of the object' do
31
+ expect(model.attributes).to include model_attributes
32
+ end
33
+ end
34
+
35
+ describe '#assign_attributes' do
36
+ it 'should return nil' do
37
+ expect(model.assign_attributes(price: '5')).to be_nil
38
+ end
39
+
40
+ it 'should update the attributes of the model' do
41
+ model.assign_attributes(price: '5')
42
+ expect(model.attributes[:price]).to eq(5.0)
43
+ end
44
+
45
+ it 'should append changed attributes to @changed' do
46
+ model.save
47
+ model.assign_attributes(name: 'Widget', price: '5')
48
+ expect(model.changed).to eq ['price']
49
+ end
50
+ end
51
+
52
+ describe '#update_attribute' do
53
+
54
+ it 'should update the attribute and save the object' do
55
+ expect(model).to receive(:save!)
56
+ model.update_attribute(:name, 'Widget 2.0')
57
+ expect(model.name).to eq('Widget 2.0')
58
+ end
59
+ end
60
+
61
+ describe '#update_attributes' do
62
+ it 'should update multiple attributes and save the object' do
63
+ expect(model).to receive(:save!)
64
+ model.update_attributes(name: 'Widget 2.0', price: '12.00')
65
+ expect(model.attributes).to include(name: 'Widget 2.0', price: 12.00)
66
+ end
67
+ end
68
+
69
+ describe 'reader method' do
70
+ it 'responds to handled columns but not unhandled columns' do
71
+ expect(model).to respond_to(:price)
72
+ expect(model).not_to respond_to(:foo)
73
+ end
74
+
75
+ context 'existing attribute' do
76
+ it 'should return the attribute' do
77
+ expect(model.price).to eq(9.99)
78
+ end
79
+ end
80
+
81
+ context 'new attribute' do
82
+ before { model.description = 'test model' }
83
+ it 'should return the attribute' do
84
+ expect(model.description).to eq('test model')
85
+ end
86
+ end
87
+
88
+ context 'nonexistent attribute' do
89
+ it 'should return nil' do
90
+ expect(model.foo).to be_nil
91
+ end
92
+ end
93
+
94
+ context 'attribute set to nil' do
95
+ before { model.price = nil }
96
+ it 'should return nil' do
97
+ expect(model.price).to be_nil
98
+ end
99
+ end
100
+ end
101
+
102
+ describe 'writer method' do
103
+ it 'responds to handled columns but not unhandled columns' do
104
+ expect(model).to respond_to(:price=)
105
+ end
106
+
107
+ context 'existing attribute' do
108
+ before { model.price = '1' }
109
+ it 'should overwrite the attribute' do
110
+ expect(model.price).to eq(1.0)
111
+ end
112
+ end
113
+ context 'new attribute' do
114
+ before { model.foo = 'bar' }
115
+ it 'should write to the attribute' do
116
+ expect(model.foo).to eq('bar')
117
+ end
118
+ end
119
+
120
+ context 'arrays' do
121
+ it 'should write to the attribute and switch type freely' do
122
+ model.foo = ['bar', 'baz']
123
+ expect(model.foo).to eq(['bar', 'baz'])
124
+ model.foo = ['quux']
125
+ expect(model.foo).to eq(['quux'])
126
+ model.foo = 'zort'
127
+ expect(model.foo).to eq('zort')
128
+ model.foo = []
129
+ expect(model.foo).to eq([])
130
+ end
131
+ end
132
+ end
133
+
134
+ describe '#delete_attribute' do
135
+ context 'the attribute exists' do
136
+ it 'should enqueue a DELETE change for that attribute' do
137
+ model.delete_attribute(:stuff)
138
+ expect(model.changes['stuff']).to eq([model_attributes[:stuff], Dynamini::Attributes::DELETED_TOKEN, 'DELETE'])
139
+ end
140
+
141
+ it 'should remove the attribute from the in-memory attributes' do
142
+ model.delete_attribute('stuff')
143
+ expect(model.attributes.keys).to_not include('stuff')
144
+ end
145
+ end
146
+
147
+ context 'the attribute does not exist' do
148
+ it 'does nothing' do
149
+ model.delete_attribute('non-existent')
150
+ end
151
+ end
152
+ end
153
+
154
+ describe '.add_to' do
155
+ context 'a change exists for the given attribute' do
156
+ it 'should overwrite the change with a change that adds to the previously set value' do
157
+ model.price = 2
158
+ model.add_to(:price, 3)
159
+ expect(model.price).to eq(5)
160
+ expect(model.changes['price']).to eq([2, 3, 'ADD'])
161
+ end
162
+ end
163
+
164
+ context 'no change exists for the given attribute' do
165
+ it 'should create an add change' do
166
+ model.add_to(:price, 2)
167
+ expect(model.price).to eq(11.99)
168
+ expect(model.changes['price']).to eq([9.99, 2, 'ADD'])
169
+ end
170
+ end
171
+
172
+ context 'a setter is called after adding' do
173
+ it 'should overwrite the ADD change with a PUT change' do
174
+ model.add_to(:price, 2)
175
+ model.price = 2
176
+ expect(model.changes['price']).to eq([11.99, 2, 'PUT'])
177
+ end
178
+ end
179
+
180
+ context 'adding to a set' do
181
+ context 'the provided value is enumerable' do
182
+ it 'merges the sets' do
183
+ model.add_to(:things, Set.new([4]))
184
+ expect(model.things).to eq(Set.new([1,2,3,4]))
185
+ end
186
+ end
187
+ context 'the provided value is not enumerable' do
188
+ it 'raises an error' do
189
+ expect{ model.add_to(:things, 4) }.to raise_error ArgumentError
190
+ end
191
+ end
192
+ context 'adding to an uninitialized set' do
193
+ it 'creates the set' do
194
+ model.add_to(:new_set, Set.new([4]))
195
+ expect(model.new_set).to eq(Set.new([4]))
196
+ end
197
+ end
198
+ end
199
+
200
+ context 'adding to an array' do
201
+ context 'the provided value is enumerable' do
202
+ it 'merges the arrays' do
203
+ model.add_to(:stuff, [7])
204
+ expect(model.stuff).to eq([4,5,6,7])
205
+ end
206
+ end
207
+ context 'the provided value is not enumerable' do
208
+ it 'raises an error' do
209
+ expect{ model.add_to(:stuff, 4) }.to raise_error ArgumentError
210
+ end
211
+ end
212
+ context 'adding to an empty array' do
213
+ it 'creates the array' do
214
+ model.add_to(:new_array, [4])
215
+ expect(model.new_array).to eq([4])
216
+ end
217
+ end
218
+ end
219
+
220
+ context 'without reading a previously saved item' do
221
+ it 'still adds' do
222
+ model.save
223
+ model_clone = HandledModel.new(id: model_attributes[:id])
224
+ model_clone.add_to(:price, 2)
225
+ model_clone.save
226
+ expect(HandledModel.find(model_attributes[:id]).price).to eq(11.99)
227
+ end
228
+ end
229
+ end
230
+ end
@@ -115,39 +115,6 @@ describe Dynamini::Base do
115
115
  end
116
116
  end
117
117
 
118
- describe '#assign_attributes' do
119
- it 'should return nil' do
120
- expect(model.assign_attributes(price: '5')).to be_nil
121
- end
122
-
123
- it 'should update the attributes of the model' do
124
- model.assign_attributes(price: '5')
125
- expect(model.attributes[:price]).to eq('5')
126
- end
127
-
128
- it 'should append changed attributes to @changed' do
129
- model.assign_attributes(name: 'Widget', price: '5')
130
- expect(model.changed).to eq ['price']
131
- end
132
- end
133
-
134
- describe '#update_attribute' do
135
-
136
- it 'should update the attribute and save the object' do
137
- expect(model).to receive(:save!)
138
- model.update_attribute(:name, 'Widget 2.0')
139
- expect(model.name).to eq('Widget 2.0')
140
- end
141
- end
142
-
143
- describe '#update_attributes' do
144
- it 'should update multiple attributes and save the object' do
145
- expect(model).to receive(:save!)
146
- model.update_attributes(name: 'Widget 2.0', price: '12.00')
147
- expect(model.attributes).to include(name: 'Widget 2.0', price: '12.00')
148
- end
149
- end
150
-
151
118
  describe '#save' do
152
119
  it 'should run before and after save callbacks' do
153
120
  expect_any_instance_of(TestClassWithCallbacks).to receive(:before_callback)
@@ -412,127 +379,60 @@ describe Dynamini::Base do
412
379
  end
413
380
 
414
381
 
415
- describe 'attributes' do
416
- describe '#attributes' do
417
- it 'should return all attributes of the object' do
418
- expect(model.attributes).to include model_attributes
419
- end
382
+ describe '#new_record?' do
383
+ it 'should return true for a new record' do
384
+ expect(Dynamini::Base.new).to be_truthy
420
385
  end
421
-
422
- describe '#new_record?' do
423
- it 'should return true for a new record' do
424
- expect(Dynamini::Base.new).to be_truthy
425
- end
426
- it 'should return false for a retrieved record' do
427
- expect(Dynamini::Base.find('abcd1234').new_record?).to be_falsey
428
- end
429
- it 'should return false after a new record is saved' do
430
- expect(model.new_record?).to be_falsey
431
- end
386
+ it 'should return false for a retrieved record' do
387
+ expect(Dynamini::Base.find('abcd1234').new_record?).to be_falsey
432
388
  end
389
+ it 'should return false after a new record is saved' do
390
+ expect(model.new_record?).to be_falsey
391
+ end
392
+ end
433
393
 
434
- describe 'reader method' do
435
- it { is_expected.to respond_to(:price) }
436
- it { is_expected.not_to respond_to(:foo) }
437
394
 
438
- context 'existing attribute' do
439
- it 'should return the attribute' do
440
- expect(model.price).to eq(9.99)
441
- end
442
- end
443
395
 
444
- context 'new attribute' do
445
- before { model.description = 'test model' }
446
- it 'should return the attribute' do
447
- expect(model.description).to eq('test model')
448
- end
449
- end
396
+ describe '#key' do
397
+ context 'when using hash key only' do
450
398
 
451
- context 'nonexistent attribute' do
452
- it 'should return nil' do
453
- expect(subject.foo).to be_nil
399
+ before do
400
+ class TestClass < Dynamini::Base
401
+ set_hash_key :foo
454
402
  end
455
403
  end
456
404
 
457
- context 'attribute set to nil' do
458
- before { model.price = nil }
459
- it 'should return nil' do
460
- expect(model.price).to be_nil
461
- end
405
+ it 'should return an hash containing only the hash_key name and value' do
406
+ expect(TestClass.new(foo: 2).send(:key)).to eq(foo: 2)
462
407
  end
463
408
  end
464
-
465
- describe 'writer method' do
466
- it { is_expected.to respond_to(:baz=) }
467
-
468
- context 'existing attribute' do
469
- before { model.price = '1' }
470
- it 'should overwrite the attribute' do
471
- expect(model.price).to eq('1')
472
- end
473
- end
474
- context 'new attribute' do
475
- before { model.foo = 'bar' }
476
- it 'should write to the attribute' do
477
- expect(model.foo).to eq('bar')
478
- end
479
- end
480
-
481
- context 'arrays' do
482
- it 'should write to the attribute and switch type freely' do
483
- model.foo = ['bar', 'baz']
484
- expect(model.foo).to eq(['bar', 'baz'])
485
- model.foo = ['quux']
486
- expect(model.foo).to eq(['quux'])
487
- model.foo = 'zort'
488
- expect(model.foo).to eq('zort')
489
- model.foo = []
490
- expect(model.foo).to eq([])
491
- end
409
+ context 'when using both hash_key and range_key' do
410
+ it 'should return an hash containing only the hash_key name and value' do
411
+ key_hash = TestClassWithRange.new(foo: 2, bar: 2015).send(:key)
412
+ expect(key_hash).to eq(foo: 2, bar: 2015)
492
413
  end
493
414
  end
415
+ end
494
416
 
495
- describe '#key' do
496
- context 'when using hash key only' do
497
-
498
- before do
499
- class TestClass < Dynamini::Base
500
- set_hash_key :foo
501
- end
502
- end
503
-
504
- it 'should return an hash containing only the hash_key name and value' do
505
- expect(TestClass.new(foo: 2).send(:key)).to eq(foo: 2)
506
- end
507
- end
508
- context 'when using both hash_key and range_key' do
509
- it 'should return an hash containing only the hash_key name and value' do
510
- key_hash = TestClassWithRange.new(foo: 2, bar: 2015).send(:key)
511
- expect(key_hash).to eq(foo: 2, bar: 2015)
512
- end
417
+ describe '#set_secondary_index' do
418
+ it 'should return an hash containing only the hash_key and range_key name and value when setting a global secondary index' do
419
+ class TestClass < Dynamini::Base
420
+ set_hash_key :foo
421
+ set_range_key :bar
422
+ set_secondary_index :git_baz, hash_key: :git, range_key: :baz
513
423
  end
514
- end
515
424
 
516
- describe '#set_secondary_index' do
517
- it 'should return an hash containing only the hash_key and range_key name and value when setting a global secondary index' do
518
- class TestClass < Dynamini::Base
519
- set_hash_key :foo
520
- set_range_key :bar
521
- set_secondary_index :git_baz, hash_key: :git, range_key: :baz
522
- end
425
+ expect(TestClass.secondary_index['git_baz']).to eq(hash_key_name: :git, range_key_name: :baz)
426
+ end
523
427
 
524
- expect(TestClass.secondary_index['git_baz']).to eq(hash_key_name: :git, range_key_name: :baz)
428
+ it 'should return an hash containing only the hash_key and range_key name and value when setting a local secondary index' do
429
+ class TestClass < Dynamini::Base
430
+ set_hash_key :foo
431
+ set_range_key :bar
432
+ set_secondary_index :foo_baz, range_key: :baz
525
433
  end
526
434
 
527
- it 'should return an hash containing only the hash_key and range_key name and value when setting a local secondary index' do
528
- class TestClass < Dynamini::Base
529
- set_hash_key :foo
530
- set_range_key :bar
531
- set_secondary_index :foo_baz, range_key: :baz
532
- end
533
-
534
- expect(TestClass.secondary_index['foo_baz']).to eq(hash_key_name: :foo, range_key_name: :baz)
535
- end
435
+ expect(TestClass.secondary_index['foo_baz']).to eq(hash_key_name: :foo, range_key_name: :baz)
536
436
  end
537
437
  end
538
438
  end
@@ -33,6 +33,13 @@ describe Dynamini::TestClient do
33
33
  test_client.update_item(table_name: table_name, key: {hash_key_name: 'hash_key_value'}, attribute_updates: {abc: {value: Set.new([2]), action: 'ADD'}})
34
34
  expect(test_client.data[table_name]['hash_key_value']).to eq(abc: Set.new([1, 2]), hash_key_name: 'hash_key_value')
35
35
  end
36
+
37
+ it 'DELETEs attributes' do
38
+ test_client = Dynamini::TestClient.new(:hash_key_name)
39
+ test_client.update_item(table_name: table_name, key: {hash_key_name: 'hash_key_value'}, attribute_updates: {abc: {value: 1, action: 'PUT'}})
40
+ test_client.update_item(table_name: table_name, key: {hash_key_name: 'hash_key_value'}, attribute_updates: {abc: {action: 'DELETE'}})
41
+ expect(test_client.data[table_name]['hash_key_value']).to eq(hash_key_name: 'hash_key_value')
42
+ end
36
43
  end
37
44
 
38
45
  context 'with Hash key and range key' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dynamini
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.7.0
4
+ version: 2.7.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Greg Ward
@@ -15,7 +15,7 @@ authors:
15
15
  autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
- date: 2016-11-24 00:00:00.000000000 Z
18
+ date: 2016-12-07 00:00:00.000000000 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: activemodel
@@ -142,7 +142,7 @@ files:
142
142
  - dynamini.gemspec
143
143
  - lib/.DS_Store
144
144
  - lib/dynamini.rb
145
- - lib/dynamini/adder.rb
145
+ - lib/dynamini/attributes.rb
146
146
  - lib/dynamini/base.rb
147
147
  - lib/dynamini/batch_operations.rb
148
148
  - lib/dynamini/client_interface.rb
@@ -155,7 +155,7 @@ files:
155
155
  - lib/dynamini/test_client.rb
156
156
  - lib/dynamini/testing.rb
157
157
  - lib/dynamini/type_handler.rb
158
- - spec/dynamini/adder_spec.rb
158
+ - spec/dynamini/attributes_spec.rb
159
159
  - spec/dynamini/base_spec.rb
160
160
  - spec/dynamini/batch_operations_spec.rb
161
161
  - spec/dynamini/dirty_spec.rb
@@ -190,7 +190,7 @@ signing_key:
190
190
  specification_version: 4
191
191
  summary: DynamoDB interface
192
192
  test_files:
193
- - spec/dynamini/adder_spec.rb
193
+ - spec/dynamini/attributes_spec.rb
194
194
  - spec/dynamini/base_spec.rb
195
195
  - spec/dynamini/batch_operations_spec.rb
196
196
  - spec/dynamini/dirty_spec.rb
@@ -1,25 +0,0 @@
1
- module Dynamini
2
- module Adder
3
-
4
- ADDABLE_TYPES = [:set, :array, :integer, :float, :time, :date]
5
-
6
- def add_to(attribute, value)
7
- complain_about(attribute) unless handles[attribute]
8
- old_value = read_attribute(attribute)
9
- add_value = self.class.attribute_callback(TypeHandler::SETTER_PROCS, handles[attribute], value, true)
10
- if ADDABLE_TYPES.include? handles[attribute][:format]
11
- @attributes[attribute] ? @attributes[attribute] += add_value : @attributes[attribute] = add_value
12
- else
13
- complain_about(attribute)
14
- end
15
- record_change(attribute, old_value, add_value, 'ADD')
16
- self
17
- end
18
-
19
- private
20
-
21
- def complain_about(attribute)
22
- raise ArgumentError, "#{attribute.capitalize} is not handled as an addable type. Addable types are set, array, integer, float, time, and date."
23
- end
24
- end
25
- end
@@ -1,106 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Dynamini::Adder do
4
-
5
- class AddHandledModel < Dynamini::Base
6
- handle :price, :float
7
- handle :things, :set
8
- handle :stuff, :array
9
- handle :when, :date
10
- handle :what_time, :time
11
- handle :widget_count, :integer
12
- handle :new_set, :set
13
- handle :new_array, :array
14
- end
15
-
16
- let(:model_attributes) {
17
- {
18
- name: 'Widget',
19
- price: 9.99,
20
- id: 'abcd1234',
21
- hash_key: '009',
22
- things: Set.new([1,2,3]),
23
- stuff: [4,5,6]
24
- }
25
- }
26
-
27
- let(:model) { AddHandledModel.new(model_attributes) }
28
-
29
-
30
- describe '.add_to' do
31
- context 'a change exists for the given attribute' do
32
- it 'should overwrite the change with a change that adds to the previously set value' do
33
- model.price = 2
34
- model.add_to(:price, 3)
35
- expect(model.price).to eq(5)
36
- expect(model.changes['price']).to eq([2, 3, 'ADD'])
37
- end
38
- end
39
-
40
- context 'no change exists for the given attribute' do
41
- it 'should create an add change' do
42
- model.add_to(:price, 2)
43
- expect(model.price).to eq(11.99)
44
- expect(model.changes['price']).to eq([9.99, 2, 'ADD'])
45
- end
46
- end
47
-
48
- context 'a setter is called after adding' do
49
- it 'should overwrite the ADD change with a PUT change' do
50
- model.add_to(:price, 2)
51
- model.price = 2
52
- expect(model.changes['price']).to eq([11.99, 2, 'PUT'])
53
- end
54
- end
55
-
56
- context 'adding to a set' do
57
- context 'the provided value is enumerable' do
58
- it 'merges the sets' do
59
- model.add_to(:things, Set.new([4]))
60
- expect(model.things).to eq(Set.new([1,2,3,4]))
61
- end
62
- end
63
- context 'the provided value is not enumerable' do
64
- it 'raises an error' do
65
- expect{ model.add_to(:things, 4) }.to raise_error ArgumentError
66
- end
67
- end
68
- context 'adding to an uninitialized set' do
69
- it 'creates the set' do
70
- model.add_to(:new_set, Set.new([4]))
71
- expect(model.new_set).to eq(Set.new([4]))
72
- end
73
- end
74
- end
75
-
76
- context 'adding to an array' do
77
- context 'the provided value is enumerable' do
78
- it 'merges the arrays' do
79
- model.add_to(:stuff, [7])
80
- expect(model.stuff).to eq([4,5,6,7])
81
- end
82
- end
83
- context 'the provided value is not enumerable' do
84
- it 'raises an error' do
85
- expect{ model.add_to(:stuff, 4) }.to raise_error ArgumentError
86
- end
87
- end
88
- context 'adding to an empty array' do
89
- it 'creates the array' do
90
- model.add_to(:new_array, [4])
91
- expect(model.new_array).to eq([4])
92
- end
93
- end
94
- end
95
-
96
- context 'without reading a previously saved item' do
97
- it 'still adds' do
98
- model.save
99
- model2 = AddHandledModel.new(id: model_attributes[:id])
100
- model2.add_to(:price, 2)
101
- model2.save
102
- expect(AddHandledModel.find(model_attributes[:id]).price).to eq(11.99)
103
- end
104
- end
105
- end
106
- end