ocean-dynamo 1.6.1 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|