ocean-dynamo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []