Dynamoid 0.0.1
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.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +58 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +66 -0
- data/Rakefile +49 -0
- data/VERSION +1 -0
- data/lib/dynamoid.rb +31 -0
- data/lib/dynamoid/adapter.rb +24 -0
- data/lib/dynamoid/adapter/aws_sdk.rb +99 -0
- data/lib/dynamoid/adapter/local.rb +77 -0
- data/lib/dynamoid/attributes.rb +27 -0
- data/lib/dynamoid/components.rb +24 -0
- data/lib/dynamoid/config.rb +19 -0
- data/lib/dynamoid/config/options.rb +74 -0
- data/lib/dynamoid/document.rb +35 -0
- data/lib/dynamoid/errors.rb +8 -0
- data/lib/dynamoid/fields.rb +32 -0
- data/lib/dynamoid/finders.rb +58 -0
- data/lib/dynamoid/indexes.rb +59 -0
- data/lib/dynamoid/persistence.rb +35 -0
- data/lib/dynamoid/relations.rb +21 -0
- data/spec/app/models/address.rb +5 -0
- data/spec/app/models/user.rb +11 -0
- data/spec/dynamoid/adapter/aws_sdk_spec.rb +123 -0
- data/spec/dynamoid/adapter/local_spec.rb +150 -0
- data/spec/dynamoid/adapter_spec.rb +13 -0
- data/spec/dynamoid/attributes_spec.rb +31 -0
- data/spec/dynamoid/document_spec.rb +38 -0
- data/spec/dynamoid/fields_spec.rb +26 -0
- data/spec/dynamoid/finders_spec.rb +110 -0
- data/spec/dynamoid/indexes_spec.rb +54 -0
- data/spec/dynamoid/persistence_spec.rb +55 -0
- data/spec/dynamoid/relations_spec.rb +6 -0
- data/spec/dynamoid_spec.rb +5 -0
- data/spec/spec_helper.rb +52 -0
- metadata +188 -0
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
gem 'activemodel'
|
3
|
+
gem 'tzinfo'
|
4
|
+
gem 'aws-sdk'
|
5
|
+
|
6
|
+
# Add dependencies required to use your gem here.
|
7
|
+
# Example:
|
8
|
+
# gem "activesupport", ">= 2.3.5"
|
9
|
+
|
10
|
+
# Add dependencies to develop your gem here.
|
11
|
+
# Include everything needed to run rake, tests, features, etc.
|
12
|
+
group :development do
|
13
|
+
gem "mocha"
|
14
|
+
gem "rake"
|
15
|
+
gem "rspec"
|
16
|
+
gem "bundler"
|
17
|
+
gem "jeweler"
|
18
|
+
gem "rcov"
|
19
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activemodel (3.1.3)
|
5
|
+
activesupport (= 3.1.3)
|
6
|
+
builder (~> 3.0.0)
|
7
|
+
i18n (~> 0.6)
|
8
|
+
activesupport (3.1.3)
|
9
|
+
multi_json (~> 1.0)
|
10
|
+
aws-sdk (1.3.5)
|
11
|
+
httparty (~> 0.7)
|
12
|
+
json (~> 1.4)
|
13
|
+
nokogiri (>= 1.4.4)
|
14
|
+
uuidtools (~> 2.1)
|
15
|
+
builder (3.0.0)
|
16
|
+
diff-lcs (1.1.3)
|
17
|
+
git (1.2.5)
|
18
|
+
httparty (0.8.1)
|
19
|
+
multi_json
|
20
|
+
multi_xml
|
21
|
+
i18n (0.6.0)
|
22
|
+
jeweler (1.6.4)
|
23
|
+
bundler (~> 1.0)
|
24
|
+
git (>= 1.2.5)
|
25
|
+
rake
|
26
|
+
json (1.6.5)
|
27
|
+
metaclass (0.0.1)
|
28
|
+
mocha (0.10.0)
|
29
|
+
metaclass (~> 0.0.1)
|
30
|
+
multi_json (1.0.4)
|
31
|
+
multi_xml (0.4.1)
|
32
|
+
nokogiri (1.5.0)
|
33
|
+
rake (0.9.2.2)
|
34
|
+
rcov (0.9.11)
|
35
|
+
rspec (2.8.0)
|
36
|
+
rspec-core (~> 2.8.0)
|
37
|
+
rspec-expectations (~> 2.8.0)
|
38
|
+
rspec-mocks (~> 2.8.0)
|
39
|
+
rspec-core (2.8.0)
|
40
|
+
rspec-expectations (2.8.0)
|
41
|
+
diff-lcs (~> 1.1.2)
|
42
|
+
rspec-mocks (2.8.0)
|
43
|
+
tzinfo (0.3.31)
|
44
|
+
uuidtools (2.1.2)
|
45
|
+
|
46
|
+
PLATFORMS
|
47
|
+
ruby
|
48
|
+
|
49
|
+
DEPENDENCIES
|
50
|
+
activemodel
|
51
|
+
aws-sdk
|
52
|
+
bundler
|
53
|
+
jeweler
|
54
|
+
mocha
|
55
|
+
rake
|
56
|
+
rcov
|
57
|
+
rspec
|
58
|
+
tzinfo
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Synthetic LLC
|
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.markdown
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# Dynamoid
|
2
|
+
|
3
|
+
Dynamoid is an ORM for Amazon's DynamoDB for Ruby applications. It provides similar functionality to ActiveRecord and improves on Amazon's existing [HashModel](http://docs.amazonwebservices.com/AWSRubySDK/latest/AWS/Record/HashModel.html) by providing better searching tools, native association support, and a local adapter for offline development.
|
4
|
+
|
5
|
+
## Warning!
|
6
|
+
|
7
|
+
I'm still working on this gem a lot. Associations aren't working and that's a bummer, and you can only use the old-school ActiveRecord style finders like ```find_all_by_<attribute_name>``` or directly finding by an ID.
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
Using Dynamoid is pretty simple. First you need to initialize it to get it going, so put code similar to this somewhere (a Rails initializer would be a great place for this if you're using Rails):
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
Dynamoid.configure do |config|
|
15
|
+
config.adapter = 'local' # This adapter allows offline development without connecting to the DynamoDB servers.
|
16
|
+
# config.adapter = 'aws_sdk' # This adapter establishes a connection to the DynamoDB servers using's Amazon's own awful AWS gem.
|
17
|
+
# config.access_key = 'access_key' # If connecting to DynamoDB, your access key is required.
|
18
|
+
# config.secret_key = 'secret_key' # So is your secret key.
|
19
|
+
config.namespace = "dynamoid_#{Rails.application.class.parent_name}_#{Rails.env}" # To namespace tables created by Dynamoid from other tables you might have.
|
20
|
+
config.warn_on_scan = true # Output a warning to stdout when you perform a scan rather than a query on a table
|
21
|
+
end
|
22
|
+
|
23
|
+
```
|
24
|
+
|
25
|
+
Inside your model:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
class User
|
29
|
+
include Dynamoid::Document
|
30
|
+
|
31
|
+
field :name
|
32
|
+
field :email
|
33
|
+
|
34
|
+
index :name
|
35
|
+
index :email
|
36
|
+
index [:name, :email]
|
37
|
+
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
Right now, you can only do a couple things with this amazing functionality:
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
u = User.new(:name => 'Josh')
|
45
|
+
u.email = 'josh@joshsymonds.com'
|
46
|
+
u.save
|
47
|
+
|
48
|
+
u == User.find(u.id)
|
49
|
+
u == User.find_by_name('Josh')
|
50
|
+
u == User.find_by_name_and_email('Josh','josh@joshsymonds.com')
|
51
|
+
```
|
52
|
+
|
53
|
+
Not super exciting yet, true... but it's getting there!
|
54
|
+
|
55
|
+
## Credits
|
56
|
+
|
57
|
+
Dynamoid borrows code, structure, and even its name very liberally from the truly amazing [Mongoid](https://github.com/mongoid/mongoid). Without Mongoid to crib from none of this would have been possible, and I hope they don't mind me reusing their very awesome ideas to make DynamoDB just as accessible to the Ruby world as MongoDB.
|
58
|
+
|
59
|
+
## Running the tests
|
60
|
+
|
61
|
+
The tests can be run in the simple predictable way with ```rake```. However, if you provide environment variables for ACCESS_KEY and SECRET_KEY, the tests will use the aws_sdk adapter rather than the local adapter: ```ACCESS_KEY=<accesskey> SECRET_KEY=<secretkey> rake```. Keep in mind this takes much, much longer than the local tests.
|
62
|
+
|
63
|
+
## Copyright
|
64
|
+
|
65
|
+
Copyright (c) 2012 Josh Symonds. See LICENSE.txt for further details.
|
66
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "Dynamoid"
|
18
|
+
gem.homepage = "http://github.com/Veraticus/Dynamoid"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = "Dynamoid is an ORM for Amazon's DynamoDB"
|
21
|
+
gem.description = "Dynamoid is an ORM for Amazon's DynamoDB that supports offline development, associations, querying, and everything else you'd expect from an ActiveRecord-style replacement."
|
22
|
+
gem.email = "josh@joshsymonds.com"
|
23
|
+
gem.authors = ["Josh Symonds"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rspec/core'
|
29
|
+
require 'rspec/core/rake_task'
|
30
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
31
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
RSpec::Core::RakeTask.new(:rcov) do |spec|
|
35
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
36
|
+
spec.rcov = true
|
37
|
+
end
|
38
|
+
|
39
|
+
task :default => :spec
|
40
|
+
|
41
|
+
require 'rake/rdoctask'
|
42
|
+
Rake::RDocTask.new do |rdoc|
|
43
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
44
|
+
|
45
|
+
rdoc.rdoc_dir = 'rdoc'
|
46
|
+
rdoc.title = "dynamoid #{version}"
|
47
|
+
rdoc.rdoc_files.include('README*')
|
48
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
49
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/lib/dynamoid.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require "delegate"
|
2
|
+
require "time"
|
3
|
+
require "securerandom"
|
4
|
+
require "active_support/core_ext"
|
5
|
+
require 'active_support/json'
|
6
|
+
require "active_support/inflector"
|
7
|
+
require "active_support/lazy_load_hooks"
|
8
|
+
require "active_support/time_with_zone"
|
9
|
+
require "active_model"
|
10
|
+
|
11
|
+
require 'dynamoid/errors'
|
12
|
+
require 'dynamoid/attributes'
|
13
|
+
require 'dynamoid/fields'
|
14
|
+
require 'dynamoid/indexes'
|
15
|
+
require 'dynamoid/persistence'
|
16
|
+
require 'dynamoid/finders'
|
17
|
+
require 'dynamoid/config'
|
18
|
+
require 'dynamoid/components'
|
19
|
+
require 'dynamoid/document'
|
20
|
+
require 'dynamoid/adapter'
|
21
|
+
|
22
|
+
module Dynamoid
|
23
|
+
extend self
|
24
|
+
|
25
|
+
def configure
|
26
|
+
block_given? ? yield(Config) : Config
|
27
|
+
Dynamoid::Adapter.reconnect!
|
28
|
+
end
|
29
|
+
alias :config :configure
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Dynamoid #:nodoc:
|
3
|
+
|
4
|
+
module Adapter
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def adapter
|
8
|
+
reconnect! unless @adapter
|
9
|
+
@adapter
|
10
|
+
end
|
11
|
+
|
12
|
+
def reconnect!
|
13
|
+
require "dynamoid/adapter/#{Dynamoid::Config.adapter}" unless Dynamoid::Adapter.const_defined?(Dynamoid::Config.adapter.camelcase)
|
14
|
+
@adapter = Dynamoid::Adapter.const_get(Dynamoid::Config.adapter.camelcase)
|
15
|
+
@adapter.connect! if @adapter.respond_to?(:connect!)
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(method, *args)
|
19
|
+
return @adapter.send(method, *args) if @adapter.respond_to?(method)
|
20
|
+
super
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'aws'
|
2
|
+
|
3
|
+
module Dynamoid
|
4
|
+
module Adapter
|
5
|
+
module AwsSdk
|
6
|
+
extend self
|
7
|
+
@@connection = nil
|
8
|
+
|
9
|
+
def connect!
|
10
|
+
@@connection = AWS::DynamoDB.new(:access_key_id => Dynamoid::Config.access_key, :secret_access_key => Dynamoid::Config.secret_key)
|
11
|
+
end
|
12
|
+
|
13
|
+
def connection
|
14
|
+
@@connection
|
15
|
+
end
|
16
|
+
|
17
|
+
# BatchGetItem
|
18
|
+
def batch_get_item(options)
|
19
|
+
batch = AWS::DynamoDB::BatchGet.new(:config => @@connection.config)
|
20
|
+
options.each do |t, ids|
|
21
|
+
batch.table(t, :all, Array(ids))
|
22
|
+
end
|
23
|
+
hash = Hash.new{|h, k| h[k] = []}
|
24
|
+
batch.each do |table_name, attributes|
|
25
|
+
hash[table_name] << attributes.symbolize_keys!
|
26
|
+
end
|
27
|
+
hash
|
28
|
+
end
|
29
|
+
|
30
|
+
# CreateTable
|
31
|
+
def create_table(table_name, key)
|
32
|
+
table = @@connection.tables.create(table_name, 10, 5, :hash_key => {key.to_sym => :string})
|
33
|
+
sleep 0.5 while table.status == :creating
|
34
|
+
return table
|
35
|
+
end
|
36
|
+
|
37
|
+
# DeleteItem
|
38
|
+
def delete_item(table_name, key)
|
39
|
+
table = @@connection.tables[table_name]
|
40
|
+
table.load_schema
|
41
|
+
result = table.items[key]
|
42
|
+
result.delete unless result.attributes.to_h.empty?
|
43
|
+
true
|
44
|
+
end
|
45
|
+
|
46
|
+
# DeleteTable
|
47
|
+
def delete_table(table_name)
|
48
|
+
@@connection.tables[table_name].delete
|
49
|
+
end
|
50
|
+
|
51
|
+
# DescribeTable
|
52
|
+
|
53
|
+
# GetItem
|
54
|
+
def get_item(table_name, key)
|
55
|
+
table = @@connection.tables[table_name]
|
56
|
+
table.load_schema
|
57
|
+
result = table.items[key].attributes.to_h
|
58
|
+
if result.empty?
|
59
|
+
nil
|
60
|
+
else
|
61
|
+
result.symbolize_keys!
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# ListTables
|
66
|
+
def list_tables
|
67
|
+
@@connection.tables.collect(&:name)
|
68
|
+
end
|
69
|
+
|
70
|
+
# PutItem
|
71
|
+
def put_item(table_name, object)
|
72
|
+
table = @@connection.tables[table_name]
|
73
|
+
table.load_schema
|
74
|
+
table.items.create(object.delete_if{|k, v| v.nil?})
|
75
|
+
end
|
76
|
+
|
77
|
+
# Query
|
78
|
+
def query(table_name, id)
|
79
|
+
get_item(table_name, id)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Scan
|
83
|
+
def scan(table_name, scan_hash)
|
84
|
+
table = @@connection.tables[table_name]
|
85
|
+
table.load_schema
|
86
|
+
results = []
|
87
|
+
table.items.select do |data|
|
88
|
+
attributes = data.attributes.symbolize_keys!
|
89
|
+
results << attributes if scan_hash.all?{|k, v| !attributes[k].nil? && attributes[k] == v}
|
90
|
+
end
|
91
|
+
results
|
92
|
+
end
|
93
|
+
|
94
|
+
# UpdateItem
|
95
|
+
|
96
|
+
# UpdateTable
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Dynamoid
|
2
|
+
module Adapter
|
3
|
+
module Local
|
4
|
+
extend self
|
5
|
+
# Gimpy hash that should be somewhat equivalent to what Amazon's actual DynamoDB, for offline development.
|
6
|
+
|
7
|
+
def data
|
8
|
+
@data ||= {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def reset_data
|
12
|
+
self.data.each {|k, v| v[:data] = {}}
|
13
|
+
end
|
14
|
+
|
15
|
+
# BatchGetItem
|
16
|
+
def batch_get_item(options)
|
17
|
+
Hash.new { |h, k| h[k] = Array.new }.tap do |hash|
|
18
|
+
options.each do |table_name, keys|
|
19
|
+
table = data[table_name]
|
20
|
+
Array(keys).each do |key|
|
21
|
+
hash[table_name] << table[:data][key]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# CreateTable
|
28
|
+
def create_table(table_name, key)
|
29
|
+
data[table_name] = {:id => key, :data => {}}
|
30
|
+
end
|
31
|
+
|
32
|
+
# DeleteItem
|
33
|
+
def delete_item(table_name, key)
|
34
|
+
data[table_name][:data].delete(key)
|
35
|
+
end
|
36
|
+
|
37
|
+
# DeleteTable
|
38
|
+
def delete_table(table_name)
|
39
|
+
data.delete(table_name)
|
40
|
+
end
|
41
|
+
|
42
|
+
# DescribeTable
|
43
|
+
|
44
|
+
# GetItem
|
45
|
+
def get_item(table_name, key)
|
46
|
+
data[table_name][:data][key]
|
47
|
+
end
|
48
|
+
|
49
|
+
# ListTables
|
50
|
+
def list_tables
|
51
|
+
data.keys
|
52
|
+
end
|
53
|
+
|
54
|
+
# PutItem
|
55
|
+
def put_item(table_name, object)
|
56
|
+
table = data[table_name]
|
57
|
+
table[:data][object[table[:id]]] = object
|
58
|
+
end
|
59
|
+
|
60
|
+
# Query
|
61
|
+
def query(table_name, id)
|
62
|
+
get_item(table_name, id)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Scan
|
66
|
+
def scan(table_name, scan_hash)
|
67
|
+
return [] if data[table_name].nil?
|
68
|
+
data[table_name][:data].values.select{|d| scan_hash.all?{|k, v| !d[k].nil? && d[k] == v}}
|
69
|
+
end
|
70
|
+
|
71
|
+
# UpdateItem
|
72
|
+
|
73
|
+
# UpdateTable
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|