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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5a5d6a93318aea2c36029d1f40d3496ad5cc16a1
4
- data.tar.gz: 17f2f4d1edf4885662aea89e61948924d7e2bfdb
3
+ metadata.gz: 0a277c429cc29e02d55452ba4bca425a2b330cd4
4
+ data.tar.gz: d21e7ae3f0dcd90e5cf993bc43f5ccb4e6148e26
5
5
  SHA512:
6
- metadata.gz: d3f9aff5b1b2f3402946acb6c5806b32582e3baeefa758ea4d35183386cde1bb937e88204a8feb8fddabc107b16c13540c334bd0ddf04ce91b39822e9a3c6284
7
- data.tar.gz: 1cfb33f43e6888a7f7f7417900baf99b9d777ef2797a52d96f5af36e090bedd46f2543210f62237f7e4db547570a67094cb1bd3e094ec13e36671b11154fe1eb
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
- * Local secondary indices are now supported! See below for more information.
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
- === Examples
79
+ == Examples
80
80
 
81
- ==== Basic syntax
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
- ==== Attributes
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
- ==== Schema args and options
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
- === +has_many+ and +belongs_to+
142
+ == +has_many+ and +belongs_to+
143
143
 
144
- ==== Example
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
- ==== Restrictions
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
- ==== Implementation
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
- We now have basic support for local secondary indices. Up to five attributes can
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 :username and
236
- the range key :expires_at. The +local_secondary_index+ declaration makes it possible
237
- to access items using the same hash key :username but with :token as an alternate
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
- We will add support for specifying that a secondary index is to be used in +find_each+
247
- and its derivatives, but for now you will have to specify this explicitly in the
248
- options passed to +in_batches+, +query+, and +scan+.
249
- * +query+: http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Table.html#query-instance_method.
250
- * +scan+: http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Table.html#scan-instance_method
251
- * +in_batches+, +find_each+: lib/ocean-dynamo/queries.rb
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. Delete the child item.
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)
@@ -52,5 +52,8 @@ module OceanDynamo
52
52
  class_attribute :relations, instance_writer: false
53
53
  self.relations = nil
54
54
 
55
+ class_attribute :global_secondary_indexes, instance_writer: false
56
+ self.global_secondary_indexes = nil
57
+
55
58
  end
56
59
  end
@@ -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 once.
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
- def find_each(limit: nil, batch_size: 1000, consistent: false)
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
@@ -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:
@@ -94,7 +94,7 @@ module OceanDynamo
94
94
  end
95
95
 
96
96
 
97
- def create_table
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: [{ attribute_name: table_hash_key.to_s, key_type: "HASH" },
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 table_attribute_definitions
152
- attrs = []
153
- attrs << { attribute_name: table_hash_key.to_s, attribute_type: attribute_type(table_hash_key) }
154
- attrs << { attribute_name: table_range_key.to_s, attribute_type: attribute_type(table_range_key) } if table_range_key
155
- local_secondary_indexes.each do |name|
156
- attrs << { attribute_name: name, attribute_type: attribute_type(name) }
157
- end
158
- attrs
159
- end
160
-
161
- def table_key_schema
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
 
@@ -1,3 +1,3 @@
1
1
  module OceanDynamo
2
- VERSION = "1.1.0"
2
+ VERSION = "1.2.0"
3
3
  end
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.1.0
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\nOceanDynamo is a massively scalable Amazon DynamoDB
140
- near drop-in replacement for \nActiveRecord.\n\nAs one important use case for OceanDynamo
141
- is to facilitate the conversion of SQL\ndatabases to no-SQL DynamoDB databases,
142
- it is important that the syntax and semantics\nof OceanDynamo are as close as possible
143
- to those of ActiveRecord. This includes\ncallbacks, exceptions and method chaining
144
- semantics. OceanDynamo follows this pattern \nclosely and is of course based on
145
- ActiveModel.\n\nThe attribute and persistence layer of OceanDynamo is modeled on
146
- that of ActiveRecord:\n+save+, +save!+, +create+, +update+, +update!+, +update_attributes+,
147
- +find_each+,\n+destroy_all+, +delete_all+ and all the other methods you're used
148
- to are available. \nThe design goal is always to implement as much of the ActiveRecord
149
- interface as possible, \nwithout compromising scalability. This makes the task of
150
- switching from SQL to no-SQL \nmuch easier.\n\nThanks to its structural similarity
151
- to ActiveRecord, OceanDynamo works with FactoryGirl.\n\nOceanDynamo uses primary
152
- indices to retrieve related table items, meaning it scales without \nlimits.\n\nSee
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: