dynamini 1.10.2 → 1.10.4
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 +5 -5
- data/dynamini.gemspec +2 -2
- data/lib/dynamini/base.rb +12 -170
- data/lib/dynamini/client_interface.rb +61 -0
- data/lib/dynamini/dirty.rb +38 -0
- data/lib/dynamini/increment.rb +44 -0
- data/lib/dynamini/querying.rb +11 -26
- data/lib/dynamini/test_client.rb +2 -2
- data/lib/dynamini/type_handler.rb +58 -0
- data/spec/dynamini/base_spec.rb +0 -284
- data/spec/dynamini/client_interface_spec.rb +10 -0
- data/spec/dynamini/dirty_spec.rb +144 -0
- data/spec/dynamini/increment_spec.rb +88 -0
- data/spec/dynamini/type_handler_spec.rb +79 -0
- metadata +36 -24
data/lib/dynamini/querying.rb
CHANGED
@@ -16,8 +16,8 @@ module Dynamini
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def find_or_new(hash_value, range_value = nil)
|
19
|
-
|
20
|
-
|
19
|
+
validate_query_values(hash_value, range_value)
|
20
|
+
|
21
21
|
|
22
22
|
r = client.get_item(table_name: table_name, key: create_key_hash(hash_value, range_value))
|
23
23
|
if r.item
|
@@ -39,6 +39,12 @@ module Dynamini
|
|
39
39
|
objects
|
40
40
|
end
|
41
41
|
|
42
|
+
protected
|
43
|
+
|
44
|
+
def range_is_numeric?
|
45
|
+
handles[@range_key] && [:integer, :time, :float, :date].include?(handles[@range_key][:format])
|
46
|
+
end
|
47
|
+
|
42
48
|
private
|
43
49
|
|
44
50
|
def dynamo_query(args)
|
@@ -72,30 +78,9 @@ module Dynamini
|
|
72
78
|
expression
|
73
79
|
end
|
74
80
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
if start_value && end_value
|
79
|
-
'BETWEEN'
|
80
|
-
elsif start_value
|
81
|
-
'GE'
|
82
|
-
elsif end_value
|
83
|
-
'LE'
|
84
|
-
end
|
85
|
-
)
|
86
|
-
attribute_value_list = []
|
87
|
-
|
88
|
-
if handle = handles[range_key.to_sym]
|
89
|
-
attribute_value_list << attribute_callback(SETTER_PROCS, handle, start_value) if start_value
|
90
|
-
attribute_value_list << attribute_callback(SETTER_PROCS, handle, end_value) if end_value
|
91
|
-
else
|
92
|
-
attribute_value_list << start_value if start_value
|
93
|
-
attribute_value_list << end_value if end_value
|
94
|
-
end
|
95
|
-
|
96
|
-
{attribute_value_list: attribute_value_list, comparison_operator: operator}
|
81
|
+
def validate_query_values(hash_value, range_value)
|
82
|
+
fail 'Key cannot be blank.' if (hash_value.nil? || hash_value == '')
|
83
|
+
fail 'Range key cannot be blank.' if range_key && range_value.nil?
|
97
84
|
end
|
98
|
-
|
99
|
-
|
100
85
|
end
|
101
86
|
end
|
data/lib/dynamini/test_client.rb
CHANGED
@@ -61,7 +61,7 @@ module Dynamini
|
|
61
61
|
OpenStruct.new(item: attributes_hash)
|
62
62
|
end
|
63
63
|
|
64
|
-
#
|
64
|
+
# No range key support - use query instead.
|
65
65
|
def batch_get_item(args = {})
|
66
66
|
responses = {}
|
67
67
|
|
@@ -76,7 +76,7 @@ module Dynamini
|
|
76
76
|
OpenStruct.new(responses: responses)
|
77
77
|
end
|
78
78
|
|
79
|
-
#
|
79
|
+
# TODO add range key support
|
80
80
|
def batch_write_item(request_options)
|
81
81
|
request_options[:request_items].each do |table_name, put_requests|
|
82
82
|
put_requests.each do |request_hash|
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Dynamini
|
2
|
+
module TypeHandler
|
3
|
+
module ClassMethods
|
4
|
+
def handle(column, format_class, options = {})
|
5
|
+
self.handles = self.handles.merge(column => { format: format_class, options: options })
|
6
|
+
|
7
|
+
define_handled_getter(column, format_class, options)
|
8
|
+
define_handled_setter(column, format_class)
|
9
|
+
end
|
10
|
+
|
11
|
+
def define_handled_getter(column, format_class, _options = {})
|
12
|
+
proc = GETTER_PROCS[format_class]
|
13
|
+
fail 'Unsupported data type: ' + format_class.to_s if proc.nil?
|
14
|
+
|
15
|
+
define_method(column) do
|
16
|
+
read_attribute(column)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def define_handled_setter(column, format_class)
|
21
|
+
method_name = (column.to_s + '=')
|
22
|
+
proc = SETTER_PROCS[format_class]
|
23
|
+
fail 'Unsupported data type: ' + format_class.to_s if proc.nil?
|
24
|
+
define_method(method_name) do |value|
|
25
|
+
write_attribute(column, value)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
GETTER_PROCS = {
|
31
|
+
integer: proc { |v| v.to_i },
|
32
|
+
date: proc { |v| v.is_a?(Date) ? v : Time.at(v).to_date },
|
33
|
+
time: proc { |v| Time.at(v.to_f) },
|
34
|
+
float: proc { |v| v.to_f },
|
35
|
+
symbol: proc { |v| v.to_sym },
|
36
|
+
string: proc { |v| v },
|
37
|
+
boolean: proc { |v| v }
|
38
|
+
}
|
39
|
+
|
40
|
+
SETTER_PROCS = {
|
41
|
+
integer: proc { |v| v.to_i },
|
42
|
+
time: proc { |v| (v.is_a?(Date) ? v.to_time : v).to_f },
|
43
|
+
float: proc { |v| v.to_f },
|
44
|
+
symbol: proc { |v| v.to_s },
|
45
|
+
string: proc { |v| v },
|
46
|
+
boolean: proc { |v| v },
|
47
|
+
date: proc { |v| v.to_time.to_f }
|
48
|
+
}
|
49
|
+
|
50
|
+
def handles
|
51
|
+
self.class.handles
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.included(base)
|
55
|
+
base.extend ClassMethods
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/spec/dynamini/base_spec.rb
CHANGED
@@ -45,58 +45,9 @@ describe Dynamini::Base do
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
-
describe '.client' do
|
49
|
-
it 'should not reinstantiate the client' do
|
50
|
-
expect(Dynamini::TestClient).to_not receive(:new)
|
51
|
-
Dynamini::Base.client
|
52
|
-
end
|
53
|
-
end
|
54
48
|
|
55
49
|
describe 'operations' do
|
56
50
|
|
57
|
-
describe '.handle' do
|
58
|
-
|
59
|
-
class HandledClass < Dynamini::Base;
|
60
|
-
end
|
61
|
-
|
62
|
-
context 'when reading the handled attirubte' do
|
63
|
-
before { HandledClass.handle :price, :integer, default: 9 }
|
64
|
-
it 'should return the proper format' do
|
65
|
-
object = HandledClass.new(price: "1")
|
66
|
-
expect(object.price).to eq(1)
|
67
|
-
end
|
68
|
-
it 'should return the default value if not assigned' do
|
69
|
-
object = HandledClass.new
|
70
|
-
expect(object.price).to eq(9)
|
71
|
-
end
|
72
|
-
it 'should return an array with formated item if handled' do
|
73
|
-
object = HandledClass.new(price: ["1", "2"])
|
74
|
-
expect(object.price).to eq([1, 2])
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
context 'when writing the handled attribute' do
|
79
|
-
before { HandledClass.handle :price, :float, default: 9 }
|
80
|
-
it 'should convert the value to handled format' do
|
81
|
-
object = HandledClass.new(price: "1")
|
82
|
-
expect(object.attributes[:price]).to eq(1.0)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
end
|
87
|
-
|
88
|
-
describe '.new' do
|
89
|
-
let(:dirty_model) { Dynamini::Base.new(model_attributes) }
|
90
|
-
|
91
|
-
it 'should append all initial attrs to @changed, including hash_key' do
|
92
|
-
expect(dirty_model.changed).to eq(model_attributes.keys.map(&:to_s).delete_if { |k, v| k == 'id' })
|
93
|
-
end
|
94
|
-
|
95
|
-
it 'should not include the primary key in the changes' do
|
96
|
-
expect(dirty_model.changes[:id]).to be_nil
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
51
|
describe '.create' do
|
101
52
|
it 'should save the item' do
|
102
53
|
other_model_attributes = model_attributes
|
@@ -119,79 +70,6 @@ describe Dynamini::Base do
|
|
119
70
|
end
|
120
71
|
end
|
121
72
|
|
122
|
-
describe '.increment!' do
|
123
|
-
context 'when incrementing a nil value' do
|
124
|
-
it 'should save' do
|
125
|
-
expect(model.class.client).to receive(:update_item).with(
|
126
|
-
table_name: 'bases',
|
127
|
-
key: {id: model_attributes[:id]},
|
128
|
-
attribute_updates: hash_including(
|
129
|
-
"foo" => {
|
130
|
-
value: 5,
|
131
|
-
action: 'ADD'
|
132
|
-
}
|
133
|
-
)
|
134
|
-
)
|
135
|
-
model.increment!(foo: 5)
|
136
|
-
end
|
137
|
-
it 'should update the value' do
|
138
|
-
model.increment!(foo: 5)
|
139
|
-
expect(Dynamini::Base.find('abcd1234').foo.to_i).to eq 5
|
140
|
-
end
|
141
|
-
end
|
142
|
-
context 'when incrementing a numeric value' do
|
143
|
-
it 'should save' do
|
144
|
-
expect(model).to receive(:read_attribute).and_return(9.99)
|
145
|
-
expect(model.class.client).to receive(:update_item).with(
|
146
|
-
table_name: 'bases',
|
147
|
-
key: {id: model_attributes[:id]},
|
148
|
-
attribute_updates: hash_including(
|
149
|
-
"price" => {
|
150
|
-
value: 5,
|
151
|
-
action: 'ADD'
|
152
|
-
}
|
153
|
-
)
|
154
|
-
)
|
155
|
-
model.increment!(price: 5)
|
156
|
-
|
157
|
-
end
|
158
|
-
it 'should sum the values' do
|
159
|
-
expect(model).to receive(:read_attribute).and_return(9.99)
|
160
|
-
model.increment!(price: 5)
|
161
|
-
expect(Dynamini::Base.find('abcd1234').price).to eq 14.99
|
162
|
-
end
|
163
|
-
end
|
164
|
-
context 'when incrementing a non-numeric value' do
|
165
|
-
it 'should raise an error and not save' do
|
166
|
-
expect(model).to receive(:read_attribute).and_return('hello')
|
167
|
-
expect { model.increment!(price: 5) }.to raise_error(StandardError)
|
168
|
-
end
|
169
|
-
end
|
170
|
-
context 'when incrementing with a non-numeric value' do
|
171
|
-
it 'should raise an error and not save' do
|
172
|
-
expect { model.increment!(foo: 'bar') }.to raise_error(StandardError)
|
173
|
-
end
|
174
|
-
end
|
175
|
-
context 'when incrementing multiple values' do
|
176
|
-
it 'should create/sum both values' do
|
177
|
-
allow(model).to receive(:read_attribute).and_return(9.99)
|
178
|
-
model.increment!(price: 5, baz: 6)
|
179
|
-
found_model = Dynamini::Base.find('abcd1234')
|
180
|
-
expect(found_model.price).to eq 14.99
|
181
|
-
expect(found_model.baz).to eq 6
|
182
|
-
end
|
183
|
-
end
|
184
|
-
context 'when incrementing a new record' do
|
185
|
-
it 'should save the record and init the values and timestamps' do
|
186
|
-
Dynamini::Base.new(id: 1, foo: 'bar').increment!(baz: 1)
|
187
|
-
found_model = Dynamini::Base.find(1)
|
188
|
-
expect(found_model.baz).to eq 1
|
189
|
-
expect(found_model.created_at).to_not be_nil
|
190
|
-
expect(found_model.updated_at).to_not be_nil
|
191
|
-
end
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
73
|
describe '#==' do
|
196
74
|
let(:model_a) { Dynamini::Base.new(model_attributes).tap {
|
197
75
|
|model| model.send(:clear_changes)
|
@@ -503,52 +381,6 @@ describe Dynamini::Base do
|
|
503
381
|
end
|
504
382
|
end
|
505
383
|
|
506
|
-
describe 'custom column handling' do
|
507
|
-
class HandleModel < Dynamini::Base
|
508
|
-
handle :price, :float, default: 10
|
509
|
-
handle :start_date, :time
|
510
|
-
handle :int_list, :integer
|
511
|
-
handle :sym_list, :symbol
|
512
|
-
end
|
513
|
-
|
514
|
-
let(:handle_model) { HandleModel.new }
|
515
|
-
|
516
|
-
it 'should create getters and setters' do
|
517
|
-
expect(handle_model).to_not receive(:method_missing)
|
518
|
-
handle_model.price = 1
|
519
|
-
handle_model.price
|
520
|
-
end
|
521
|
-
|
522
|
-
it 'should retrieve price as a float' do
|
523
|
-
handle_model.price = '5.2'
|
524
|
-
expect(handle_model.price).to be_a(Float)
|
525
|
-
end
|
526
|
-
|
527
|
-
it 'should default price to 0 if not set' do
|
528
|
-
expect(handle_model.price).to eq 10
|
529
|
-
end
|
530
|
-
|
531
|
-
it 'should store times as floats' do
|
532
|
-
handle_model.start_date = Time.now
|
533
|
-
expect(handle_model.attributes[:start_date]).to be_a(Float)
|
534
|
-
expect(handle_model.attributes[:start_date] > 1_000_000_000).to be_truthy
|
535
|
-
expect(handle_model.start_date).to be_a(Time)
|
536
|
-
end
|
537
|
-
|
538
|
-
it 'should reject bad data' do
|
539
|
-
expect { handle_model.int_list = {a: 1} }.to raise_error NoMethodError
|
540
|
-
end
|
541
|
-
|
542
|
-
it 'should save casted arrays' do
|
543
|
-
handle_model.int_list = [12, 24, 48]
|
544
|
-
expect(handle_model.int_list).to eq([12, 24, 48])
|
545
|
-
end
|
546
|
-
|
547
|
-
it 'should retrieve casted arrays' do
|
548
|
-
handle_model.sym_list = ['foo', 'bar', 'baz']
|
549
|
-
expect(handle_model.sym_list).to eq([:foo, :bar, :baz])
|
550
|
-
end
|
551
|
-
end
|
552
384
|
|
553
385
|
describe 'attributes' do
|
554
386
|
describe '#attributes' do
|
@@ -617,122 +449,6 @@ describe Dynamini::Base do
|
|
617
449
|
end
|
618
450
|
end
|
619
451
|
|
620
|
-
describe '#__was' do
|
621
|
-
|
622
|
-
context 'nonexistent attribute' do
|
623
|
-
it 'should raise an error' do
|
624
|
-
expect { Dynamini::Base.new.thing_was }.to raise_error ArgumentError
|
625
|
-
end
|
626
|
-
end
|
627
|
-
|
628
|
-
context 'after saving' do
|
629
|
-
it 'should clear all _was values' do
|
630
|
-
model = Dynamini::Base.new
|
631
|
-
model.new_val = 'new'
|
632
|
-
model.save
|
633
|
-
expect(model.new_val_was).to eq('new')
|
634
|
-
end
|
635
|
-
end
|
636
|
-
|
637
|
-
context 'new record' do
|
638
|
-
|
639
|
-
subject(:model) { Dynamini::Base.new(baz: 'baz') }
|
640
|
-
it { is_expected.to respond_to(:baz_was) }
|
641
|
-
|
642
|
-
context 'handled attribute with default' do
|
643
|
-
it 'should return the default value' do
|
644
|
-
Dynamini::Base.handle(:num, :integer, default: 2)
|
645
|
-
expect(model.num_was).to eq(2)
|
646
|
-
end
|
647
|
-
end
|
648
|
-
|
649
|
-
context 'handled attribute with no default' do
|
650
|
-
it 'should return nil' do
|
651
|
-
Dynamini::Base.handle(:num, :integer)
|
652
|
-
expect(model.num_was).to be_nil
|
653
|
-
end
|
654
|
-
end
|
655
|
-
|
656
|
-
context 'newly assigned attribute' do
|
657
|
-
it 'should return nil' do
|
658
|
-
model.new_attribute = 'hello'
|
659
|
-
expect(model.new_attribute_was).to be_nil
|
660
|
-
end
|
661
|
-
end
|
662
|
-
end
|
663
|
-
|
664
|
-
context 'previously saved record' do
|
665
|
-
subject(:model) { Dynamini::Base.new({baz: 'baz', nil_val: nil}, false) }
|
666
|
-
context 'unchanged attribute' do
|
667
|
-
it 'should return the current value' do
|
668
|
-
expect(model.baz_was).to eq('baz')
|
669
|
-
end
|
670
|
-
end
|
671
|
-
|
672
|
-
context 'newly assigned attribute or attribute changed from explicit nil' do
|
673
|
-
it 'should return nil' do
|
674
|
-
model.nil_val = 'no longer nil'
|
675
|
-
model.new_val = 'new'
|
676
|
-
expect(model.nil_val_was).to be_nil
|
677
|
-
expect(model.new_val_was).to be_nil
|
678
|
-
end
|
679
|
-
end
|
680
|
-
|
681
|
-
context 'attribute changed from value to value' do
|
682
|
-
it 'should return the old value' do
|
683
|
-
model.baz = 'baz2'
|
684
|
-
expect(model.baz_was).to eq('baz')
|
685
|
-
end
|
686
|
-
end
|
687
|
-
end
|
688
|
-
end
|
689
|
-
|
690
|
-
describe '#changes' do
|
691
|
-
it 'should not return the hash key or range key' do
|
692
|
-
Dynamini::Base.set_range_key(:range_key)
|
693
|
-
model.instance_variable_set(:@changes, {id: 'test_hash_key', range_key: "test_range_key"})
|
694
|
-
expect(model.changes).to eq({})
|
695
|
-
Dynamini::Base.set_range_key(nil)
|
696
|
-
end
|
697
|
-
|
698
|
-
context 'no change detected' do
|
699
|
-
it 'should return an empty hash' do
|
700
|
-
expect(model.changes).to eq({})
|
701
|
-
end
|
702
|
-
end
|
703
|
-
|
704
|
-
context 'attribute changed' do
|
705
|
-
before { model.price = 1 }
|
706
|
-
it 'should include the changed attribute' do
|
707
|
-
expect(model.changes['price']).to eq([9.99, 1])
|
708
|
-
end
|
709
|
-
end
|
710
|
-
|
711
|
-
context 'attribute created' do
|
712
|
-
before { model.foo = 'bar' }
|
713
|
-
it 'should include the created attribute' do
|
714
|
-
expect(model.changes['foo']).to eq([nil, 'bar'])
|
715
|
-
end
|
716
|
-
end
|
717
|
-
|
718
|
-
context 'attribute changed twice' do
|
719
|
-
before do
|
720
|
-
model.foo = 'bar'
|
721
|
-
model.foo = 'baz'
|
722
|
-
end
|
723
|
-
it 'should only include one copy of the changed attribute' do
|
724
|
-
expect(model.changes['foo']).to eq(['bar', 'baz'])
|
725
|
-
end
|
726
|
-
end
|
727
|
-
end
|
728
|
-
|
729
|
-
describe '#changed' do
|
730
|
-
it 'should stringify the keys of changes' do
|
731
|
-
allow(model).to receive(:changes).and_return({'price' => [1, 2], 'name' => ['a', 'b']})
|
732
|
-
expect(model.changed).to eq(['price', 'name'])
|
733
|
-
end
|
734
|
-
end
|
735
|
-
|
736
452
|
describe '#key' do
|
737
453
|
context 'when using hash key only' do
|
738
454
|
|