ocean-dynamo 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 339c58baa4132d2d0b311087fbad549938a548b0
4
+ data.tar.gz: 070fcef8bb7d01c0b7bb1f1151389cae94388b51
5
+ SHA512:
6
+ metadata.gz: 421fae8d3ff273f4218fa6f4607a6d0538a36ab33490128d716ec8eb1e243333e9590f07a981e426eec8680af4860b2cafc12904b54c0117510c3f0ba4c9a4a8
7
+ data.tar.gz: 0c7062fab4972a0008cc8e71078d407f4945fcf428a0f0bec36a268ca77ef94618db4abf8bd6bf6bcbf08049b45b60cc1c8fcaf7837bed43eaacdac83e88ae85
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 Peter Bengtson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,103 @@
1
+ == ocean-dynamo
2
+
3
+ This is the OceanDynamo ruby gem, implementing a highly scalable Amazon DynamoDB
4
+ near drop-in replacement for ActiveRecord.
5
+
6
+ OceanDynamo requires Ruby 2.0 and Ruby on Rails 4.0.0 or later.
7
+
8
+ {<img src="https://badge.fury.io/rb/ocean-dynamo.png" alt="Gem Version" />}[http://badge.fury.io/rb/ocean-dynamo]
9
+
10
+
11
+ ==== Features
12
+
13
+ OceanDynamo will use secondary indices to retrieve related table items,
14
+ which means it will scale without limits. (NB: this is a pre-release which as yet doesn't
15
+ implement relations, but they are underway.)
16
+
17
+ As one important use case for OceanDynamo is to facilitate the conversion of SQL based
18
+ ActiveRecord models to DynamoDB based models, it is important that the syntax and semantics
19
+ of OceanDynamo's operations are as close as possible to those of ActiveRecord. This means
20
+ that all callbacks should be available, before, around and after, and that they should be
21
+ called in the same order as in ActiveRecord. OceanDynamo follows this pattern closely and
22
+ is of course based on ActiveModel.
23
+
24
+ The attribute and persistence layer of OceanDynamo is modeled on that of ActiveRecord:
25
+ there's +save+, +save!+, +create+, +update+, +update!+, +update_attribute+ and all the other
26
+ methods you're used to. The design goal is always to implement as much as possible of the
27
+ ActiveRecord interface, without sacrificing scalability. This makes the task of switching from
28
+ SQL to no-SQL much easier.
29
+
30
+
31
+ === Documentation
32
+
33
+ * Ocean-dynamo gem API: http://rdoc.info/github/OceanDev/ocean-dynamo/frames
34
+ * Ocean-dynamo gem on Rubygems: https://rubygems.org/gems/ocean-dynamo
35
+ * Ocean-dynamo source and wiki: https://github.org/OceanDev/ocean-dynamo
36
+
37
+ See also Ocean, a Rails framework for creating highly scalable SOAs in the cloud, in which
38
+ OceanDynamo is used as a central component:
39
+ * http://wiki.oceanframework.net
40
+
41
+
42
+ === Running the specs
43
+
44
+ To run the specs for the OceanDynamo gem, you must first install the bundle. It will download
45
+ a gem called +fake_dynamo+, which runs a local, in-memory functional clone of Amazon DynamoDB.
46
+ We use +fake_dynamo+ during development and testing.
47
+
48
+ First of all, copy the AWS configuration file from the template:
49
+
50
+ cp spec/dummy/config/aws.yml.example spec/dummy/config/aws.yml
51
+
52
+ NB: +aws.yml+ is excluded from source control. This allows you to enter your AWS credentials
53
+ safely. Note that +aws.yml.example+ is under source control: don't edit it.
54
+
55
+ Make sure your have version 0.1.3 of the +fake_dynamo+ gem. It implements the +2011-12-05+ version
56
+ of the DynamoDB API. We're not yet using the +2012-08-10+ version, as the +aws-sdk+ ruby gem
57
+ doesn't fully support it. We'll make the change as soon as +aws-sdk+ is updated. Reportedly,
58
+ it's in the works.
59
+
60
+ Next, start +fake_dynamo+:
61
+
62
+ fake_dynamo --port 4567
63
+
64
+ If this returns errors, make sure that <tt>/usr/local/var/fake_dynamo</tt> exists and
65
+ is writable:
66
+
67
+ sudo mkdir -p /usr/local/var/fake_dynamo
68
+ sudo chown peterb:staff /usr/local/var/fake_dynamo
69
+
70
+ When +fake_dynamo+ runs normally, open another window and issue the following command:
71
+
72
+ curl -X DELETE http://localhost:4567
73
+
74
+ This will reset the +fake_dynamo+ database. It's not a required operation when starting
75
+ +fake_dynamo+; we're just using it here as a test that the installation works. It will
76
+ be issued automatically as part of the test suite, so don't expect test data to survive
77
+ between runs.
78
+
79
+ With +fake_dynamo+ running, you should now be able to do
80
+
81
+ rspec
82
+
83
+ All tests should pass.
84
+
85
+
86
+ === Rails console
87
+
88
+ The Rails console is available from the built-in dummy application:
89
+
90
+ cd spec/dummy
91
+ rails console
92
+
93
+ You may need to initialise the table connection (for each table):
94
+
95
+ CloudModel.establish_db_connection
96
+
97
+ This will, amongst other things, also create the CloudModel table if it doesn't already
98
+ exist. On Amazon, this will take a little while. With +fake_dynamo+, it's practically
99
+ instant.
100
+
101
+ When you leave the console, you must navigate back to the top directory (<tt>cd ../..</tt>)
102
+ in order to be able to run RSpec again.
103
+
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Ocean'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+
22
+ Bundler::GemHelper.install_tasks
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'lib'
28
+ t.libs << 'test'
29
+ t.pattern = 'test/**/*_test.rb'
30
+ t.verbose = false
31
+ end
32
+
33
+
34
+ task default: :test
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ Rails.application.routes.draw do
2
+
3
+ end
@@ -0,0 +1,9 @@
1
+ require "ocean-dynamo/engine"
2
+
3
+ require "ocean-dynamo/dynamo"
4
+
5
+
6
+ module OceanDynamo
7
+
8
+
9
+ end
@@ -0,0 +1,538 @@
1
+
2
+ require "aws-sdk"
3
+
4
+ module OceanDynamo
5
+
6
+ DEFAULT_FIELDS = [
7
+ [:created_at, :datetime],
8
+ [:updated_at, :datetime],
9
+ [:lock_version, :integer, default: 0]
10
+ ]
11
+
12
+ class DynamoError < StandardError; end
13
+
14
+ class NoPrimaryKeyDeclared < DynamoError; end
15
+ class UnknownTableStatus < DynamoError; end
16
+ class UnsupportedType < DynamoError; end
17
+ class RecordInvalid < DynamoError; end
18
+ class RecordNotSaved < DynamoError; end
19
+ class RecordNotFound < DynamoError; end
20
+ class RecordInConflict < DynamoError; end
21
+
22
+
23
+ class Base
24
+
25
+ include ActiveModel::Model
26
+ include ActiveModel::Validations::Callbacks
27
+
28
+
29
+ # ---------------------------------------------------------
30
+ #
31
+ # Class variables and methods
32
+ #
33
+ # ---------------------------------------------------------
34
+
35
+ class_attribute :dynamo_client, instance_writer: false
36
+ class_attribute :dynamo_table, instance_writer: false
37
+ class_attribute :dynamo_items, instance_writer: false
38
+
39
+ class_attribute :table_name, instance_writer: false
40
+ class_attribute :table_name_prefix, instance_writer: false
41
+ class_attribute :table_name_suffix, instance_writer: false
42
+
43
+ class_attribute :table_hash_key, instance_writer: false
44
+ class_attribute :table_range_key, instance_writer: false
45
+
46
+ class_attribute :table_read_capacity_units, instance_writer: false
47
+ class_attribute :table_write_capacity_units, instance_writer: false
48
+
49
+ class_attribute :fields, instance_writer: false
50
+
51
+
52
+ def self.set_table_name(name)
53
+ self.table_name = name
54
+ end
55
+
56
+ def self.set_table_name_prefix(prefix)
57
+ self.table_name_prefix = prefix
58
+ end
59
+
60
+ def self.set_table_name_suffix(suffix)
61
+ self.table_name_suffix = suffix
62
+ end
63
+
64
+
65
+ def self.compute_table_name
66
+ name.pluralize.underscore
67
+ end
68
+
69
+
70
+ def self.table_full_name
71
+ "#{table_name_prefix}#{table_name}#{table_name_suffix}"
72
+ end
73
+
74
+
75
+ def self.primary_key(hash_key, range_key=nil)
76
+ self.table_hash_key = hash_key
77
+ self.table_range_key = range_key
78
+ # Find a better place to do the following initialisation:
79
+ set_table_name compute_table_name unless self.table_name
80
+ nil
81
+ end
82
+
83
+
84
+ def self.read_capacity_units(units)
85
+ self.table_read_capacity_units = units
86
+ end
87
+
88
+
89
+ def self.write_capacity_units(units)
90
+ self.table_write_capacity_units = units
91
+ end
92
+
93
+
94
+ def self.field(name, type=:string, **pairs)
95
+ attr_accessor name
96
+ fields[name] = {type: type,
97
+ default: pairs[:default]}
98
+ end
99
+
100
+
101
+ def self.establish_db_connection
102
+ setup_dynamo
103
+ if dynamo_table.exists?
104
+ wait_until_table_is_active
105
+ else
106
+ create_table
107
+ end
108
+ set_dynamo_table_keys
109
+ end
110
+
111
+
112
+ def self.setup_dynamo
113
+ #self.dynamo_client = AWS::DynamoDB::Client.new(:api_version => '2012-08-10')
114
+ self.dynamo_client ||= AWS::DynamoDB.new
115
+ self.dynamo_table = dynamo_client.tables[table_full_name]
116
+ self.dynamo_items = dynamo_table.items
117
+ end
118
+
119
+
120
+ def self.wait_until_table_is_active
121
+ loop do
122
+ case dynamo_table.status
123
+ when :active
124
+ set_dynamo_table_keys
125
+ return
126
+ when :updating, :creating
127
+ sleep 1
128
+ next
129
+ when :deleting
130
+ sleep 1 while dynamo_table.exists?
131
+ create_table
132
+ return
133
+ else
134
+ raise UnknownTableStatus.new("Unknown DynamoDB table status '#{dynamo_table.status}'")
135
+ end
136
+ sleep 1
137
+ end
138
+ end
139
+
140
+
141
+ def self.set_dynamo_table_keys
142
+ dynamo_table.hash_key = [table_hash_key, fields[table_hash_key][:type]]
143
+ if table_range_key
144
+ dynamo_table.range_key = [table_range_key, fields[table_range_key][:type]]
145
+ end
146
+ end
147
+
148
+
149
+ def self.create_table
150
+ self.dynamo_table = dynamo_client.tables.create(table_full_name,
151
+ table_read_capacity_units, table_write_capacity_units,
152
+ hash_key: { table_hash_key => fields[table_hash_key][:type]},
153
+ range_key: table_range_key && { table_range_key => fields[table_range_key][:type]}
154
+ )
155
+ sleep 1 until dynamo_table.status == :active
156
+ setup_dynamo
157
+ true
158
+ end
159
+
160
+ def self.delete_table
161
+ return false unless dynamo_table.exists? && dynamo_table.status == :active
162
+ dynamo_table.delete
163
+ true
164
+ end
165
+
166
+
167
+ def self.create(attributes = nil, &block)
168
+ object = new(attributes)
169
+ yield(object) if block_given?
170
+ object.save
171
+ object
172
+ end
173
+
174
+
175
+ def self.create!(attributes = nil, &block)
176
+ object = new(attributes)
177
+ yield(object) if block_given?
178
+ object.save!
179
+ object
180
+ end
181
+
182
+
183
+ def self.find(hash, range=nil, consistent: false)
184
+ item = dynamo_items[hash, range]
185
+ raise RecordNotFound unless item.exists?
186
+ new.send(:post_instantiate, item, consistent)
187
+ end
188
+
189
+
190
+ def self.delete(hash, range=nil)
191
+ item = dynamo_items[hash, range]
192
+ return false unless item.exists?
193
+ item.delete
194
+ true
195
+ end
196
+
197
+
198
+ def self.count
199
+ dynamo_table.item_count || -1 # The || -1 is for fake_dynamo specs.
200
+ end
201
+
202
+
203
+ # ---------------------------------------------------------
204
+ #
205
+ # Callbacks
206
+ #
207
+ # ---------------------------------------------------------
208
+
209
+ define_model_callbacks :initialize, only: :after
210
+ define_model_callbacks :save
211
+ define_model_callbacks :create
212
+ define_model_callbacks :update
213
+ define_model_callbacks :destroy
214
+ define_model_callbacks :commit, only: :after
215
+ define_model_callbacks :touch
216
+
217
+
218
+ # ---------------------------------------------------------
219
+ #
220
+ # Class initialisation, done once at load time
221
+ #
222
+ # ---------------------------------------------------------
223
+
224
+ self.table_read_capacity_units = 10
225
+ self.table_write_capacity_units = 5
226
+
227
+ self.fields = HashWithIndifferentAccess.new
228
+ DEFAULT_FIELDS.each { |k, name, **pairs| Base.field k, name, **pairs }
229
+
230
+
231
+ # ---------------------------------------------------------
232
+ #
233
+ # Instance variables and methods
234
+ #
235
+ # ---------------------------------------------------------
236
+
237
+ attr_reader :attributes
238
+ attr_reader :destroyed
239
+ attr_reader :new_record
240
+ attr_reader :dynamo_item
241
+
242
+
243
+ def initialize(attributes={})
244
+ run_callbacks :initialize do
245
+ @attributes = HashWithIndifferentAccess.new
246
+ fields.each do |name, v|
247
+ write_attribute(name, evaluate_default(v[:default])) unless read_attribute(name)
248
+ self.class.class_eval "def #{name}; read_attribute('#{name}'); end"
249
+ self.class.class_eval "def #{name}=(value); write_attribute('#{name}', value); end"
250
+ if fields[name][:type] == :boolean
251
+ self.class.class_eval "def #{name}?; read_attribute('#{name}'); end"
252
+ end
253
+ end
254
+ super
255
+ @dynamo_item = nil
256
+ @destroyed = false
257
+ @new_record = true
258
+ raise NoPrimaryKeyDeclared unless table_hash_key
259
+ end
260
+ end
261
+
262
+
263
+ def read_attribute(name)
264
+ @attributes[name]
265
+ end
266
+
267
+
268
+ def write_attribute(name, value)
269
+ @attributes[name] = value
270
+ end
271
+
272
+
273
+ def [](attribute)
274
+ read_attribute attribute
275
+ end
276
+
277
+
278
+ def []=(attribute, value)
279
+ write_attribute attribute, value
280
+ end
281
+
282
+
283
+ def id
284
+ read_attribute(table_hash_key)
285
+ end
286
+
287
+
288
+ def id=(value)
289
+ write_attribute(table_hash_key, value)
290
+ end
291
+
292
+
293
+ def to_key
294
+ return nil unless persisted?
295
+ key = respond_to?(:id) && id
296
+ key ? [key] : nil
297
+ end
298
+
299
+
300
+
301
+ def assign_attributes(values)
302
+ values.each do |k, v|
303
+ send("#{k}=", v)
304
+ end
305
+ end
306
+
307
+
308
+ def serialized_attributes
309
+ result = {}
310
+ fields.each do |attribute, metadata|
311
+ serialized = serialize_attribute(attribute, read_attribute(attribute), metadata)
312
+ result[attribute] = serialized unless serialized == nil
313
+ end
314
+ result
315
+ end
316
+
317
+
318
+ def serialize_attribute(attribute, value,
319
+ metadata=fields[attribute],
320
+ type: metadata[:type],
321
+ default: metadata[:default])
322
+ return nil if value == nil
323
+ case type
324
+ when :string
325
+ value == "" ? nil : value
326
+ when :integer
327
+ value
328
+ when :float
329
+ value
330
+ when :boolean
331
+ value ? "true" : "false"
332
+ when :datetime
333
+ value.to_i
334
+ when :serialized
335
+ value.to_json
336
+ else
337
+ raise UnsupportedType.new(type.to_s)
338
+ end
339
+ end
340
+
341
+
342
+ def deserialized_attributes(consistent_read: false, hash: nil)
343
+ hash ||= dynamo_item.attributes.to_hash(consistent_read: consistent_read)
344
+ result = {}
345
+ fields.each do |attribute, metadata|
346
+ result[attribute] = deserialize_attribute(hash[attribute], metadata)
347
+ end
348
+ result
349
+ end
350
+
351
+
352
+ def deserialize_attribute(value, metadata,
353
+ type: metadata[:type],
354
+ default: metadata[:default])
355
+ if value == nil && default != nil && type != :string
356
+ return evaluate_default(default)
357
+ end
358
+ case type
359
+ when :string
360
+ return "" if value == nil
361
+ value
362
+ when :integer
363
+ return nil if value == nil
364
+ value.is_a?(Array) ? value.collect(&:to_i) : value.to_i
365
+ when :float
366
+ return nil if value == nil
367
+ value.is_a?(Array) ? value.collect(&:to_f) : value.to_f
368
+ when :boolean
369
+ case value
370
+ when "true"
371
+ true
372
+ when "false"
373
+ false
374
+ else
375
+ nil
376
+ end
377
+ when :datetime
378
+ return nil if value == nil
379
+ Time.at(value.to_i)
380
+ when :serialized
381
+ return nil if value == nil
382
+ JSON.parse(value)
383
+ else
384
+ raise UnsupportedType.new(type.to_s)
385
+ end
386
+ end
387
+
388
+
389
+ def destroyed?
390
+ @destroyed
391
+ end
392
+
393
+
394
+ def new_record?
395
+ @new_record
396
+ end
397
+
398
+
399
+ def persisted?
400
+ !(new_record? || destroyed?)
401
+ end
402
+
403
+
404
+ def save
405
+ begin
406
+ create_or_update
407
+ rescue RecordInvalid
408
+ false
409
+ end
410
+ end
411
+
412
+
413
+ def save!(*)
414
+ create_or_update || raise(RecordNotSaved)
415
+ end
416
+
417
+
418
+ def update_attributes(attributes={})
419
+ assign_attributes(attributes)
420
+ save
421
+ end
422
+
423
+
424
+ def update_attributes!(attributes={})
425
+ assign_attributes(attributes)
426
+ save!
427
+ end
428
+
429
+
430
+ def create_or_update
431
+ result = new_record? ? create : update
432
+ result != false
433
+ end
434
+
435
+
436
+ def create
437
+ return false unless valid?(:create)
438
+ run_callbacks :commit do
439
+ run_callbacks :save do
440
+ run_callbacks :create do
441
+ write_attribute(table_hash_key, SecureRandom.uuid) if read_attribute(table_hash_key) == nil
442
+ t = Time.now
443
+ self.created_at ||= t
444
+ self.updated_at ||= t
445
+ dynamo_persist
446
+ true
447
+ end
448
+ end
449
+ end
450
+ end
451
+
452
+
453
+ def update
454
+ return false unless valid?(:update)
455
+ run_callbacks :commit do
456
+ run_callbacks :save do
457
+ run_callbacks :update do
458
+ self.updated_at = Time.now
459
+ dynamo_persist
460
+ true
461
+ end
462
+ end
463
+ end
464
+ end
465
+
466
+ def destroy
467
+ run_callbacks :commit do
468
+ run_callbacks :destroy do
469
+ delete
470
+ end
471
+ end
472
+ end
473
+
474
+
475
+ def delete
476
+ if persisted?
477
+ @dynamo_item.delete
478
+ end
479
+ @destroyed = true
480
+ freeze
481
+ end
482
+
483
+
484
+ def reload(**keywords)
485
+ range_key = table_range_key && attributes[table_range_key]
486
+ new_instance = self.class.find(id, range_key, **keywords)
487
+ assign_attributes(new_instance.attributes)
488
+ self
489
+ end
490
+
491
+
492
+ def touch(name=nil)
493
+ run_callbacks :touch do
494
+ attrs = [:updated_at]
495
+ attrs << name if name
496
+ t = Time.now
497
+ attrs.each { |k| write_attribute name, t }
498
+ # TODO: handle lock_version
499
+ dynamo_item.attributes.update do |u|
500
+ attrs.each do |k|
501
+ u.set(k => serialize_attribute(k, t))
502
+ end
503
+ end
504
+ self
505
+ end
506
+ end
507
+
508
+
509
+
510
+ protected
511
+
512
+ def evaluate_default(v)
513
+ return v.call if v.is_a?(Proc)
514
+ return v.clone if v.is_a?(Array) || v.is_a?(String) # Instances need their own copy
515
+ v
516
+ end
517
+
518
+
519
+ def dynamo_persist
520
+ @dynamo_item = dynamo_items.put(serialized_attributes)
521
+ @new_record = false
522
+ end
523
+
524
+
525
+ def post_instantiate(item, consistent)
526
+ @dynamo_item = item
527
+ @new_record = false
528
+ assign_attributes(deserialized_attributes(
529
+ hash: nil,
530
+ consistent_read: consistent)
531
+ )
532
+ self
533
+ end
534
+
535
+ end # Base
536
+
537
+ end # Dynamo
538
+
@@ -0,0 +1,6 @@
1
+ module OceanDynamo
2
+ class Engine < ::Rails::Engine
3
+ config.generators.integration_tool :rspec
4
+ config.generators.test_framework :rspec
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ module OceanDynamo
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ocean-dynamo
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Peter Bengtson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-09-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '4.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '4.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sqlite3
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: factory_girl_rails
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: '4.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '4.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: fake_dynamo
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ version: 0.1.3
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: 0.1.3
111
+ description: "== ocean-dynamo\n\nThis is the OceanDynamo ruby gem, implementing a
112
+ highly scalable Amazon DynamoDB near drop-in \nreplacement for ActiveRecord.\n\nOceanDynamo
113
+ will use secondary indices to retrieve related table items, \nwhich means it will
114
+ scale without limits. (NB: this is a pre-release which as yet doesn't\nimplement
115
+ relations, but they are underway.)\n\nAs one important use case for OceanDynamo
116
+ is to facilitate the conversion of SQL based\nActiveRecord models to DynamoDB based
117
+ models, it is important that the syntax and semantics\nof OceanDynamo's operations
118
+ are as close as possible to those of ActiveRecord. This means\nthat all callbacks
119
+ should be available, before, around and after, and that they should be\ncalled in
120
+ the same order as in ActiveRecord. Ocean-dynamo follows this pattern closely and\nis
121
+ of course based on ActiveModel.\n\nThe attribute and persistence layer of OceanDynamo
122
+ is modeled on that of ActiveRecord:\nthere's +save+, +save!+, +create+, +update+,
123
+ +update!+, +update_attribute+ and all the other\nmethods you're used to. The design
124
+ goal is always to implement as much as possible of the\nActiveRecord interface,
125
+ without sacrificing scalability. This makes the task of switching from\nSQL to no-SQL
126
+ much easier.\n\nSee also Ocean, a Rails framework for creating highly scalable SOAs
127
+ in the cloud, in which\nocean-dynamo is used as a central component: http://wiki.oceanframework.net"
128
+ email:
129
+ - peter@peterbengtson.com
130
+ executables: []
131
+ extensions: []
132
+ extra_rdoc_files: []
133
+ files:
134
+ - config/routes.rb
135
+ - lib/ocean-dynamo/dynamo.rb
136
+ - lib/ocean-dynamo/engine.rb
137
+ - lib/ocean-dynamo/version.rb
138
+ - lib/ocean-dynamo.rb
139
+ - MIT-LICENSE
140
+ - Rakefile
141
+ - README.rdoc
142
+ homepage: https://github.com/OceanDev/ocean-dynamo
143
+ licenses:
144
+ - MIT
145
+ metadata: {}
146
+ post_install_message:
147
+ rdoc_options: []
148
+ require_paths:
149
+ - lib
150
+ required_ruby_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - '>='
153
+ - !ruby/object:Gem::Version
154
+ version: 2.0.0
155
+ required_rubygems_version: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - '>='
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ requirements: []
161
+ rubyforge_project:
162
+ rubygems_version: 2.0.7
163
+ signing_key:
164
+ specification_version: 4
165
+ summary: This gem implements common Ocean behaviour for Ruby and Ruby on Rails.
166
+ test_files: []