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 +4 -4
- data/Gemfile.lock +2 -2
- data/README.md +7 -5
- data/dynamini.gemspec +1 -1
- data/lib/dynamini/attributes.rb +110 -0
- data/lib/dynamini/base.rb +2 -74
- data/lib/dynamini/test_client.rb +5 -2
- data/spec/dynamini/attributes_spec.rb +230 -0
- data/spec/dynamini/base_spec.rb +35 -135
- data/spec/dynamini/test_client_spec.rb +7 -0
- metadata +5 -5
- data/lib/dynamini/adder.rb +0 -25
- data/spec/dynamini/adder_spec.rb +0 -106
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 15802466753d1d5ac670e2d1649cecb5f4cf8334
|
4
|
+
data.tar.gz: b2c044abc94835728ab8b356be4f771ef3246f2c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a6224689aaedb675bbdb9ab84fddd710184666e6357c525affc799d4d5befc5c04a640d68785df8352e3282ea5c3a9d69bf187885656df60382a65aa89700abe
|
7
|
+
data.tar.gz: 09bccbb64058d707bb4cc72f7af7ee50ef1ea6daaa3ef172c869d29ec304d7eeaa6179ca269cf243341bd369d40a56b56396ee57edd3de473b3609da791a46c6
|
data/Gemfile.lock
CHANGED
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.
|
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
|
data/dynamini.gemspec
CHANGED
@@ -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
|
data/lib/dynamini/base.rb
CHANGED
@@ -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 '
|
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::
|
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
|
data/lib/dynamini/test_client.rb
CHANGED
@@ -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
|
data/spec/dynamini/base_spec.rb
CHANGED
@@ -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 '
|
416
|
-
|
417
|
-
|
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
|
-
|
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
|
-
|
445
|
-
|
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
|
-
|
452
|
-
|
453
|
-
|
399
|
+
before do
|
400
|
+
class TestClass < Dynamini::Base
|
401
|
+
set_hash_key :foo
|
454
402
|
end
|
455
403
|
end
|
456
404
|
|
457
|
-
|
458
|
-
|
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
|
-
|
466
|
-
|
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
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
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
|
-
|
517
|
-
|
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
|
-
|
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
|
-
|
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.
|
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-
|
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/
|
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/
|
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/
|
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
|
data/lib/dynamini/adder.rb
DELETED
@@ -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
|
data/spec/dynamini/adder_spec.rb
DELETED
@@ -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
|