lotus-dynamodb 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: 8b6e1c051b76f678f3694e0e0cb8f70871a3dbce
4
+ data.tar.gz: d7e164efdaa51814a36a7c803c959ad9c21c3b71
5
+ SHA512:
6
+ metadata.gz: 5a326db3a7714f0573be6b4ebadd1dc2ad5dc7fea5303be5c61c09bcfdd37435084215ff60837156f9c4481c044f24af96b8b65d504a9b4e25a3f8f5f6069771
7
+ data.tar.gz: d1a6068ecc5898cf70ce48ed8b751498ff405fbca62f0a60990f36197d40fe4489e21c0011fff4bd04f215f7642c20763219b624966acaf2578f62be566c308a
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .greenbar
19
+ .ruby-gemset
20
+ .ruby-version
21
+ .fake_dynamo.*
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ before_script: 'bundle exec fake_dynamo -d .fake_dynamo.db -P .fake_dynamo.pid -D'
3
+ script: 'bundle exec rake test:coverage'
4
+ after_script: 'kill `cat .fake_dynamo.pid`'
5
+ rvm:
6
+ - 2.0.0
7
+ - 2.1.0
8
+ - 2.1.1
9
+ - 2.1.2
data/.yardopts ADDED
@@ -0,0 +1,5 @@
1
+ --protected
2
+ --private
3
+ -
4
+ LICENSE.md
5
+ lib/**/*.rb
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ if !ENV['TRAVIS']
5
+ gem 'byebug', require: false, platforms: :ruby if RUBY_VERSION == '2.1.2'
6
+ gem 'yard', require: false
7
+ end
8
+
9
+ gem 'simplecov', require: false
10
+ gem 'coveralls', require: false
11
+
12
+ # Benchmarking
13
+ gem 'benchmark-ips', '~> 1.2'
14
+
15
+ # Fixes are not merged yet
16
+ gem 'fake_dynamo', github: 'krasnoukhov/fake_dynamo'
data/LICENSE.md ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Dmitry Krasnoukhov
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Procfile ADDED
@@ -0,0 +1 @@
1
+ fake_dynamo: bundle exec fake_dynamo -l debug -d .fake_dynamo.db
data/README.md ADDED
@@ -0,0 +1,112 @@
1
+ # Lotus::Model DynamoDB Adapter
2
+
3
+ > An adapter is a concrete implementation of persistence logic for a specific
4
+ > database.
5
+ >
6
+ > -- <cite>[jodosha](https://github.com/jodosha), [Lotus::Model](https://github.com/lotus/model)</cite>
7
+
8
+ This adapter implements persistence layer for a [Amazon DynamoDB](https://aws.amazon.com/dynamodb/),
9
+ and it pretends to be a _really_ sane solution to fully experience DynamoDB advantages with Ruby.
10
+
11
+ It is built using ```AWS::DynamoDB::Client```, which is a part of ```aws-sdk``` gem and implements latest version of DynamoDB protocol.
12
+
13
+ ## Status
14
+
15
+ [![Gem Version](https://badge.fury.io/rb/lotus-dynamodb.svg)](http://badge.fury.io/rb/lotus-dynamodb)
16
+ [![Build Status](https://secure.travis-ci.org/krasnoukhov/lotus-dynamodb.svg?branch=master)](http://travis-ci.org/krasnoukhov/lotus-dynamodb?branch=master)
17
+ [![Coverage Status](https://img.shields.io/coveralls/krasnoukhov/lotus-dynamodb.svg)](https://coveralls.io/r/krasnoukhov/lotus-dynamodb?branch=master)
18
+ [![Code Climate](https://img.shields.io/codeclimate/github/krasnoukhov/lotus-dynamodb.svg)](https://codeclimate.com/github/krasnoukhov/lotus-dynamodb)
19
+ [![Inline docs](http://inch-pages.github.io/github/krasnoukhov/lotus-dynamodb.svg)](http://inch-pages.github.io/github/krasnoukhov/lotus-dynamodb)
20
+ [![Dependencies](https://gemnasium.com/krasnoukhov/lotus-dynamodb.svg)](https://gemnasium.com/krasnoukhov/lotus-dynamodb)
21
+
22
+ ## Links
23
+
24
+ * API Doc: [http://rdoc.info/github/krasnoukhov/lotus-dynamodb](http://rdoc.info/github/krasnoukhov/lotus-dynamodb)
25
+ * Bugs/Issues: [https://github.com/krasnoukhov/lotus-dynamodb/issues](https://github.com/krasnoukhov/lotus-dynamodb/issues)
26
+
27
+ ## Installation
28
+
29
+ Add this line to your application's Gemfile:
30
+
31
+ gem 'lotus-dynamodb'
32
+
33
+ And then execute:
34
+
35
+ $ bundle
36
+
37
+ Or install it yourself as:
38
+
39
+ $ gem install lotus-dynamodb
40
+
41
+ ## Usage
42
+
43
+ Please refer to [Lotus::Model](https://github.com/lotus/model#usage) docs for any details related to Entity, Repository, Data Mapper and Adapter.
44
+
45
+ ### Data types
46
+
47
+ This adapter supports coercion to all DynamoDB types, including blobs and sets.
48
+
49
+ List of Ruby types that are supported:
50
+
51
+ * AWS::DynamoDB::Binary – ```B```
52
+ * Array – ```S``` (via MultiJson)
53
+ * Boolean – ```N``` (1 for true and 0 for false)
54
+ * Date – ```N``` (Integer, seconds since Epoch)
55
+ * DateTime – ```N``` (Float, seconds since Epoch)
56
+ * Float – ```N```
57
+ * Hash – ```S``` (via MultiJson)
58
+ * Integer – ```N```
59
+ * Set – ```SS```, ```NS```, ```BS``` (Set of String, Number or AWS::DynamoDB::Binary)
60
+ * String – ```S```
61
+ * Time – ```N``` (Float, seconds since Epoch)
62
+
63
+ ### Repository methods
64
+
65
+ See [complete list](https://github.com/lotus/model#repositories) of Repository methods provided by ```Lotus::Model```.
66
+
67
+ Following methods are not supported since it's incompatible with DynamoDB:
68
+
69
+ * first
70
+ * last
71
+
72
+ ### Query methods
73
+
74
+ Generic methods supported by DynamoDB adapter:
75
+
76
+ * [all](http://rdoc.info/github/krasnoukhov/lotus-dynamodb/Lotus/Model/Adapters/Dynamodb/Query#all-instance_method)
77
+ * [where](http://rdoc.info/github/krasnoukhov/lotus-dynamodb/Lotus/Model/Adapters/Dynamodb/Query#where-instance_method) (aliases: ```eq```, ```in```, ```between```)
78
+ * [or](http://rdoc.info/github/krasnoukhov/lotus-dynamodb/Lotus/Model/Adapters/Dynamodb/Query#or-instance_method)
79
+ * [exclude](http://rdoc.info/github/krasnoukhov/lotus-dynamodb/Lotus/Model/Adapters/Dynamodb/Query#exclude-instance_method) (aliases: ```not```, ```ne```)
80
+ * [select](http://rdoc.info/github/krasnoukhov/lotus-dynamodb/Lotus/Model/Adapters/Dynamodb/Query#select-instance_method)
81
+ * [order](http://rdoc.info/github/krasnoukhov/lotus-dynamodb/Lotus/Model/Adapters/Dynamodb/Query#order-instance_method) (alias: ```asc```)
82
+ * [desc](http://rdoc.info/github/krasnoukhov/lotus-dynamodb/Lotus/Model/Adapters/Dynamodb/Query#desc-instance_method)
83
+ * [limit](http://rdoc.info/github/krasnoukhov/lotus-dynamodb/Lotus/Model/Adapters/Dynamodb/Query#limit-instance_method)
84
+ * [exists?](http://rdoc.info/github/krasnoukhov/lotus-dynamodb/Lotus/Model/Adapters/Dynamodb/Query#exist%3F-instance_method) (alias: ```exist?```)
85
+ * [count](http://rdoc.info/github/krasnoukhov/lotus-dynamodb/Lotus/Model/Adapters/Dynamodb/Query#count-instance_method)
86
+
87
+ DynamoDB-specific methods:
88
+
89
+ * [query](http://rdoc.info/github/krasnoukhov/lotus-dynamodb/Lotus/Model/Adapters/Dynamodb/Query#query-instance_method) – ensure ```query``` operation is performed instead of ```scan```
90
+ * [consistent](http://rdoc.info/github/krasnoukhov/lotus-dynamodb/Lotus/Model/Adapters/Dynamodb/Query#consistent-instance_method) – require consistent read for query
91
+ * [index](http://rdoc.info/github/krasnoukhov/lotus-dynamodb/Lotus/Model/Adapters/Dynamodb/Query#index-instance_method) – perform query on specific index
92
+ * [le](http://rdoc.info/github/krasnoukhov/lotus-dynamodb/Lotus/Model/Adapters/Dynamodb/Query#le-instance_method)
93
+ * [lt](http://rdoc.info/github/krasnoukhov/lotus-dynamodb/Lotus/Model/Adapters/Dynamodb/Query#lt-instance_method)
94
+ * [ge](http://rdoc.info/github/krasnoukhov/lotus-dynamodb/Lotus/Model/Adapters/Dynamodb/Query#ge-instance_method)
95
+ * [gt](http://rdoc.info/github/krasnoukhov/lotus-dynamodb/Lotus/Model/Adapters/Dynamodb/Query#gt-instance_method)
96
+ * [contains](http://rdoc.info/github/krasnoukhov/lotus-dynamodb/Lotus/Model/Adapters/Dynamodb/Query#contains-instance_method)
97
+ * [not_contains](http://rdoc.info/github/krasnoukhov/lotus-dynamodb/Lotus/Model/Adapters/Dynamodb/Query#not_contains-instance_method)
98
+ * [begins_with](http://rdoc.info/github/krasnoukhov/lotus-dynamodb/Lotus/Model/Adapters/Dynamodb/Query#begins_with-instance_method)
99
+ * [null](http://rdoc.info/github/krasnoukhov/lotus-dynamodb/Lotus/Model/Adapters/Dynamodb/Query#null-instance_method)
100
+ * [not_null](http://rdoc.info/github/krasnoukhov/lotus-dynamodb/Lotus/Model/Adapters/Dynamodb/Query#not_null-instance_method)
101
+
102
+ ### Example
103
+
104
+ Check out the simple example in [examples/purchase.rb](examples/purchase.rb).
105
+
106
+ ## Contributing
107
+
108
+ 1. Fork it
109
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
110
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
111
+ 4. Push to the branch (`git push origin my-new-feature`)
112
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'bundler/gem_tasks'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.pattern = 'test/**/*_test.rb'
7
+ t.libs.push 'test'
8
+ end
9
+
10
+ namespace :test do
11
+ task :coverage do
12
+ ENV['COVERAGE'] = 'true'
13
+ Rake::Task['test'].invoke
14
+ end
15
+ end
16
+
17
+ task default: :test
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #
4
+ # Link to original gist: https://gist.github.com/jodosha/17a8bd1a49899753f617
5
+ #
6
+
7
+ require 'benchmark'
8
+ require 'benchmark/ips'
9
+ require 'lotus/model'
10
+ require 'lotus-dynamodb'
11
+
12
+ GC.disable
13
+ TIMES = (ENV['TIMES'] || 1_000_000).to_i
14
+
15
+ class Project
16
+ include Lotus::Entity
17
+ self.attributes = :attr1, :attr2, :attr3, :attr4,
18
+ :attr5, :attr6, :attr7, :attr8, :attr9
19
+ end
20
+
21
+ default_collection = Lotus::Model::Mapping::Collection.new(:projects, Lotus::Model::Mapping::Coercer) do
22
+ entity Project
23
+
24
+ attribute :id, Integer
25
+ attribute :attr1, String
26
+ attribute :attr2, String
27
+ attribute :attr3, String
28
+ attribute :attr4, String
29
+ attribute :attr5, String
30
+ attribute :attr6, String
31
+ attribute :attr7, String
32
+ attribute :attr8, String
33
+ attribute :attr9, String
34
+ end
35
+
36
+ dynamodb_collection = Lotus::Model::Mapping::Collection.new(:projects, Lotus::Model::Adapters::Dynamodb::Coercer) do
37
+ entity Project
38
+
39
+ attribute :id, Integer
40
+ attribute :attr1, String
41
+ attribute :attr2, String
42
+ attribute :attr3, String
43
+ attribute :attr4, String
44
+ attribute :attr5, String
45
+ attribute :attr6, String
46
+ attribute :attr7, String
47
+ attribute :attr8, String
48
+ attribute :attr9, String
49
+ end
50
+
51
+ record = Hash[id: '23', attr1: 'attr1', attr2: 'attr2',
52
+ attr3: 'attr3', attr4: 'attr4', attr5: 'attr5',
53
+ attr6: 'attr6', attr7: 'attr7', attr8: 'attr8',
54
+ attr9: 'attr9']
55
+
56
+ default_collection.load!
57
+ dynamodb_collection.load!
58
+
59
+ Benchmark.bm(30) do |bm|
60
+ bm.report 'default' do
61
+ TIMES.times do
62
+ default_collection.deserialize([record])
63
+ end
64
+ end
65
+
66
+ bm.report 'dynamodb' do
67
+ TIMES.times do
68
+ dynamodb_collection.deserialize([record])
69
+ end
70
+ end
71
+ end
72
+
73
+ Benchmark.ips do |x|
74
+ x.report('default') { default_collection.deserialize([record]) }
75
+ x.report('dynamodb') { dynamodb_collection.deserialize([record]) }
76
+ end
data/examples/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gem 'lotus-dynamodb'
@@ -0,0 +1,164 @@
1
+ require 'bundler'
2
+ Bundler.require(:default)
3
+
4
+ require 'lotus/model'
5
+ require 'lotus-dynamodb'
6
+
7
+ AWS.config(
8
+ use_ssl: false,
9
+ dynamo_db_endpoint: 'localhost',
10
+ dynamo_db_port: 4567,
11
+ access_key_id: '',
12
+ secret_access_key: '',
13
+ )
14
+
15
+ #
16
+ # Say, we have a ```purchases``` DynamoDB table
17
+ #
18
+ # This table stores purchases which are split by a ```region``` and sorted by
19
+ # creation time.
20
+ # Local secondary index allows sorting records by subtotal, and global index is
21
+ # used to retrieve specific records by ```uuid``` attribute, even if we don't
22
+ # know a ```region``` of these records.
23
+ #
24
+
25
+ DB = AWS::DynamoDB::Client.new(api_version: Lotus::Dynamodb::API_VERSION)
26
+
27
+ begin
28
+ DB.describe_table("purchases")
29
+ rescue AWS::DynamoDB::Errors::ResourceNotFoundException
30
+ DB.create_table(
31
+ table_name: "purchases",
32
+
33
+ # List of all attributes which are used as table and indexes keys
34
+ attribute_definitions: [
35
+ { attribute_name: "region", attribute_type: "S" },
36
+ { attribute_name: "created_at", attribute_type: "N" },
37
+ { attribute_name: "subtotal", attribute_type: "N" },
38
+ { attribute_name: "uuid", attribute_type: "S" },
39
+ ],
40
+
41
+ # Key schema of table
42
+ key_schema: [
43
+ { attribute_name: "region", key_type: "HASH" },
44
+ { attribute_name: "created_at", key_type: "RANGE" },
45
+ ],
46
+
47
+ # List of local indexes
48
+ local_secondary_indexes: [{
49
+ index_name: "by_subtotal",
50
+ key_schema: [
51
+ { attribute_name: "region", key_type: "HASH" },
52
+ { attribute_name: "subtotal", key_type: "RANGE" },
53
+ ],
54
+ projection: {
55
+ projection_type: "ALL",
56
+ },
57
+ }],
58
+
59
+ # List of global indexes
60
+ global_secondary_indexes: [{
61
+ index_name: "by_uuid",
62
+ key_schema: [
63
+ { attribute_name: "uuid", key_type: "HASH" },
64
+ ],
65
+ projection: {
66
+ projection_type: "ALL",
67
+ },
68
+ provisioned_throughput: {
69
+ read_capacity_units: 10,
70
+ write_capacity_units: 10,
71
+ },
72
+ }],
73
+
74
+ # Capacity
75
+ provisioned_throughput: {
76
+ read_capacity_units: 10,
77
+ write_capacity_units: 10,
78
+ },
79
+ )
80
+ end
81
+
82
+ #
83
+ # Entity
84
+ #
85
+
86
+ class Purchase
87
+ include Lotus::Entity
88
+ self.attributes = :id, :region, :subtotal, :item_ids, :content, :created_at
89
+ end
90
+
91
+ #
92
+ # Repository
93
+ #
94
+
95
+ class PurchaseRepository
96
+ include Lotus::Repository
97
+
98
+ class << self
99
+ def find_by_uuid(uuid)
100
+ query do
101
+ index("by_uuid").where(uuid: uuid).limit(1)
102
+ end.all.first
103
+ end
104
+
105
+ def top_by_subtotal(region, limit)
106
+ query do
107
+ index("by_subtotal").where(region: region).desc.limit(limit)
108
+ end.all
109
+ end
110
+ end
111
+ end
112
+
113
+ #
114
+ # Mapper
115
+ #
116
+
117
+ coercer = Lotus::Model::Adapters::Dynamodb::Coercer
118
+ mapper = Lotus::Model::Mapper.new(coercer) do
119
+ collection :purchases do
120
+ entity Purchase
121
+
122
+ attribute :id, String, as: :uuid
123
+ attribute :region, String
124
+ attribute :subtotal, Float
125
+ attribute :item_ids, Set
126
+ attribute :content, AWS::DynamoDB::Binary
127
+ attribute :created_at, Time
128
+
129
+ identity :uuid
130
+ end
131
+ end.load!
132
+
133
+ #
134
+ # Adapter
135
+ #
136
+
137
+ PurchaseRepository.adapter = Lotus::Model::Adapters::DynamodbAdapter.new(mapper)
138
+
139
+ #
140
+ # Create some data
141
+ #
142
+
143
+ purchases = [
144
+ { region: "europe", subtotal: 15.0, item_ids: [1, 2] },
145
+ { region: "europe", subtotal: 10.0, content: "Huge Blob Here" },
146
+ { region: "usa", subtotal: 5.0, item_ids: ["strings", "as", "well"] },
147
+ { region: "asia", subtotal: 100.0 },
148
+ ].map do |purchase|
149
+ PurchaseRepository.create(
150
+ Purchase.new(purchase.merge(created_at: Time.new))
151
+ )
152
+ end
153
+
154
+ #
155
+ # Perform queries
156
+ #
157
+
158
+ puts "Find by UUID"
159
+ puts PurchaseRepository.find_by_uuid(purchases.first.id).inspect
160
+ puts
161
+
162
+ puts "Top by subtotal"
163
+ puts PurchaseRepository.top_by_subtotal("europe", 50).map(&:inspect)
164
+ puts
@@ -0,0 +1,14 @@
1
+ # Lotus namespace
2
+ #
3
+ # @since 0.1.0
4
+ module Lotus
5
+ # Lotus::Dynamodb namespace
6
+ #
7
+ # @since 0.1.0
8
+ module Dynamodb
9
+ # Defines the DynamoDB API version
10
+ #
11
+ # @since 0.1.0
12
+ API_VERSION = "2012-08-10"
13
+ end
14
+ end
@@ -0,0 +1,8 @@
1
+ module Lotus
2
+ module Dynamodb
3
+ # Defines the version
4
+ #
5
+ # @since 0.1.0
6
+ VERSION = '0.1.0'
7
+ end
8
+ end