dynamini 2.12.1 → 2.12.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 295d5d3e9ac24824561c2d3ea60328036a2a1ef08cb442c75dc07f07110f05dc
4
- data.tar.gz: dc6a149f087e727efed3d20952468639a3ea0b82f3e3acc9d954d9a0a8307153
3
+ metadata.gz: 7c5532b785f0dddd894eb863f17905aab853a124fb3aeb6b046e67b3f0574b2a
4
+ data.tar.gz: 6b897e7501ec92aab731f94dbf93ee830ed4e0b615103e4b268c38f2796fbb05
5
5
  SHA512:
6
- metadata.gz: 70172147217e1531d2be9a15973babb8cbd77361eff4025e39d816e0591e3c4e25278a6b73431b89a4bd5acdc7415f28ed40a9c423b1f0b36ba980151baca358
7
- data.tar.gz: 3e8383facfa971c03a77e22ad8399cf5f5d024a88cc65bd67be213eaf0a967a315f5a4a34908c46afd44e1d4835de5d5a130e8d0950ec12282c6e67da206af8b
6
+ metadata.gz: 11dd2f65ba75d324367f9493fa8a0f2bdfca46526d2647cc738e3c7661303ada53e1aac4409770b393c837a90a892988296e20645374fff4d9d87ad513ac701c
7
+ data.tar.gz: fdb2715ba459b388db428af4b46a2cb11281fd6efd8a3471f3e9ce13c7c0893ed5424a6b422c20797eec8c57ba01fbf06284edd634abd311b587db4e78b05ab8
data/README.md CHANGED
@@ -259,14 +259,14 @@ page_two = Product.scan(start_key: products_page_one.last_evaluated_key)
259
259
  ```
260
260
 
261
261
  ## Secondary Indices
262
- To define a secondary index (so that you can .scan or .query it), you can set them at the top of your Dynamini subclass. The index names have to match the names you've set up through the DynamoDB console. If your secondary index uses a range key, specify it here as well.
262
+ To define a secondary index (so that you can .scan or .query it), you can set them at the top of your Dynamini subclass. The index names have to match the names you've set up through the DynamoDB console. You'll need to specify which attribute your index is keyed to, and if your secondary index uses a range key, specify it here as well.
263
263
 
264
264
  ```ruby
265
265
  class Comment < Dynamini::Base
266
266
  set_hash_key :id
267
- set_range_key :comment_date
268
- set_secondary_index :score_index
269
- set_secondary_index :popularity_index, range_key: :popularity
267
+ set_range_key :comment_date # filter comments by date
268
+ set_secondary_index :score_index, hash_key: :score # lookup comments by score
269
+ set_secondary_index :user_index, hash_key: :user_id, range_key: :comment_date # lookup comments by user, filtering by date
270
270
  end
271
271
  ```
272
272
  For more information on how and why to use secondary indices, see http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SecondaryIndexes.html
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'dynamini'
3
- s.version = '2.12.1'
3
+ s.version = '2.12.2'
4
4
  s.summary = 'DynamoDB interface'
5
5
  s.description = 'Lightweight DynamoDB interface gem designed as
6
6
  a drop-in replacement for ActiveRecord.
@@ -4,6 +4,7 @@ module Dynamini
4
4
  require 'dynamini/base'
5
5
  require 'dynamini/configuration'
6
6
  require 'dynamini/test_client'
7
+ require 'dynamini/item_splitter'
7
8
 
8
9
  class << self
9
10
  attr_writer :configuration
@@ -130,7 +130,10 @@ module Dynamini
130
130
 
131
131
  def trigger_save(options = {})
132
132
  generate_timestamps! unless options[:skip_timestamps]
133
- save_to_dynamo
133
+ updates = ItemSplitter.split(attribute_updates)
134
+ updates.each do |u|
135
+ save_to_dynamo(u)
136
+ end
134
137
  clear_changes
135
138
  @new_record = false
136
139
  true
@@ -10,7 +10,7 @@ module Dynamini
10
10
  end
11
11
  end
12
12
 
13
- def save_to_dynamo
13
+ def save_to_dynamo(attribute_updates)
14
14
  self.class.client.update_item(
15
15
  table_name: self.class.table_name,
16
16
  key: key,
@@ -0,0 +1,63 @@
1
+ module Dynamini
2
+ class ItemSplitter
3
+
4
+ MAX_SIZE = 380_000
5
+
6
+ class << self
7
+
8
+ def split(attribute_updates)
9
+ unprocessed_au = attribute_updates.map { |k, v| {k => v} }
10
+ updates = []
11
+ current_update_size = 0
12
+ current_update = {}
13
+
14
+ while unprocessed_au.length > 0 do
15
+ size = au_size(unprocessed_au[0])
16
+ if size > MAX_SIZE
17
+ part_one, part_two = split_au(unprocessed_au[0])
18
+ unprocessed_au.shift
19
+ unprocessed_au.unshift(part_two)
20
+ unprocessed_au.unshift(part_one)
21
+ else
22
+ current_update_size += size
23
+ if current_update_size > MAX_SIZE
24
+ updates.push(current_update)
25
+ current_update_size = 0
26
+ current_update = {}
27
+ else
28
+ current_update_size += size
29
+ key, value = key_and_value(unprocessed_au[0])
30
+ current_update[key] = value
31
+ unprocessed_au.shift
32
+ end
33
+ end
34
+ end
35
+
36
+ updates.push(current_update) unless current_update.empty?
37
+ updates
38
+ end
39
+
40
+ private
41
+
42
+ def au_size(au)
43
+ au.to_s.bytesize
44
+ end
45
+
46
+ def split_au(au)
47
+ attribute_name = au.keys[0]
48
+ attribute_action = au.values[0][:action]
49
+ attribute_value = au.values[0][:value]
50
+
51
+ raise "#{attribute_name} is too large to save and is not splittable (not enumerable)." unless attribute_value.is_a?(Enumerable)
52
+
53
+ part_one = {attribute_name => {action: attribute_action, value: attribute_value[0..(attribute_value.length / 2) - 1]}}
54
+ part_two = {attribute_name => {action: "ADD", value: attribute_value[attribute_value.length / 2..-1]}}
55
+ [part_one, part_two]
56
+ end
57
+
58
+ def key_and_value(au)
59
+ [au.keys[0], au.values[0]]
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe Dynamini::ItemSplitter do
4
+ before do
5
+ stub_const("Dynamini::ItemSplitter::MAX_SIZE", 200)
6
+ end
7
+
8
+ context 'regular update' do
9
+ it 'returns one update' do
10
+ input = {foo: {action: 'PUT', value: '123'}, bar: {action: 'ADD', value: [4,5,6]}}
11
+ expect(Dynamini::ItemSplitter.split(input)).to eq([input])
12
+ end
13
+ end
14
+
15
+ context 'large update' do
16
+ it 'returns multiple updates' do
17
+ input = {foo: {action: 'PUT', value: '123'}, bar: {action: 'ADD', value: [4,5,6]}, baz: {action: 'PUT', value: 'hello'}}
18
+ output = Dynamini::ItemSplitter.split(input)
19
+ expect(output[0]).to eq({foo: {action: 'PUT', value: '123'}, bar: {action: 'ADD', value: [4,5,6]}})
20
+ expect(output[1]).to eq(baz: {action: 'PUT', value: 'hello'})
21
+ end
22
+ end
23
+
24
+ context 'large enumerable attribute' do
25
+ it 'splits the attribute' do
26
+ input = {enum: {action: 'PUT', value: Array.new(20, 'hello')}}
27
+ output = Dynamini::ItemSplitter.split(input)
28
+ expect(output.length).to eq(2)
29
+ expect(output[0][:enum][:action]).to eq('PUT')
30
+ expect(output[1][:enum][:action]).to eq('ADD')
31
+ end
32
+ end
33
+
34
+ context 'large non-enumerable attribute' do
35
+ it 'raises an error' do
36
+ input = {not_enum: {action: 'PUT', value: Array.new(20, 'hello').to_s}}
37
+ expect{ Dynamini::ItemSplitter.split(input) }.to raise_error("not_enum is too large to save and is not splittable (not enumerable).")
38
+ end
39
+ end
40
+ end
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.12.1
4
+ version: 2.12.2
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: 2018-02-26 00:00:00.000000000 Z
18
+ date: 2018-07-04 00:00:00.000000000 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: activemodel
@@ -150,6 +150,7 @@ files:
150
150
  - lib/dynamini/errors.rb
151
151
  - lib/dynamini/global_id.rb
152
152
  - lib/dynamini/increment.rb
153
+ - lib/dynamini/item_splitter.rb
153
154
  - lib/dynamini/querying.rb
154
155
  - lib/dynamini/test_client.rb
155
156
  - lib/dynamini/testing.rb
@@ -160,6 +161,7 @@ files:
160
161
  - spec/dynamini/dirty_spec.rb
161
162
  - spec/dynamini/global_id_spec.rb
162
163
  - spec/dynamini/increment_spec.rb
164
+ - spec/dynamini/item_splitter_spec.rb
163
165
  - spec/dynamini/querying_spec.rb
164
166
  - spec/dynamini/test_client_spec.rb
165
167
  - spec/dynamini/type_handler_spec.rb
@@ -195,6 +197,7 @@ test_files:
195
197
  - spec/dynamini/dirty_spec.rb
196
198
  - spec/dynamini/global_id_spec.rb
197
199
  - spec/dynamini/increment_spec.rb
200
+ - spec/dynamini/item_splitter_spec.rb
198
201
  - spec/dynamini/querying_spec.rb
199
202
  - spec/dynamini/test_client_spec.rb
200
203
  - spec/dynamini/type_handler_spec.rb