dynamini 1.0.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1b9099abc1be34863020585c0b6a7da041d06d81
4
+ data.tar.gz: 0f01450cdfbc3c4c4d09e18641bb87b6fd20666e
5
+ SHA512:
6
+ metadata.gz: 4aef240c1f66a73a6eb9bbbb3825800b9da44becf37b223174c19965e56ea4e9f965b5db37f1e13f106b23d180642ceabc513bd27898fcd3d8d9bcc83fed0326
7
+ data.tar.gz: 0ecddfe0e5fad8dcf8fd8357a19f6f0e226477ccf1d0e8cd448a63a92547fdf7874ce597b9455727acc2344f0dfeab249c4a7bc259c5d69c3fa299f7486a0596
data/lib/dynamini.rb ADDED
@@ -0,0 +1,25 @@
1
+ module Dynamini
2
+ require 'active_model'
3
+ require 'dynamini/base'
4
+ require 'dynamini/configuration'
5
+
6
+ class << self
7
+ attr_writer :configuration
8
+ end
9
+
10
+ def self.configuration
11
+ @configuration ||= Configuration.new
12
+ end
13
+
14
+ def self.configure
15
+ yield(configuration)
16
+ end
17
+ end
18
+
19
+
20
+
21
+ # Dynamini.configure do |config|
22
+ # config.aws_region = 'eu-west-1'
23
+ # config.access_key_id =
24
+ # config.secret_access_key =
25
+ # end
@@ -0,0 +1,237 @@
1
+ module Dynamini
2
+ class Base
3
+ include ActiveModel::Validations
4
+ attr_reader :attributes
5
+
6
+ BATCH_SIZE = 25
7
+
8
+ class << self
9
+ attr_writer :hash_key, :table_name, :batch_write_queue
10
+
11
+ def table_name
12
+ @table_name || name.demodulize.downcase.pluralize
13
+ end
14
+
15
+ def hash_key
16
+ @hash_key || :id
17
+ end
18
+
19
+ def batch_write_queue
20
+ @batch_write_queue ||= []
21
+ end
22
+
23
+ def client
24
+ @client ||= Aws::DynamoDB::Client.new(
25
+ region: Dynamini.configuration.region,
26
+ access_key_id: Dynamini.configuration.access_key_id,
27
+ secret_access_key: Dynamini.configuration.secret_access_key)
28
+ end
29
+
30
+ def create(attributes, options={})
31
+ model = self.new(attributes, true)
32
+ model if model.save(options)
33
+ end
34
+
35
+ def create!(attributes, options={})
36
+ model = self.new(attributes, true)
37
+ model if model.save!(options)
38
+ end
39
+
40
+ def find(key)
41
+ response = client.get_item(table_name: table_name, key: {hash_key => key})
42
+ raise 'Item not found.' unless response.item
43
+ self.new(response.item.symbolize_keys, false)
44
+ end
45
+
46
+ def find_or_new(key)
47
+ response = client.get_item(table_name: table_name, key: {hash_key => key})
48
+ if response.item
49
+ self.new(response.item.symbolize_keys, false)
50
+ else
51
+ self.new(hash_key => key)
52
+ end
53
+ end
54
+
55
+ def batch_find(ids = [])
56
+ return [] if ids.length < 1
57
+ raise StandardError, 'Batch find is limited to 100 items' if ids.length > 100
58
+ key_structure = ids.map { |i| {hash_key => i} }
59
+ response = self.dynamo_batch_get(key_structure)
60
+ response.responses[table_name]
61
+ end
62
+
63
+ def enqueue_for_save(attributes, options = {})
64
+ model = self.new(attributes, true)
65
+ model.generate_timestamps! unless options[:skip_timestamps]
66
+ if model.valid?
67
+ batch_write_queue << model
68
+ flush_queue! if batch_write_queue.length == BATCH_SIZE
69
+ return true
70
+ end
71
+ false
72
+ end
73
+
74
+ def flush_queue!
75
+ response = self.dynamo_batch_save(batch_write_queue)
76
+ self.batch_write_queue = []
77
+ response
78
+ end
79
+
80
+ end
81
+
82
+ def initialize(attributes={}, new_record = true)
83
+ @attributes = attributes
84
+ @changed = Set.new
85
+ @new_record = new_record
86
+ add_changed(attributes)
87
+ end
88
+
89
+ def assign_attributes(attributes)
90
+ attributes.each do |key, value|
91
+ record_change(key, read_attribute(key), value)
92
+ end
93
+ @attributes.merge!(attributes)
94
+ nil
95
+ end
96
+
97
+ def save(options = {})
98
+ @changed.empty? || valid? && trigger_save(options)
99
+ end
100
+
101
+ def save!(options = {})
102
+
103
+ options[:validate]= true if options[:validate].nil?
104
+
105
+ unless @changed.empty?
106
+ if (options[:validate] && valid?) || !options[:validate]
107
+ trigger_save(options)
108
+ else
109
+ raise StandardError, errors.full_messages
110
+ end
111
+ end
112
+ end
113
+
114
+ def touch(options = {validate: true})
115
+ raise RuntimeError, 'Cannot touch a new record.' if new_record?
116
+ if (options[:validate] && valid?) || !options[:validate]
117
+ trigger_touch
118
+ else
119
+ raise StandardError, errors.full_messages
120
+ end
121
+ end
122
+
123
+ def changes
124
+ @attributes.select { |attribute| @changed.include?(attribute.to_s) && attribute != self.class.hash_key }
125
+ end
126
+
127
+ def changed
128
+ @changed.to_a
129
+ end
130
+
131
+ def new_record?
132
+ @new_record
133
+ end
134
+
135
+ private
136
+
137
+ def add_changed(attributes)
138
+ @changed += attributes.keys.map(&:to_s)
139
+ end
140
+
141
+ def trigger_save(options = {})
142
+ generate_timestamps! unless options[:skip_timestamps]
143
+ save_to_dynamo
144
+ clear_changes
145
+ @new_record = false
146
+ true
147
+ end
148
+
149
+ def trigger_touch
150
+ generate_timestamps!
151
+ touch_to_dynamo
152
+ true
153
+ end
154
+
155
+ def generate_timestamps!
156
+ self.updated_at= Time.now.to_f
157
+ self.created_at= Time.now.to_f if new_record?
158
+ end
159
+
160
+ def save_to_dynamo
161
+ Base.client.update_item(table_name: self.class.table_name, key: key, attribute_updates: attribute_updates)
162
+ end
163
+
164
+ def touch_to_dynamo
165
+ Base.client.update_item(table_name: self.class.table_name, key: key, attribute_updates: {updated_at: {value: updated_at, action: 'PUT'}})
166
+ end
167
+
168
+ def self.dynamo_batch_get(key_structure)
169
+ client.batch_get_item(request_items: {table_name => {keys: key_structure}})
170
+ end
171
+
172
+ def self.dynamo_batch_save(model_array)
173
+ put_requests = []
174
+ model_array.each do |model|
175
+ put_requests << {put_request: {item: model.attributes.reject{|k, v| v.blank?}.stringify_keys}}
176
+ end
177
+ request_options = {request_items: {
178
+ "#{table_name}" => put_requests}
179
+ }
180
+ client.batch_write_item(request_options)
181
+ end
182
+
183
+ def key
184
+ {self.class.hash_key => @attributes[self.class.hash_key]}
185
+ end
186
+
187
+ def attribute_updates
188
+ changes.reduce({}) do |updates, (key, value)|
189
+ updates[key] = {value: value, action: 'PUT'} unless value.blank?
190
+ updates
191
+ end
192
+ end
193
+
194
+ def clear_changes
195
+ @changed = Set.new
196
+ end
197
+
198
+ def method_missing(name, *args, &block)
199
+ if write_method?(name)
200
+ attribute = name[0..-2].to_sym
201
+ new_value = args.first
202
+ write_attribute(attribute, new_value)
203
+ elsif read_method?(name)
204
+ read_attribute(name)
205
+ else
206
+ super
207
+ end
208
+ end
209
+
210
+ def read_method?(name)
211
+ name =~ /^([a-zA-Z][-_\w]*)[^=?]*$/
212
+ end
213
+
214
+ def write_method?(name)
215
+ name =~ /^([a-zA-Z][-_\w]*)=.*$/
216
+ end
217
+
218
+ def respond_to_missing?(name, include_private=false)
219
+ @attributes.keys.include?(name) || write_method?(name) || super
220
+ end
221
+
222
+ def write_attribute(attribute, new_value)
223
+ raise StandardError, 'Cannot edit hash key, create a new object instead.' if attribute == self.class.hash_key
224
+ old_value = @attributes[attribute]
225
+ @attributes[attribute] = new_value
226
+ record_change(attribute, new_value, old_value)
227
+ end
228
+
229
+ def record_change(attribute, new_value, old_value)
230
+ @changed << attribute.to_s if new_value != old_value
231
+ end
232
+
233
+ def read_attribute(name)
234
+ @attributes[name]
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,5 @@
1
+ module Dynamini
2
+ class Configuration
3
+ attr_accessor :region, :access_key_id, :secret_access_key
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dynamini
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Greg Ward
8
+ - David McHoull
9
+ - Alishan Ladhani
10
+ - Emily Fan
11
+ autorequire:
12
+ bindir: bin
13
+ cert_chain: []
14
+ date: 2015-09-02 00:00:00.000000000 Z
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: activemodel
18
+ requirement: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: '4'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '4'
30
+ - !ruby/object:Gem::Dependency
31
+ name: aws-sdk
32
+ requirement: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - ~>
35
+ - !ruby/object:Gem::Version
36
+ version: '2'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: '2'
44
+ - !ruby/object:Gem::Dependency
45
+ name: rspec
46
+ requirement: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ~>
49
+ - !ruby/object:Gem::Version
50
+ version: '3'
51
+ type: :development
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ~>
56
+ - !ruby/object:Gem::Version
57
+ version: '3'
58
+ - !ruby/object:Gem::Dependency
59
+ name: shoulda-matchers
60
+ requirement: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ~>
63
+ - !ruby/object:Gem::Version
64
+ version: '2'
65
+ type: :development
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ~>
70
+ - !ruby/object:Gem::Version
71
+ version: '2'
72
+ description: Lightweight DynamoDB interface designed as a drop-in replacement for
73
+ ActiveRecord.
74
+ email: dev@retailcommon.com
75
+ executables: []
76
+ extensions: []
77
+ extra_rdoc_files: []
78
+ files:
79
+ - lib/dynamini.rb
80
+ - lib/dynamini/base.rb
81
+ - lib/dynamini/configuration.rb
82
+ homepage: https://github.com/47colborne/dynamini
83
+ licenses:
84
+ - MIT
85
+ metadata: {}
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - '>='
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ requirements: []
101
+ rubyforge_project:
102
+ rubygems_version: 2.4.8
103
+ signing_key:
104
+ specification_version: 4
105
+ summary: DynamoDB interface
106
+ test_files: []