lotus-dynamodb 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: 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