dynamini 2.9.2 → 2.9.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -2
- data/Gemfile.lock +1 -1
- data/README.md +47 -31
- data/dynamini.gemspec +1 -1
- data/lib/dynamini/attributes.rb +1 -1
- data/lib/dynamini/type_handler.rb +14 -11
- data/spec/dynamini/attributes_spec.rb +10 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1dfc513f038cc378fa09c49d4371fb55de060207
|
4
|
+
data.tar.gz: 278157bffc21bbae3fbaa3a7e2a1652571afc4e3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bb4bc3eeaf5c7f7b1321fd0aa29da4bcc5d5c8b968f3e2ab5619cc60fe8dde8ac53ca2135b3c73de70dcbaf4797183cec0a82ceabac6678c097c1a2d6522ff2b
|
7
|
+
data.tar.gz: 51d96dc40837859551f3fffd6a1dcbdf5d5137613a4e610bb5f980c744c67d78d55c001f986e9be9518e70b4e29fc3af983cecdc65dfcb376ecebe7e17f624b0
|
data/.travis.yml
CHANGED
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -5,8 +5,21 @@ Dynamini is a lightweight DynamoDB interface designed as a drop-in replacement f
|
|
5
5
|
[![Code Climate](https://codeclimate.com/github/47colborne/dynamini/badges/gpa.svg)](https://codeclimate.com/github/47colborne/dynamini)
|
6
6
|
[![Gem Version](https://badge.fury.io/rb/dynamini.svg)](https://badge.fury.io/rb/dynamini)
|
7
7
|
|
8
|
+
##### Table of Contents
|
9
|
+
- [The Basics](#the-basics)
|
10
|
+
- [Configuration](#configuration)
|
11
|
+
- [Datatype Handling](#datatype-handling)
|
12
|
+
- [Enumerable Attributes](#enumerable-attributes)
|
13
|
+
- [Querying](#querying)
|
14
|
+
- [Scanning](#scanning)
|
15
|
+
- [Secondary Indices](#secondary-indices)
|
16
|
+
- [Batch Saving](#batch-saving)
|
17
|
+
- [Testing](#testing)
|
18
|
+
- [Things to Remember](#things-to-remember)
|
19
|
+
- [Contributing](#contributing)
|
20
|
+
|
8
21
|
## The Basics
|
9
|
-
This gem
|
22
|
+
This gem is designed to provide an ActiveRecord-like interface for Amazon's DynamoDB, making it easy for you to make the switch from a traditional relational DB. Once you've set up your table in the DynamoDB console, and installed and configured Dynamini, the behavior of the following ActiveRecord methods will be preserved:
|
10
23
|
|
11
24
|
Class methods:
|
12
25
|
* create(attributes)
|
@@ -38,7 +51,8 @@ Instance methods:
|
|
38
51
|
* created_at
|
39
52
|
|
40
53
|
We've included ActiveModel::Validations, so any validators will still work and be triggered by the save/create methods.
|
41
|
-
|
54
|
+
|
55
|
+
There are also some new methods specific to DynamoDB's API that don't have a counterpart in ActiveRecord:
|
42
56
|
|
43
57
|
* find_or_nil(hash_key, range_key) - since ActiveRecord's find_by isn't applicable to noSQL, use this method if you want a .find that doesn't raise exceptions when the item doesn't exist
|
44
58
|
* batch_find([keys]) - to retrieve multiple objects at once.
|
@@ -48,7 +62,7 @@ There are also some new functions specific to DynamoDB's API:
|
|
48
62
|
* delete_attribute!(attrubute) - same as above but with an included save!
|
49
63
|
|
50
64
|
## Configuration
|
51
|
-
In application.rb, or in initializers/dynamini.rb, include your AWS settings like
|
65
|
+
In application.rb, or in initializers/dynamini.rb, include your AWS settings like this:
|
52
66
|
|
53
67
|
```ruby
|
54
68
|
Dynamini.configure do |config|
|
@@ -107,7 +121,7 @@ The following datatypes are supported by handle:
|
|
107
121
|
Booleans and strings don't actually need to be translated, but you can set up defaults for those fields this way.
|
108
122
|
The magic fields updated_at and created_at are handled as :time by default.
|
109
123
|
|
110
|
-
## Enumerable
|
124
|
+
## Enumerable Attributes
|
111
125
|
You can save arrays and sets to your Dynamini model. Optionally, you can have Dynamini perform type conversion on each element of your enumerable. Here's how it works:
|
112
126
|
|
113
127
|
```ruby
|
@@ -146,9 +160,11 @@ Please note that changing enumerables in place using mutator methods like << or
|
|
146
160
|
|
147
161
|
If you want to make changes like this, either clone it then use the assignment operator (e.g. model.array = model.array.dup << 'foo') or call model.mark(:attribute) after mutation and before saving to force Dynamini to write the change.
|
148
162
|
|
149
|
-
## Querying
|
163
|
+
## Querying
|
164
|
+
|
165
|
+
Dynamini includes a query function that's much more narrow than ActiveRecord's where function, since DynamoDB is not automatically optimized for highly flexible read operations. It's designed to retrieve a selection of records that belong to a given hash key but have various range key values.
|
150
166
|
|
151
|
-
|
167
|
+
To use .query, your table needs to be configured with a range key, and you need to :handle that range field as a fundamentally numeric type - integer, float, date, or time. If your range key field isn't numeric, you won't be able to .query, but you'll still be able to .find your records normally.
|
152
168
|
|
153
169
|
Query takes the following arguments:
|
154
170
|
* :hash_key (required)
|
@@ -199,7 +215,7 @@ DailyWeather.query(hash_key: "Toronto", scan_index_forward: false)
|
|
199
215
|
> [C, B, A]
|
200
216
|
```
|
201
217
|
|
202
|
-
##
|
218
|
+
## Scanning
|
203
219
|
Table scanning is a very expensive operation, and should not be undertaken without a good understanding of the read/write costs. As such, Dynamini doesn't implement the traditional ActiveRecord collection methods like .all or .where. Instead, you can .scan, which has an interface much closer to DynamoDB's native SDK method.
|
204
220
|
|
205
221
|
The following options are supported:
|
@@ -215,7 +231,6 @@ Note that start_key can be either a hash { "AttributeName" => "Value" } or a val
|
|
215
231
|
|
216
232
|
For more information about using segment and total_segments for parallelization, see: http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html
|
217
233
|
|
218
|
-
|
219
234
|
```ruby
|
220
235
|
products_page_one = Product.scan(limit: 100, start_key: 'abcd')
|
221
236
|
|
@@ -229,7 +244,7 @@ page_two = Product.scan(start_key: products_page_one.last_evaluated_key)
|
|
229
244
|
```
|
230
245
|
|
231
246
|
## Secondary Indices
|
232
|
-
To define a secondary index (so that you can .scan
|
247
|
+
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.
|
233
248
|
|
234
249
|
```ruby
|
235
250
|
class Comment < Dynamini::Base
|
@@ -240,28 +255,6 @@ class Comment < Dynamini::Base
|
|
240
255
|
end
|
241
256
|
```
|
242
257
|
For more information on how and why to use secondary indices, see http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/SecondaryIndexes.html
|
243
|
-
## Testing
|
244
|
-
We've included an optional in-memory test client, so you don't necessarily have to connect to a real Dynamo instance when running tests. You could also use this in your development environment if you don't have a real Dynamo instance yet, but the data saved to it won't persist through a server restart.
|
245
|
-
|
246
|
-
To activate this feature, just require the testing module:
|
247
|
-
```ruby
|
248
|
-
require 'dynamini/testing'
|
249
|
-
```
|
250
|
-
This module replaces all API calls Dynamini makes to AWS DynamoDB with calls to Dynamini::TestClient.
|
251
|
-
|
252
|
-
The test client will not reset its database unless you tell it to, like so:
|
253
|
-
```ruby
|
254
|
-
Vehicle.client.reset
|
255
|
-
```
|
256
|
-
|
257
|
-
So, for instance, to get Rspec working with your test suite the way your ActiveRecord model behaved, add these lines to your spec_helper.rb:
|
258
|
-
```ruby
|
259
|
-
require 'dynamini/testing'
|
260
|
-
|
261
|
-
config.after(:each) {
|
262
|
-
Vehicle.client.reset # Large test suites will be very slow and unpredictable otherwise!
|
263
|
-
}
|
264
|
-
```
|
265
258
|
|
266
259
|
## Batch Saving
|
267
260
|
Dynamini implements DynamoDB's batch write operation, mapped to the .import method you might be used to from ActiveRecord. http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchWriteItem.html
|
@@ -294,6 +287,29 @@ Product.find('qwerty').updated_at
|
|
294
287
|
|
295
288
|
````
|
296
289
|
|
290
|
+
## Testing
|
291
|
+
We've included an optional in-memory test client, so you don't necessarily have to connect to a real Dynamo instance when running tests. You could also use this in your development environment if you don't have a real Dynamo instance yet, but the data saved to it won't persist through a server restart.
|
292
|
+
|
293
|
+
To activate this feature, just require the testing module:
|
294
|
+
```ruby
|
295
|
+
require 'dynamini/testing'
|
296
|
+
```
|
297
|
+
This module replaces all API calls Dynamini makes to AWS DynamoDB with calls to Dynamini::TestClient.
|
298
|
+
|
299
|
+
The test client will not reset its database unless you tell it to, like so:
|
300
|
+
```ruby
|
301
|
+
Vehicle.client.reset
|
302
|
+
```
|
303
|
+
|
304
|
+
So, for instance, to get Rspec working with your test suite the way your ActiveRecord model behaved, add these lines to your spec_helper.rb:
|
305
|
+
```ruby
|
306
|
+
require 'dynamini/testing'
|
307
|
+
|
308
|
+
config.after(:each) {
|
309
|
+
Vehicle.client.reset # Large test suites will be very slow and unpredictable otherwise!
|
310
|
+
}
|
311
|
+
```
|
312
|
+
|
297
313
|
## Things to remember
|
298
314
|
* Since DynamoDB is schemaless, your model will respond to any method that looks like a reader, meaning model.foo will return nil.
|
299
315
|
* You can also write any arbitrary attribute to your model.
|
data/dynamini.gemspec
CHANGED
data/lib/dynamini/attributes.rb
CHANGED
@@ -94,7 +94,7 @@ module Dynamini
|
|
94
94
|
|
95
95
|
def write_attribute(attribute, new_value, change: true, **options)
|
96
96
|
old_value = read_attribute(attribute)
|
97
|
-
if (handle = self.class.handles[attribute.to_sym])
|
97
|
+
if (handle = self.class.handles[attribute.to_sym])
|
98
98
|
new_value = self.class.attribute_callback(TypeHandler::SETTER_PROCS, handle, new_value, change)
|
99
99
|
end
|
100
100
|
@attributes[attribute] = new_value
|
@@ -4,33 +4,35 @@ module Dynamini
|
|
4
4
|
module TypeHandler
|
5
5
|
|
6
6
|
GETTER_PROCS = {
|
7
|
-
integer: proc { |v| v
|
7
|
+
integer: proc { |v| v&.to_i },
|
8
8
|
date: proc do |v|
|
9
9
|
if v.is_a?(Date)
|
10
10
|
v
|
11
|
-
|
11
|
+
elsif v
|
12
12
|
Time.methods.include?(:zone) ? Time.zone.at(v).to_date : Time.at(v).to_date
|
13
13
|
end
|
14
14
|
end,
|
15
15
|
time: proc do |v|
|
16
|
-
|
16
|
+
if v
|
17
|
+
Time.methods.include?(:zone) ? Time.zone.at(v.to_f) : Time.at(v.to_f)
|
18
|
+
end
|
17
19
|
end,
|
18
|
-
float: proc { |v| v
|
19
|
-
symbol: proc { |v| v
|
20
|
-
string: proc { |v| v
|
20
|
+
float: proc { |v| v&.to_f },
|
21
|
+
symbol: proc { |v| v&.to_sym },
|
22
|
+
string: proc { |v| v&.to_s },
|
21
23
|
boolean: proc { |v| v },
|
22
24
|
array: proc { |v| v.is_a?(Enumerable) ? v.to_a : [v] },
|
23
25
|
set: proc { |v| v.is_a?(Enumerable) ? Set.new(v) : Set.new([v]) }
|
24
26
|
}.freeze
|
25
27
|
|
26
28
|
SETTER_PROCS = {
|
27
|
-
integer: proc { |v| v
|
29
|
+
integer: proc { |v| v&.to_i },
|
28
30
|
time: proc { |v| (v.is_a?(Date) ? v.to_time : v).to_f },
|
29
|
-
float: proc { |v| v
|
30
|
-
symbol: proc { |v| v
|
31
|
-
string: proc { |v| v
|
31
|
+
float: proc { |v| v&.to_f },
|
32
|
+
symbol: proc { |v| v&.to_s },
|
33
|
+
string: proc { |v| v&.to_s },
|
32
34
|
boolean: proc { |v| v },
|
33
|
-
date: proc { |v| v
|
35
|
+
date: proc { |v| v&.to_time.to_f },
|
34
36
|
array: proc { |v| v.is_a?(Enumerable) ? v.to_a : [v] },
|
35
37
|
set: proc { |v| v.is_a?(Enumerable) ? Set.new(v) : Set.new([v]) }
|
36
38
|
}.freeze
|
@@ -91,6 +93,7 @@ module Dynamini
|
|
91
93
|
end
|
92
94
|
|
93
95
|
def attribute_callback(procs, handle, value, validate)
|
96
|
+
value = handle[:options][:default] if value.nil?
|
94
97
|
callback = procs[handle[:format]]
|
95
98
|
if should_convert_elements?(handle, value)
|
96
99
|
result = convert_elements(value, procs[handle[:options][:of]])
|
@@ -129,6 +129,16 @@ describe Dynamini::Attributes do
|
|
129
129
|
expect(model.foo).to eq([])
|
130
130
|
end
|
131
131
|
end
|
132
|
+
|
133
|
+
context 'when setting a handled attribute to its current value' do
|
134
|
+
it 'should not detect a change' do
|
135
|
+
Dynamini::Base.handle(:my_set, :set, of: :string)
|
136
|
+
Dynamini::Base.create!(id: '123', my_set: nil)
|
137
|
+
model = Dynamini::Base.find('123')
|
138
|
+
model.my_set = nil
|
139
|
+
expect(model.changes).to be_empty
|
140
|
+
end
|
141
|
+
end
|
132
142
|
end
|
133
143
|
|
134
144
|
describe '#delete_attribute' do
|
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.9.
|
4
|
+
version: 2.9.3
|
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: 2017-09-
|
18
|
+
date: 2017-09-13 00:00:00.000000000 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
21
|
name: activemodel
|