ocean-dynamo 1.6.1 → 1.7.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 +4 -4
- data/README.md +98 -97
- data/lib/ocean-dynamo/tables.rb +7 -6
- data/lib/ocean-dynamo/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a7ade2e3ad29ce511cb3537e6d800280da158c8a1eeb676ac767b0489c7c25d1
|
4
|
+
data.tar.gz: d1f727f3fcf58637e9888eeffcb3a4120b5107a79c400b7ae845936bd2f8eab7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7938d1e380cb4f93fa99682cc667f29480c73ee3dcb99e06f74b5fd8092c4f91f03039831e3391112f1c11d3d9f6f11a0a1e57b0c2343e59c3889bb435df2a89
|
7
|
+
data.tar.gz: 15eb31c9595bbabe5feed8474bb75b69139393dfeb262202ee5416162c66de998cc954558132b6c2589ebea4afc46a01d072ede7b08edaddef6eeaf4a80b5fc6
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
# ocean-dynamo
|
2
2
|
|
3
3
|
OceanDynamo is a massively scalable Amazon DynamoDB near drop-in replacement for
|
4
4
|
ActiveRecord.
|
@@ -9,7 +9,7 @@ but 5.1 will require a couple of changes.
|
|
9
9
|
{<img src="https://badge.fury.io/rb/ocean-dynamo.png" alt="Gem Version" />}[http://badge.fury.io/rb/ocean-dynamo]
|
10
10
|
|
11
11
|
|
12
|
-
|
12
|
+
## Features
|
13
13
|
|
14
14
|
As one important use case for OceanDynamo is to facilitate the conversion of SQL
|
15
15
|
databases to no-SQL DynamoDB databases, it is important that the syntax and semantics
|
@@ -18,8 +18,8 @@ callbacks, exceptions and method chaining semantics. OceanDynamo follows this pa
|
|
18
18
|
closely and is of course based on ActiveModel.
|
19
19
|
|
20
20
|
The attribute and persistence layer of OceanDynamo is modeled on that of ActiveRecord:
|
21
|
-
there's
|
22
|
-
|
21
|
+
there's `save`, `save!`, `create`, `update`, `update!`, `update_attributes`, `find_each`,
|
22
|
+
`destroy_all`, `delete_all`, `read_attribute`, `write_attribute` and all the other
|
23
23
|
methods you're used to. The design goal is always to implement as much of the ActiveRecord
|
24
24
|
interface as possible, without compromising scalability. This makes the task of switching
|
25
25
|
from SQL to no-SQL much easier.
|
@@ -34,22 +34,22 @@ with FactoryBot.
|
|
34
34
|
OceanDynamo supports optimistic record locking via a combination of DynamoDB conditional
|
35
35
|
writes and atomic updates. This is activated by default.
|
36
36
|
|
37
|
-
|
37
|
+
## Current State
|
38
38
|
|
39
39
|
* Secondary indices are now fully supported! See below for more information.
|
40
40
|
* Version 2 of the AWS Ruby SDK is now used.
|
41
41
|
* Work begun on association proxies, etc.
|
42
42
|
|
43
43
|
|
44
|
-
|
44
|
+
## Future milestones
|
45
45
|
|
46
46
|
* Direct support for the DynamoDB JSON attribute types for arrays and hashes
|
47
47
|
* Collection proxies, to implement ActiveRecord-style method chaining, e.g.:
|
48
48
|
<code>blog_entry.comments.build(body: "Cool!").save!</code>
|
49
|
-
* The
|
49
|
+
* The `has_and_belongs_to_many` assocation.
|
50
50
|
|
51
51
|
|
52
|
-
|
52
|
+
## Current use
|
53
53
|
|
54
54
|
OceanDynamo is used as a central component in Ocean, a Rails framework and development,
|
55
55
|
testing, and production pipeline for creating massively scalable HATEOAS microservice
|
@@ -59,15 +59,15 @@ SOAs in the cloud.
|
|
59
59
|
However, OceanDynamo can of course also be used stand-alone.
|
60
60
|
|
61
61
|
|
62
|
-
|
62
|
+
# Documentation
|
63
63
|
|
64
64
|
* Ocean-dynamo gem on Rubygems: https://rubygems.org/gems/ocean-dynamo
|
65
65
|
* Ocean-dynamo gem API: http://rubydoc.info/gems/ocean-dynamo/frames
|
66
|
-
* Ocean-dynamo source
|
66
|
+
* Ocean-dynamo source: https://gitlab.com/ocean-dev/ocean-dynamo
|
67
67
|
* AWS DynamoDB Ruby SDK v2: http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB.html
|
68
68
|
|
69
69
|
|
70
|
-
|
70
|
+
# Contributing
|
71
71
|
|
72
72
|
Contributions are welcome. Fork in the usual way. OceanDynamo is developed using
|
73
73
|
TDD: the specs are extensive and test coverage is very near to 100 percent. Pull requests
|
@@ -75,12 +75,12 @@ will not be considered unless all tests pass and coverage is equally high or hig
|
|
75
75
|
All contributed code must therefore also be exhaustively tested.
|
76
76
|
|
77
77
|
|
78
|
-
|
78
|
+
# Examples
|
79
79
|
|
80
|
-
|
80
|
+
## Basic syntax
|
81
81
|
|
82
82
|
The following example shows the basic syntax for declaring a DynamoDB-based schema.
|
83
|
-
|
83
|
+
```
|
84
84
|
class AsyncJob < OceanDynamo::Table
|
85
85
|
|
86
86
|
dynamo_schema(:guid) do
|
@@ -104,24 +104,25 @@ The following example shows the basic syntax for declaring a DynamoDB-based sche
|
|
104
104
|
end
|
105
105
|
|
106
106
|
end
|
107
|
+
```
|
107
108
|
|
108
|
-
|
109
|
+
## Attributes
|
109
110
|
|
110
|
-
Each attribute has a name, a type (
|
111
|
-
or
|
112
|
-
value, which can be a Proc. The hash key attribute is by default
|
113
|
-
the example above) and is a
|
111
|
+
Each attribute has a name, a type (`:string`, `:integer`, `:float`, `:datetime`, `:boolean`,
|
112
|
+
or `:serialized`) where `:string` is the default. Each attribute also optionally has a default
|
113
|
+
value, which can be a Proc. The hash key attribute is by default `:id` (overridden as `:guid` in
|
114
|
+
the example above) and is a `:string`.
|
114
115
|
|
115
|
-
The
|
116
|
+
The `:string`, `:integer`, `:float` and `:datetime` types can also store sets of their type.
|
116
117
|
Sets are represented as arrays, may not contain duplicates and may not be empty.
|
117
118
|
|
118
|
-
All attributes except the
|
119
|
+
All attributes except the `:string` type can take the value `nil`. Storing `nil` for a string
|
119
120
|
value will return the empty string, <tt>""</tt>.
|
120
121
|
|
121
|
-
|
122
|
-
|
123
|
-
+dynamo_schema+ takes args and many options. Here's the full syntax:
|
122
|
+
## Schema args and options
|
124
123
|
|
124
|
+
`dynamo_schema` takes args and many options. Here's the full syntax:
|
125
|
+
```
|
125
126
|
dynamo_schema(
|
126
127
|
table_hash_key = :id, # The name of the hash key attribute
|
127
128
|
table_range_key = nil, # The name of the range key attribute (or nil)
|
@@ -139,22 +140,23 @@ value will return the empty string, <tt>""</tt>.
|
|
139
140
|
...
|
140
141
|
...
|
141
142
|
end
|
142
|
-
|
143
|
-
Tip: you might want to use the following idiom when declaring a
|
144
|
-
|
145
|
-
dynamo_schema(:id, table_name_suffix: Api.basename_suffix,
|
146
|
-
create: Rails.env != "production") do
|
143
|
+
```
|
144
|
+
Tip: you might want to use the following idiom when declaring a `dynamo_schema`. It is used everywhere in Ocean:
|
145
|
+
```
|
146
|
+
dynamo_schema(:id, table_name_suffix: Api.basename_suffix, create: true) do
|
147
147
|
...
|
148
148
|
end
|
149
|
+
```
|
150
|
+
This will auto-create the DynamoDB table in all environments and also add a
|
151
|
+
suffix to the table name in order to prevent collisions during tests.
|
152
|
+
Cf `Api.basename_suffix` in the `ocean-rails` gem for more information.
|
149
153
|
|
150
|
-
|
151
|
-
|
152
|
-
== +has_many+ and +belongs_to+
|
153
|
-
|
154
|
-
=== Example
|
154
|
+
# has_many and belongs_to
|
155
155
|
|
156
|
-
|
156
|
+
## Example
|
157
157
|
|
158
|
+
The following example shows how to set up `has_many` / `belongs_to` relations:
|
159
|
+
```
|
158
160
|
class Forum < OceanDynamo::Table
|
159
161
|
dynamo_schema do
|
160
162
|
attribute :name
|
@@ -179,38 +181,38 @@ The following example shows how to set up +has_many+ / +belongs_to+ relations:
|
|
179
181
|
end
|
180
182
|
belongs_to :topic, composite_key: true
|
181
183
|
end
|
182
|
-
|
184
|
+
```
|
183
185
|
The only non-standard aspect of the above is <tt>composite_key: true</tt>, which
|
184
|
-
is required as the Topic class itself has a
|
186
|
+
is required as the Topic class itself has a `belongs_to` relation and thus has
|
185
187
|
a composite key. This must be declared in the child class as it needs to know
|
186
188
|
how to retrieve its parent.
|
187
189
|
|
188
190
|
|
189
|
-
|
191
|
+
## Restrictions
|
190
192
|
|
191
|
-
Restrictions for
|
192
|
-
* The hash key must be specified and must not be
|
193
|
+
Restrictions for `belongs_to` tables:
|
194
|
+
* The hash key must be specified and must not be `:id`.
|
193
195
|
* The range key must not be specified at all.
|
194
|
-
*
|
195
|
-
*
|
196
|
+
* `belongs_to` can be specified only once in each class.
|
197
|
+
* `belongs_to` must be placed after the `dynamo_schema` attribute block.
|
196
198
|
|
197
|
-
Restrictions for
|
198
|
-
*
|
199
|
+
Restrictions for `has_many` tables:
|
200
|
+
* `has_many` must be placed after the `dynamo_schema` attribute block.
|
199
201
|
|
200
|
-
These restrictions allow OceanDynamo to implement the
|
202
|
+
These restrictions allow OceanDynamo to implement the `has_many` / `belongs_to`
|
201
203
|
relation in a very efficient and massively scalable way.
|
202
204
|
|
203
205
|
|
204
|
-
|
206
|
+
## Implementation
|
205
207
|
|
206
|
-
|
208
|
+
`belongs_to` claims the range key and uses it to store its own id, which normally
|
207
209
|
would be stored in the hash key attribute. Instead, the hash key attribute holds the
|
208
210
|
id of the parent. We have thus reversed the roles of these two fields. As a result,
|
209
211
|
all children store their parent id in the hash key, and their own id in the
|
210
212
|
range key.
|
211
213
|
|
212
214
|
This type of relation is even more efficient than its ActiveRecord counterpart as
|
213
|
-
it uses only primary indices in both directions of the
|
215
|
+
it uses only primary indices in both directions of the `has_many` / `belongs_to`
|
214
216
|
association. No scans.
|
215
217
|
|
216
218
|
Furthermore, since DynamoDB has powerful primary index searches involving substrings
|
@@ -218,18 +220,18 @@ and matching, the fact that the range key is a string can be used to implement
|
|
218
220
|
wildcard matching of additional attributes. This gives, amongst other things, the
|
219
221
|
equivalent of an SQL GROUP BY request, again without requiring any secondary indices.
|
220
222
|
|
221
|
-
It's our goal to use a similar technique to implement
|
223
|
+
It's our goal to use a similar technique to implement `has_and_belongs_to_many` relations,
|
222
224
|
which means that secondary indices won't be necessary for the vast majority of
|
223
225
|
DynamoDB tables. This ultimately means reduced operational costs, as well as
|
224
226
|
reduced complexity.
|
225
227
|
|
226
228
|
|
227
|
-
|
229
|
+
# Secondary Indices
|
228
230
|
|
229
|
-
|
231
|
+
## Local Secondary Indices
|
230
232
|
|
231
233
|
Up to five attributes can be declared as local secondary indices, in the following manner:
|
232
|
-
|
234
|
+
```
|
233
235
|
class Authentication < OceanDynamo::Table
|
234
236
|
dynamo_schema(:username, :expires_at) do
|
235
237
|
attribute :token, :string, local_secondary_index: true
|
@@ -239,10 +241,10 @@ Up to five attributes can be declared as local secondary indices, in the followi
|
|
239
241
|
attribute :api_user_id, :string
|
240
242
|
end
|
241
243
|
end
|
242
|
-
|
243
|
-
The items of the above table can be accessed by using the hash key
|
244
|
-
the range key
|
245
|
-
to access items using the same hash key
|
244
|
+
```
|
245
|
+
The items of the above table can be accessed by using the hash key `:username` and
|
246
|
+
the range key `:expires_at`. The `local_secondary_index` declaration makes it possible
|
247
|
+
to access items using the same hash key `:username` but with `:token` as an alternate
|
246
248
|
range key. Local secondary indices all use the same hash key as the primary index,
|
247
249
|
substituting another attribute instead as the range key.
|
248
250
|
|
@@ -251,29 +253,29 @@ combination of keys. Secondary indices don't require the range key to be unique
|
|
251
253
|
the same hash key. This means that secondary index searches always will return a
|
252
254
|
collection.
|
253
255
|
|
254
|
-
Local secondary indices are queried through
|
256
|
+
Local secondary indices are queried through `find_local_each` and `find_local`.
|
255
257
|
They take the same arguments; the former yields to a block for each item,
|
256
258
|
the other returns all items in an array.
|
257
259
|
|
258
|
-
The following finds all Authentications where
|
259
|
-
|
260
|
+
The following finds all Authentications where `:username` is "joe" and `:token` is "quux":
|
261
|
+
```
|
260
262
|
Authentication.find_local(:username, "joe", :token, "=", "quux")
|
261
|
-
|
262
|
-
This retrieves all Authentications belonging to Joe, sorted on
|
263
|
-
|
263
|
+
```
|
264
|
+
This retrieves all Authentications belonging to Joe, sorted on `:token`:
|
265
|
+
```
|
264
266
|
Authentication.find_local(:username, "joe", :token, ">=", "0")
|
265
|
-
|
267
|
+
```
|
266
268
|
The same thing but with the only the item with the highest token value:
|
267
|
-
|
269
|
+
```
|
268
270
|
Authentication.find_local(:username, "joe", :token, ">=", "0",
|
269
271
|
scan_index_forward: false, limit: 1)
|
272
|
+
```
|
270
273
|
|
274
|
+
## Global Secondary Indices
|
271
275
|
|
272
|
-
|
273
|
-
|
274
|
-
Global secondary indices are declared after all attributes, but still within the +do+
|
276
|
+
Global secondary indices are declared after all attributes, but still within the `do`
|
275
277
|
block:
|
276
|
-
|
278
|
+
```
|
277
279
|
class Authentication < OceanDynamo::Table
|
278
280
|
dynamo_schema(:username, :expires_at) do
|
279
281
|
attribute :token, :string, local_secondary_index: true
|
@@ -287,41 +289,40 @@ block:
|
|
287
289
|
global_secondary_index :expires_at, write_capacity_units: 50
|
288
290
|
end
|
289
291
|
end
|
290
|
-
|
291
|
-
Each
|
292
|
+
```
|
293
|
+
Each `global_secondary_index` clause (there can be a maximum of 5 per table) takes
|
292
294
|
the following arguments:
|
293
|
-
*
|
294
|
-
*
|
295
|
-
*
|
296
|
-
*
|
297
|
-
*
|
295
|
+
* `hash_value` (required),
|
296
|
+
* `range_value` (optional),
|
297
|
+
* `:projection` (default `:keys_only`, `:all` for all attributes)
|
298
|
+
* `:read_capacity_units` (defaults to the table's read capacity, normally 10)
|
299
|
+
* `:write_capacity_units` (default to the table's write capacity, normally 5)
|
298
300
|
|
299
|
-
Global secondary indices are queried through
|
301
|
+
Global secondary indices are queried through `find_global_each` and `find_global`.
|
300
302
|
They take the same arguments; the former yields to a block for each item,
|
301
303
|
the other returns all items in an array.
|
302
304
|
|
303
|
-
The following finds all Authentications whose
|
304
|
-
|
305
|
+
The following finds all Authentications whose `:token` is `"quux"`:
|
306
|
+
```
|
305
307
|
Authentication.find_global(:token, "quux")
|
306
|
-
|
307
|
-
This retrieves all Authentications belonging to the user with the ID
|
308
|
-
sorted in ascending order of the
|
309
|
-
|
308
|
+
```
|
309
|
+
This retrieves all Authentications belonging to the user with the ID `"dfstw-ruyhdf-ewijf"`,
|
310
|
+
sorted in ascending order of the `:expires_at` attribute:
|
311
|
+
```
|
310
312
|
Authentication.find_global(:api_user_id, "dfstw-ruyhdf-ewijf",
|
311
313
|
:expires_at, ">=", 0)
|
312
|
-
|
313
|
-
To get the highest
|
314
|
-
|
314
|
+
```
|
315
|
+
To get the highest `:expires_at` record:
|
316
|
+
```
|
315
317
|
Authentication.find_global(:api_user_id, "dfstw-ruyhdf-ewijf",
|
316
318
|
:expires_at, ">=", 0,
|
317
319
|
scan_index_forward: false, limit: 1)
|
320
|
+
```
|
318
321
|
|
319
|
-
|
320
|
-
== Installation
|
322
|
+
# Installation
|
321
323
|
```
|
322
324
|
gem install ocean-dynamo
|
323
325
|
```
|
324
|
-
|
325
326
|
Ocean-dynamo relies on Aws.config to fetch configuration data. In Ocean, we use
|
326
327
|
an init file similar to this one:
|
327
328
|
```
|
@@ -347,12 +348,12 @@ Aws.config.update(cfg)
|
|
347
348
|
```
|
348
349
|
As you can see, this relies on ENV variables to choose from three different
|
349
350
|
setups:
|
350
|
-
|
351
|
+
1. If you're using discrete AWS credentials, set AWS_REGION, AWS_ACCESS_KEY_ID,
|
351
352
|
and AWS_SECRET_ACCESS_KEY.
|
352
|
-
|
353
|
-
|
353
|
+
2. If you're using localstack, provide AWS_DYNAMODB_ENDPOINT (http://localhost:4569).
|
354
|
+
3. If you're using EC2 or ECS instance profile credentials, set only AWS_REGION.
|
354
355
|
|
355
|
-
|
356
|
+
# Running the specs
|
356
357
|
|
357
358
|
We're using localstack (https://github.com/localstack/localstack) for development
|
358
359
|
and testing. Install (we use a Docker container) and run it. If you're using
|
@@ -360,18 +361,18 @@ ocean-dynamo with the Ocean Rails context, you can install and start it by
|
|
360
361
|
following the instructions in the `ocean` repo.
|
361
362
|
|
362
363
|
With localstack running, you should now be able to do
|
363
|
-
|
364
|
+
```
|
364
365
|
bundle exec rspec
|
365
|
-
|
366
|
+
```
|
366
367
|
All tests should pass.
|
367
368
|
|
368
|
-
|
369
|
+
# Rails console
|
369
370
|
|
370
371
|
The Rails console is available from the built-in dummy application:
|
371
|
-
|
372
|
+
```
|
372
373
|
cd spec/dummy
|
373
374
|
rails console
|
374
|
-
|
375
|
+
```
|
375
376
|
This will, amongst other things, also create the CloudModel table if it doesn't already
|
376
377
|
exist. On Amazon, this will take a little while. With DynamoDB Local, it's practically
|
377
378
|
instant.
|
data/lib/ocean-dynamo/tables.rb
CHANGED
@@ -4,7 +4,7 @@ module OceanDynamo
|
|
4
4
|
def self.included(base)
|
5
5
|
base.extend(ClassMethods)
|
6
6
|
end
|
7
|
-
|
7
|
+
|
8
8
|
|
9
9
|
# ---------------------------------------------------------
|
10
10
|
#
|
@@ -14,7 +14,7 @@ module OceanDynamo
|
|
14
14
|
|
15
15
|
module ClassMethods
|
16
16
|
|
17
|
-
def dynamo_schema(table_hash_key=:id,
|
17
|
+
def dynamo_schema(table_hash_key=:id,
|
18
18
|
table_range_key=nil,
|
19
19
|
table_name: compute_table_name,
|
20
20
|
table_name_prefix: nil,
|
@@ -23,9 +23,10 @@ module OceanDynamo
|
|
23
23
|
write_capacity_units: 5,
|
24
24
|
connect: :late,
|
25
25
|
create: false,
|
26
|
+
client: nil,
|
26
27
|
**keywords,
|
27
28
|
&block)
|
28
|
-
self.dynamo_client =
|
29
|
+
self.dynamo_client = client
|
29
30
|
self.dynamo_resource = nil
|
30
31
|
self.dynamo_table = nil
|
31
32
|
self.table_connected = false
|
@@ -131,7 +132,7 @@ module OceanDynamo
|
|
131
132
|
#puts "Updating table #{table_full_name}"
|
132
133
|
# attrs = table_attribute_definitions
|
133
134
|
# active_attrs = []
|
134
|
-
# dynamo_table.attribute_definitions.each do |k|
|
135
|
+
# dynamo_table.attribute_definitions.each do |k|
|
135
136
|
# active_attrs << { attribute_name: k.attribute_name, attribute_type: k.attribute_type }
|
136
137
|
# end
|
137
138
|
# return false if active_attrs == attrs
|
@@ -149,7 +150,7 @@ module OceanDynamo
|
|
149
150
|
attrs << { attribute_name: name, attribute_type: attribute_type(name) }
|
150
151
|
end
|
151
152
|
global_secondary_indexes.each do |index_name, data|
|
152
|
-
data["keys"].each do |name|
|
153
|
+
data["keys"].each do |name|
|
153
154
|
next if attrs.any? { |a| a[:attribute_name] == name }
|
154
155
|
attrs << { attribute_name: name, attribute_type: attribute_type(name) }
|
155
156
|
end
|
@@ -221,7 +222,7 @@ module OceanDynamo
|
|
221
222
|
end
|
222
223
|
|
223
224
|
end
|
224
|
-
|
225
|
+
|
225
226
|
|
226
227
|
# ---------------------------------------------------------
|
227
228
|
#
|
data/lib/ocean-dynamo/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ocean-dynamo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter Bengtson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-09-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk
|