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.
@@ -16,8 +16,8 @@ module Dynamini
16
16
  end
17
17
 
18
18
  def find_or_new(hash_value, range_value = nil)
19
- fail 'Key cannot be blank.' if (hash_value.nil? || hash_value == '')
20
- fail 'Range key cannot be blank.' if range_key && range_value.nil?
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
- #FIXME unused method
76
- def build_range_expression(start_value, end_value)
77
- operator = (
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
@@ -61,7 +61,7 @@ module Dynamini
61
61
  OpenStruct.new(item: attributes_hash)
62
62
  end
63
63
 
64
- #FIXME Add range key support
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
- #FIXME Add range key support
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
@@ -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
 
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ describe Dynamini::ClientInterface do
4
+ describe '.client' do
5
+ it 'should not reinstantiate the client' do
6
+ expect(Dynamini::TestClient).to_not receive(:new)
7
+ Dynamini::Base.client
8
+ end
9
+ end
10
+ end