dynamini 2.7.0 → 2.7.3

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: 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