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