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 +7 -0
- data/lib/dynamini.rb +25 -0
- data/lib/dynamini/base.rb +237 -0
- data/lib/dynamini/configuration.rb +5 -0
- metadata +106 -0
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
|
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: []
|