ocean-dynamo 1.1.0 → 1.2.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.rdoc +51 -24
- data/lib/ocean-dynamo/associations/has_many.rb +3 -2
- data/lib/ocean-dynamo/attributes.rb +1 -0
- data/lib/ocean-dynamo/class_variables.rb +3 -0
- data/lib/ocean-dynamo/queries.rb +12 -5
- data/lib/ocean-dynamo/schema.rb +20 -0
- data/lib/ocean-dynamo/tables.rb +40 -18
- data/lib/ocean-dynamo/version.rb +1 -1
- metadata +15 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a277c429cc29e02d55452ba4bca425a2b330cd4
|
4
|
+
data.tar.gz: d21e7ae3f0dcd90e5cf993bc43f5ccb4e6148e26
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eac3188b5e301a633ee829651adae2c9f228f824e35d3ffb798abb3ff6e382ca76b9ed0e985957aebdd83207875608fc2e31843cbe25cf8547988a3a44dc4885
|
7
|
+
data.tar.gz: 42b49827d66a23bb4a65ec9ed55c24ad25a34fdebca03b61648b6615aed753c5339e737eca41e6f58ab3057d381defc3887946df00cfb632350dedfbcf853c98
|
data/README.rdoc
CHANGED
@@ -33,7 +33,7 @@ with FactoryGirl.
|
|
33
33
|
|
34
34
|
=== Current State
|
35
35
|
|
36
|
-
*
|
36
|
+
* Secondary indices are now supported! See below for more information.
|
37
37
|
* Version 2 of the AWS Ruby SDK is now used. This required an internal reorganisation,
|
38
38
|
but it also gives us access to local and global secondary indices.
|
39
39
|
* Work begun on association proxies, etc.
|
@@ -76,9 +76,9 @@ will not be considered unless all tests pass and coverage is equally high or hig
|
|
76
76
|
All contributed code must therefore also be exhaustively tested.
|
77
77
|
|
78
78
|
|
79
|
-
|
79
|
+
== Examples
|
80
80
|
|
81
|
-
|
81
|
+
=== Basic syntax
|
82
82
|
|
83
83
|
The following example shows the basic syntax for declaring a DynamoDB-based schema.
|
84
84
|
|
@@ -104,7 +104,7 @@ The following example shows the basic syntax for declaring a DynamoDB-based sche
|
|
104
104
|
|
105
105
|
end
|
106
106
|
|
107
|
-
|
107
|
+
=== Attributes
|
108
108
|
|
109
109
|
Each attribute has a name, a type (+:string+, +:integer+, +:float+, +:datetime+, +:boolean+,
|
110
110
|
or +:serialized+) where +:string+ is the default. Each attribute also optionally has a default
|
@@ -117,7 +117,7 @@ Sets are represented as arrays, may not contain duplicates and may not be empty.
|
|
117
117
|
All attributes except the +:string+ type can take the value +nil+. Storing +nil+ for a string
|
118
118
|
value will return the empty string, <tt>""</tt>.
|
119
119
|
|
120
|
-
|
120
|
+
=== Schema args and options
|
121
121
|
|
122
122
|
+dynamo_schema+ takes args and many options. Here's the full syntax:
|
123
123
|
|
@@ -139,9 +139,9 @@ value will return the empty string, <tt>""</tt>.
|
|
139
139
|
...
|
140
140
|
end
|
141
141
|
|
142
|
-
|
142
|
+
== +has_many+ and +belongs_to+
|
143
143
|
|
144
|
-
|
144
|
+
=== Example
|
145
145
|
|
146
146
|
The following example shows how to set up +has_many+ / +belongs_to+ relations:
|
147
147
|
|
@@ -175,7 +175,7 @@ is required as the Topic class itself has a +belongs_to+ relation and thus has
|
|
175
175
|
a composite key. This must be declared in the child class as it needs to know
|
176
176
|
how to retrieve its parent.
|
177
177
|
|
178
|
-
|
178
|
+
=== Restrictions
|
179
179
|
|
180
180
|
Restrictions for +belongs_to+ tables:
|
181
181
|
* The hash key must be specified and must not be +:id+.
|
@@ -189,7 +189,7 @@ Restrictions for +has_many+ tables:
|
|
189
189
|
These restrictions allow OceanDynamo to implement the +has_many+ / +belongs_to+
|
190
190
|
relation in a very efficient and massively scalable way.
|
191
191
|
|
192
|
-
|
192
|
+
=== Implementation
|
193
193
|
|
194
194
|
+belongs_to+ claims the range key and uses it to store its own id, which normally
|
195
195
|
would be stored in the hash key attribute. Instead, the hash key attribute holds the
|
@@ -214,16 +214,24 @@ reduced complexity.
|
|
214
214
|
Nevertheless, as we now have switched to v2 of the DynamoDB API, we will be adding
|
215
215
|
the possibility to define both local and secondary indices for Tables.
|
216
216
|
|
217
|
+
== Secondary Indices
|
218
|
+
|
219
|
+
We now have basic support for secondary indices.
|
220
|
+
|
221
|
+
We will add high-level support for specifying that a secondary index is to be used
|
222
|
+
in +in_batches+ and +find_each+ etc, but for now you will have to specify that a
|
223
|
+
secondary index is to be used explicitly in the options passed to +in_batches+,
|
224
|
+
+query+, and +scan+.
|
225
|
+
* +query+: http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Table.html#query-instance_method
|
226
|
+
* +scan+: http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Table.html#scan-instance_method
|
227
|
+
* +in_batches+, +find_each+: lib/ocean-dynamo/queries.rb
|
217
228
|
|
218
229
|
=== Local Secondary Indices
|
219
230
|
|
220
|
-
|
221
|
-
be declared as local secondary indices, in the following manner:
|
231
|
+
Up to five attributes can be declared as local secondary indices, in the following manner:
|
222
232
|
|
223
233
|
class Authentication < OceanDynamo::Table
|
224
|
-
dynamo_schema(:username, :expires_at
|
225
|
-
timestamps: nil, locking: false
|
226
|
-
table_name_suffix: Api.basename_suffix) do
|
234
|
+
dynamo_schema(:username, :expires_at) do
|
227
235
|
attribute :token, :string, local_secondary_index: true
|
228
236
|
attribute :max_age, :integer
|
229
237
|
attribute :created_at, :datetime
|
@@ -232,23 +240,42 @@ be declared as local secondary indices, in the following manner:
|
|
232
240
|
end
|
233
241
|
end
|
234
242
|
|
235
|
-
The items of the above table can be accessed by using the hash key
|
236
|
-
the range key
|
237
|
-
to access items using the same hash key
|
243
|
+
The items of the above table can be accessed by using the hash key +:username+ and
|
244
|
+
the range key +:expires_at+. The +local_secondary_index+ declaration makes it possible
|
245
|
+
to access items using the same hash key +:username+ but with +:token+ as an alternate
|
238
246
|
range key. Local secondary indices all use the same hash key as the primary index,
|
239
247
|
substituting another attribute instead as the range key.
|
240
248
|
|
241
|
-
NB: The primary index [:username, :expires_at] requires all items to have a unique
|
249
|
+
NB: The primary index <tt>[:username, :expires_at]</tt> requires all items to have a unique
|
242
250
|
combination of keys. Secondary indices don't require the range key to be unique for
|
243
251
|
the same hash key. This means that secondary index searches always will return a
|
244
252
|
collection.
|
245
253
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
254
|
+
=== Global Secondary Indices
|
255
|
+
|
256
|
+
Global secondary indices are declared after all attributes, but still within the +do+
|
257
|
+
block:
|
258
|
+
|
259
|
+
class Authentication < OceanDynamo::Table
|
260
|
+
dynamo_schema(:username, :expires_at) do
|
261
|
+
attribute :token, :string, local_secondary_index: true
|
262
|
+
attribute :max_age, :integer
|
263
|
+
attribute :created_at, :datetime
|
264
|
+
attribute :expires_at, :datetime
|
265
|
+
attribute :api_user_id, :string
|
266
|
+
|
267
|
+
global_secondary_index :token, projection: :all
|
268
|
+
global_secondary_index :api_user_id, :expires_at, read_capacity_units: 100
|
269
|
+
global_secondary_index :expires_at, write_capacity_units: 50
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
Each +global_secondary_index+ clause takes the following arguments:
|
274
|
+
* +hash_value+ (required),
|
275
|
+
* +range_value+ (optional),
|
276
|
+
* +:projection (default :keys_only, :all for all attributes)
|
277
|
+
* read_capacity_units (default 10)
|
278
|
+
* write_capacity_units (default 5)
|
252
279
|
|
253
280
|
|
254
281
|
== Installation
|
@@ -119,6 +119,7 @@ module OceanDynamo
|
|
119
119
|
}
|
120
120
|
end
|
121
121
|
|
122
|
+
|
122
123
|
#
|
123
124
|
# Reads all children of a has_many relation.
|
124
125
|
#
|
@@ -184,9 +185,9 @@ module OceanDynamo
|
|
184
185
|
child_hash_key = child_class.table_hash_key.to_s
|
185
186
|
child_range_key = child_class.table_range_key && child_class.table_range_key.to_s
|
186
187
|
child_class.in_batches :query, opts do |attrs|
|
187
|
-
# There is no way in the DynamoDB API to update a key attribute.
|
188
|
+
# There is no way in the DynamoDB API to update a primary key attribute.
|
189
|
+
# Delete the child item and recreate it with the updated key.
|
188
190
|
child_class.delete attrs[child_hash_key], child_range_key && attrs[child_range_key]
|
189
|
-
# Create a new one with NULL for key
|
190
191
|
attrs[child_hash_key] = "NULL"
|
191
192
|
child_class.dynamo_table.put_item(item: attrs)
|
192
193
|
end
|
@@ -25,6 +25,7 @@ module OceanDynamo
|
|
25
25
|
# Init
|
26
26
|
self.fields = HashWithIndifferentAccess.new
|
27
27
|
attribute(table_hash_key, :string, default: "")
|
28
|
+
self.global_secondary_indexes = Hash.new
|
28
29
|
if table_range_key
|
29
30
|
attribute(table_range_key, :string, default: "")
|
30
31
|
self.validates(table_range_key, presence: true)
|
data/lib/ocean-dynamo/queries.rb
CHANGED
@@ -59,6 +59,10 @@ module OceanDynamo
|
|
59
59
|
# +message+ must be either :scan or :query.
|
60
60
|
# +options+ is the hash of options to pass to the scan or query operation.
|
61
61
|
#
|
62
|
+
# TODO: Add support for
|
63
|
+
# index_name: "IndexName",
|
64
|
+
# select: "ALL_ATTRIBUTES", # ALL_ATTRIBUTES, ALL_PROJECTED_ATTRIBUTES, SPECIFIC_ATTRIBUTES, COUNT
|
65
|
+
#
|
62
66
|
def in_batches(message, options, &block)
|
63
67
|
_late_connect?
|
64
68
|
loop do
|
@@ -74,14 +78,17 @@ module OceanDynamo
|
|
74
78
|
|
75
79
|
#
|
76
80
|
# Looping through a collection of records from the database (using the +all+ method,
|
77
|
-
# for example) is very inefficient since it will try to instantiate all the objects at
|
78
|
-
#
|
79
|
-
# In that case, batch processing methods allow you to work with the records in batches,
|
81
|
+
# for example) is very inefficient since it will try to instantiate all the objects at
|
82
|
+
# once. Batch processing methods allow you to work with the records in batches,
|
80
83
|
# thereby greatly reducing memory consumption.
|
81
84
|
#
|
82
|
-
|
85
|
+
# TODO: Add support for
|
86
|
+
# index_name: "IndexName",
|
87
|
+
# select: "ALL_ATTRIBUTES", # ALL_ATTRIBUTES, ALL_PROJECTED_ATTRIBUTES, SPECIFIC_ATTRIBUTES, COUNT
|
88
|
+
#
|
89
|
+
def find_each(consistent: false, limit: nil, batch_size: nil)
|
83
90
|
options = { consistent_read: consistent }
|
84
|
-
batch_size = limit if limit && limit < batch_size
|
91
|
+
batch_size = limit if limit && batch_size && limit < batch_size
|
85
92
|
options[:limit] = batch_size if batch_size
|
86
93
|
in_batches :scan, options do |attrs|
|
87
94
|
if limit
|
data/lib/ocean-dynamo/schema.rb
CHANGED
@@ -39,6 +39,26 @@ module OceanDynamo
|
|
39
39
|
end
|
40
40
|
|
41
41
|
|
42
|
+
def global_secondary_index(hash_key, range_key=nil,
|
43
|
+
projection: :keys_only,
|
44
|
+
read_capacity_units: 10,
|
45
|
+
write_capacity_units: 5)
|
46
|
+
if range_key
|
47
|
+
name = "#{hash_key}_#{range_key}"
|
48
|
+
keys = [hash_key.to_s, range_key.to_s]
|
49
|
+
else
|
50
|
+
name = "#{hash_key}"
|
51
|
+
keys = [hash_key.to_s]
|
52
|
+
end
|
53
|
+
self.global_secondary_indexes[name] = {
|
54
|
+
"keys" => keys,
|
55
|
+
"projection_type" => projection.to_s.upcase,
|
56
|
+
"read_capacity_units" => read_capacity_units,
|
57
|
+
"write_capacity_units" => write_capacity_units
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
|
42
62
|
protected
|
43
63
|
|
44
64
|
def dangerous_attributes # :nodoc:
|
data/lib/ocean-dynamo/tables.rb
CHANGED
@@ -94,7 +94,7 @@ module OceanDynamo
|
|
94
94
|
end
|
95
95
|
|
96
96
|
|
97
|
-
|
97
|
+
def create_table
|
98
98
|
attrs = table_attribute_definitions # This already includes secondary indices
|
99
99
|
keys = table_key_schema
|
100
100
|
options = {
|
@@ -108,6 +108,8 @@ module OceanDynamo
|
|
108
108
|
}
|
109
109
|
lsi = local_secondary_indexes.collect { |n| local_secondary_index_declaration n }
|
110
110
|
options[:local_secondary_indexes] = lsi unless lsi.blank?
|
111
|
+
gsi = global_secondary_indexes.collect { |k, v| global_secondary_index_declaration k, v }
|
112
|
+
options[:global_secondary_indexes] = gsi unless gsi.blank?
|
111
113
|
dynamo_resource.create_table(options)
|
112
114
|
sleep 1 until dynamo_table.table_status == "ACTIVE"
|
113
115
|
setup_dynamo
|
@@ -128,6 +130,31 @@ module OceanDynamo
|
|
128
130
|
end
|
129
131
|
|
130
132
|
|
133
|
+
def table_attribute_definitions
|
134
|
+
attrs = []
|
135
|
+
attrs << { attribute_name: table_hash_key.to_s, attribute_type: attribute_type(table_hash_key) }
|
136
|
+
attrs << { attribute_name: table_range_key.to_s, attribute_type: attribute_type(table_range_key) } if table_range_key
|
137
|
+
local_secondary_indexes.each do |name|
|
138
|
+
attrs << { attribute_name: name, attribute_type: attribute_type(name) }
|
139
|
+
end
|
140
|
+
global_secondary_indexes.each do |index_name, data|
|
141
|
+
data["keys"].each do |name|
|
142
|
+
next if attrs.any? { |a| a[:attribute_name] == name }
|
143
|
+
attrs << { attribute_name: name, attribute_type: attribute_type(name) }
|
144
|
+
end
|
145
|
+
end
|
146
|
+
attrs
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
def table_key_schema(hash_key: table_hash_key, range_key: table_range_key)
|
151
|
+
keys = []
|
152
|
+
keys << { attribute_name: hash_key.to_s, key_type: "HASH" }
|
153
|
+
keys << { attribute_name: range_key.to_s, key_type: "RANGE" } if range_key
|
154
|
+
keys
|
155
|
+
end
|
156
|
+
|
157
|
+
|
131
158
|
def local_secondary_indexes
|
132
159
|
@local_secondary_indexes ||= begin
|
133
160
|
result = []
|
@@ -141,28 +168,23 @@ module OceanDynamo
|
|
141
168
|
|
142
169
|
def local_secondary_index_declaration(name)
|
143
170
|
{ index_name: name,
|
144
|
-
key_schema:
|
145
|
-
{ attribute_name: name.to_s, key_type: "RANGE" }],
|
171
|
+
key_schema: table_key_schema(range_key: name),
|
146
172
|
projection: { projection_type: "KEYS_ONLY" }
|
147
173
|
}
|
148
174
|
end
|
149
175
|
|
150
176
|
|
151
|
-
def
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
keys = []
|
163
|
-
keys << { attribute_name: table_hash_key.to_s, key_type: "HASH" }
|
164
|
-
keys << { attribute_name: table_range_key.to_s, key_type: "RANGE" } if table_range_key
|
165
|
-
keys
|
177
|
+
def global_secondary_index_declaration(index_name, data)
|
178
|
+
hash_key, range_key = data["keys"]
|
179
|
+
key_schema = table_key_schema(hash_key: hash_key, range_key: range_key)
|
180
|
+
{ index_name: index_name,
|
181
|
+
key_schema: key_schema,
|
182
|
+
projection: { projection_type: data["projection_type"] },
|
183
|
+
provisioned_throughput: {
|
184
|
+
read_capacity_units: data["read_capacity_units"],
|
185
|
+
write_capacity_units: data["write_capacity_units"]
|
186
|
+
}
|
187
|
+
}
|
166
188
|
end
|
167
189
|
|
168
190
|
|
data/lib/ocean-dynamo/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ocean-dynamo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter Bengtson
|
@@ -136,20 +136,20 @@ dependencies:
|
|
136
136
|
- - ">="
|
137
137
|
- !ruby/object:Gem::Version
|
138
138
|
version: '0'
|
139
|
-
description: "== OceanDynamo\n\
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
139
|
+
description: "== OceanDynamo\n\nAs one important use case for OceanDynamo is to facilitate
|
140
|
+
the conversion of SQL\ndatabases to no-SQL DynamoDB databases, it is important that
|
141
|
+
the syntax and semantics\nof OceanDynamo are as close as possible to those of ActiveRecord.
|
142
|
+
This includes\ncallbacks, exceptions and method chaining semantics. OceanDynamo
|
143
|
+
follows this pattern \nclosely and is of course based on ActiveModel.\n\nThe attribute
|
144
|
+
and persistence layer of OceanDynamo is modeled on that of ActiveRecord:\nthere's
|
145
|
+
+save+, +save!+, +create+, +update+, +update!+, +update_attributes+, +find_each+,\n+destroy_all+,
|
146
|
+
+delete_all+, +read_attribute+, +write_attribute+ and all the other \nmethods you're
|
147
|
+
used to. The design goal is always to implement as much of the ActiveRecord\ninterface
|
148
|
+
as possible, without compromising scalability. This makes the task of switching
|
149
|
+
\nfrom SQL to no-SQL much easier.\n\nOceanDynamo uses only primary indices to retrieve
|
150
|
+
related table items and collections, \nwhich means it will scale without limits.\n\nOceanDynamo
|
151
|
+
is fully usable as an ActiveModel and can be used by Rails\ncontrollers. Thanks
|
152
|
+
to its structural similarity to ActiveRecord, OceanDynamo works \nwith FactoryGirl.\n\nSee
|
153
153
|
also Ocean, a Rails framework for creating highly scalable SOAs in the cloud, in
|
154
154
|
which\nocean-dynamo is used as a central component: http://wiki.oceanframework.net"
|
155
155
|
email:
|