dynamosaurus 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7c83dcc84e2df48c376bcee1bbf2b75888a2ea63
4
+ data.tar.gz: a3481c7cd31a0ccce8beee8217539f0b0d993660
5
+ SHA512:
6
+ metadata.gz: 1156a59ba6601629bd3e773285e4dd5b6e6741633a61364e201b01fae67bb433e95ceba19e69d8c5a471a004d0b9c5742dcf78fc6358d7d87dac60283c7b2d53
7
+ data.tar.gz: 6c6a0ea222c90b658d50a67b160fb6177be10946ad043b71f677661f3be55c2ada79199a87c067d67bb021f6c97e45e79bc7a6270299f7401b5c63116f00e2cb
@@ -0,0 +1,17 @@
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
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in dynamosaurus.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Isamu Arimoto
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.
@@ -0,0 +1,55 @@
1
+ # Dynamosaurus
2
+
3
+ ## Installation
4
+
5
+ Add this line to your application's Gemfile:
6
+
7
+ gem 'dynamosaurus'
8
+
9
+ And then execute:
10
+
11
+ $ bundle
12
+
13
+ Or install it yourself as:
14
+
15
+ $ gem install dynamosaurus
16
+
17
+ ## Usage
18
+
19
+ require "dynamosaurus"
20
+
21
+ class SimpleKVS < Dynamosaurus::DynamoBase
22
+ key :simple_key, :string
23
+ end
24
+
25
+ aws_config = {
26
+ :access_key_id => 'aws_dynamodb_access_key_id',
27
+ :secret_access_key => 'aws_dynamodb_secret_access_key',
28
+ :region => 'us-west-1',
29
+ }
30
+ Aws.config = aws_config
31
+
32
+ # create
33
+ SimpleKVS.put({:simple_key => "key"}, {:num => 1})
34
+
35
+ # read
36
+ kvs = SimpleKVS.get("key")
37
+
38
+ # update
39
+ kvs.update({:test => 1})
40
+
41
+ # update
42
+ kvs.num = 100
43
+ kvs.save
44
+
45
+ # delete
46
+ kvs.delete
47
+
48
+
49
+ ## Contributing
50
+
51
+ 1. Fork it
52
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
53
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
54
+ 4. Push to the branch (`git push origin my-new-feature`)
55
+ 5. Create new Pull Request
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'dynamosaurus/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "dynamosaurus"
8
+ spec.version = Dynamosaurus::VERSION
9
+ spec.authors = ["Isamu Arimoto"]
10
+ spec.email = ["isamu.a@gmail.com"]
11
+ spec.description = %q{Dynamodb simple ORM}
12
+ spec.summary = %q{Dynamodb simple ORM}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "rspec"
24
+
25
+ spec.add_runtime_dependency 'aws-sdk-core', ">= 2.0.20"
26
+
27
+ end
@@ -0,0 +1,163 @@
1
+ require "dynamosaurus/version"
2
+ require "dynamosaurus/dynamo_base"
3
+ require "dynamosaurus/dynamo_class"
4
+
5
+ module Dynamosaurus
6
+ class << self
7
+ attr_accessor :logger
8
+ end
9
+
10
+ class Logger
11
+
12
+ attr_accessor :level
13
+ attr_accessor :delimiter
14
+ attr_accessor :auto_flush
15
+ attr_reader :buffer
16
+ attr_reader :log
17
+ attr_reader :init_args
18
+
19
+ Levels =
20
+ {
21
+ :fatal => 7,
22
+ :error => 6,
23
+ :warn => 4,
24
+ :info => 3,
25
+ :debug => 0
26
+ }
27
+
28
+ private
29
+
30
+ # Readies a log for writing.
31
+ #
32
+ # ==== Parameters
33
+ # log<IO, String>:: Either an IO object or a name of a logfile.
34
+ def initialize_log(log)
35
+ close if @log # be sure that we don't leave open files laying around.
36
+
37
+ if log.respond_to?(:write)
38
+ @log = log
39
+ elsif File.exist?(log)
40
+ @log = open(log, (File::WRONLY | File::APPEND))
41
+ @log.sync = true
42
+ else
43
+ FileUtils.mkdir_p(File.dirname(log)) unless File.directory?(File.dirname(log))
44
+ @log = open(log, (File::WRONLY | File::APPEND | File::CREAT))
45
+ @log.sync = true
46
+ @log.write("#{Time.now.httpdate} #{delimiter} info #{delimiter} Logfile created\n")
47
+ end
48
+ end
49
+
50
+ public
51
+
52
+ # To initialize the logger you create a new object, proxies to set_log.
53
+ #
54
+ # ==== Parameters
55
+ # *args:: Arguments to create the log from. See set_logs for specifics.
56
+ def initialize(*args)
57
+ @init_args = args
58
+ set_log(*args)
59
+ self.auto_flush = true
60
+ Dynamosaurus.logger = self
61
+ end
62
+
63
+ # Replaces an existing logger with a new one.
64
+ #
65
+ # ==== Parameters
66
+ # log<IO, String>:: Either an IO object or a name of a logfile.
67
+ # log_level<~to_sym>::
68
+ # The log level from, e.g. :fatal or :info. Defaults to :error in the
69
+ # production environment and :debug otherwise.
70
+ # delimiter<String>::
71
+ # Delimiter to use between message sections. Defaults to " ~ ".
72
+ # auto_flush<Boolean>::
73
+ # Whether the log should automatically flush after new messages are
74
+ # added. Defaults to false.
75
+ def set_log(log, log_level = nil, delimiter = " ~ ", auto_flush = false)
76
+ if log_level && Levels[log_level.to_sym]
77
+ @level = Levels[log_level.to_sym]
78
+ else
79
+ @level = Levels[:debug]
80
+ end
81
+ @buffer = []
82
+ @delimiter = delimiter
83
+ @auto_flush = auto_flush
84
+
85
+ initialize_log(log)
86
+ end
87
+
88
+ # Flush the entire buffer to the log object.
89
+ def flush
90
+ return unless @buffer.size > 0
91
+ @log.write(@buffer.slice!(0..-1).join)
92
+ end
93
+
94
+ # Close and remove the current log object.
95
+ def close
96
+ flush
97
+ @log.close if @log.respond_to?(:close) && !@log.tty?
98
+ @log = nil
99
+ end
100
+
101
+ # Appends a message to the log. The methods yield to an optional block and
102
+ # the output of this block will be appended to the message.
103
+ #
104
+ # ==== Parameters
105
+ # string<String>:: The message to be logged. Defaults to nil.
106
+ #
107
+ # ==== Returns
108
+ # String:: The resulting message added to the log file.
109
+ def <<(string = nil)
110
+ message = ""
111
+ message << delimiter
112
+ message << string if string
113
+ message << "\n" unless message[-1] == ?\n
114
+ @buffer << message
115
+ flush if @auto_flush
116
+
117
+ message
118
+ end
119
+ alias_method :push, :<<
120
+
121
+ # Generate the logging methods for DataMapper.logger for each log level.
122
+ Levels.each_pair do |name, number|
123
+ class_eval <<-LEVELMETHODS, __FILE__, __LINE__
124
+
125
+ # Appends a message to the log if the log level is at least as high as
126
+ # the log level of the logger.
127
+ #
128
+ # ==== Parameters
129
+ # string<String>:: The message to be logged. Defaults to nil.
130
+ #
131
+ # ==== Returns
132
+ # self:: The logger object for chaining.
133
+ def #{name}(message = nil)
134
+ self << message if #{number} >= level
135
+ self
136
+ end
137
+
138
+ # Appends a message to the log if the log level is at least as high as
139
+ # the log level of the logger. The bang! version of the method also auto
140
+ # flushes the log buffer to disk.
141
+ #
142
+ # ==== Parameters
143
+ # string<String>:: The message to be logged. Defaults to nil.
144
+ #
145
+ # ==== Returns
146
+ # self:: The logger object for chaining.
147
+ def #{name}!(message = nil)
148
+ self << message if #{number} >= level
149
+ flush if #{number} >= level
150
+ self
151
+ end
152
+
153
+ # ==== Returns
154
+ # Boolean:: True if this level will be logged by this logger.
155
+ def #{name}?
156
+ #{number} >= level
157
+ end
158
+ LEVELMETHODS
159
+ end
160
+
161
+ end
162
+
163
+ end
@@ -0,0 +1,144 @@
1
+ require "aws-sdk-core"
2
+
3
+ module Dynamosaurus
4
+ class DynamoBase
5
+ TYPES = {
6
+ :string => :s,
7
+ :number => :n
8
+ }
9
+
10
+ def dynamo_db
11
+ self.class.dynamo_db
12
+ end
13
+
14
+ def initialize params
15
+ data = params[:data] if params[:data]
16
+ @data = Dynamosaurus::DynamoBase.res2hash(data)
17
+ end
18
+
19
+ def table_name
20
+ self.class.table_name
21
+ end
22
+
23
+ def [] key
24
+ @data[key]
25
+ end
26
+
27
+ def []=(key,value)
28
+ @data[key] = value
29
+ end
30
+
31
+ def data
32
+ @data
33
+ end
34
+
35
+ def exist?
36
+ ! @data.empty?
37
+ end
38
+
39
+ def empty?
40
+ @data.empty?
41
+ end
42
+
43
+ def try name
44
+ @data[name.to_s]
45
+ end
46
+
47
+ def method_missing(name, *params)
48
+ name = name.to_s[0..-2] if name.to_s[-1] == "="
49
+ if exist? and @data.has_key?(name.to_s)
50
+ if params.empty?
51
+ @data[name.to_s]
52
+ else
53
+ @data[name.to_s] = params[0]
54
+ end
55
+ else
56
+ super
57
+ end
58
+ end
59
+
60
+ def key
61
+ return @key if @key
62
+ @key = self.class.get_key
63
+ end
64
+
65
+ def keys
66
+ item_key = {}
67
+ item_key[key[0].to_sym] = @data[key[0].to_s]
68
+ item_key[key[2].to_sym] = @data[key[2].to_s] if key.size == 4
69
+ item_key
70
+ end
71
+
72
+ def update attributes={}, attribute_nums={}, options={}
73
+ Dynamosaurus.logger << "update"
74
+
75
+ attribute_updates = {}
76
+ attributes.each do |k, v|
77
+ @data[k.to_s] = v
78
+ attribute_updates[k.to_s] = {
79
+ :value => v,
80
+ :action => "PUT"
81
+ }
82
+ end
83
+
84
+ attribute_nums.each do |k, v|
85
+ @data[k.to_s] = v.to_i
86
+ attribute_updates[k.to_s] = {
87
+ :value => v.to_i,
88
+ :action => "PUT"
89
+ }
90
+ end
91
+
92
+ query = {
93
+ :table_name => self.class.table_name,
94
+ :key => keys,
95
+ :attribute_updates => attribute_updates
96
+ }.merge(options)
97
+ res = dynamo_db.update_item(query)
98
+ end
99
+
100
+ def save
101
+ attribute_updates = {}
102
+ @data.each do |k, v|
103
+ if key.index(k.to_sym).nil?
104
+ attribute_updates[k.to_s] = {
105
+ :value => v,
106
+ :action => "PUT"
107
+ }
108
+ end
109
+ end
110
+
111
+ query = {
112
+ :table_name => self.class.table_name,
113
+ :key => keys,
114
+ :attribute_updates => attribute_updates
115
+ }
116
+ res = dynamo_db.update_item(query)
117
+ end
118
+
119
+ def attr_delete attributes=[]
120
+ Dynamosaurus.logger << "delete"
121
+
122
+ attribute_updates = {}
123
+ attributes.each do |k|
124
+ attribute_updates[k.to_s] = {
125
+ :action => "DELETE"
126
+ }
127
+ @data.delete(k.to_s)
128
+ end
129
+
130
+ res = dynamo_db.update_item(
131
+ :table_name => self.class.table_name,
132
+ :key => keys,
133
+ :attribute_updates => attribute_updates
134
+ )
135
+ end
136
+
137
+ def delete
138
+ dynamo_db.delete_item(
139
+ :table_name => self.class.table_name,
140
+ :key => keys
141
+ )
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,359 @@
1
+ module Dynamosaurus
2
+ class DynamoBase
3
+ class << self
4
+ def all_models
5
+ subclasses = []
6
+ ObjectSpace.each_object(Dynamosaurus::DynamoBase.singleton_class) do |k|
7
+ subclasses << k if k.superclass == Dynamosaurus::DynamoBase
8
+ end
9
+ subclasses
10
+ end
11
+
12
+ def create_table
13
+ tables = dynamo_db.list_tables.table_names
14
+
15
+ Dynamosaurus::DynamoBase.all_models.each do |model_class|
16
+ if tables.index(model_class.table_name).nil?
17
+ table = dynamo_db.create_table(
18
+ model_class.get_table_schema
19
+ )
20
+ end
21
+ end
22
+ end
23
+
24
+ def tables
25
+ Dynamosaurus::DynamoBase.all_models.map do |model_class|
26
+ model_class.table_name
27
+ end
28
+ end
29
+
30
+ def table_name
31
+ name.downcase + (ENV['DYNAMODB_SUFFIX'] || "_local")
32
+ end
33
+
34
+ def dynamo_db
35
+ return @dynamo_db if @dynamo_db
36
+ unless ENV['DYNAMODB_SUFFIX']
37
+ @dynamo_db = Aws::DynamoDB::Client.new(
38
+ :endpoint => "http://localhost:8000",
39
+ :region => "us-west-1"
40
+ )
41
+ else
42
+ @dynamo_db = Aws::DynamoDB::Client.new
43
+ end
44
+ end
45
+
46
+ def key k, type, range_key=nil, range_key_type=nil
47
+ @key = [k, Dynamosaurus::DynamoBase::TYPES[type]]
48
+ @key << range_key << Dynamosaurus::DynamoBase::TYPES[range_key_type] if range_key
49
+ end
50
+
51
+ def get_key
52
+ @key
53
+ end
54
+
55
+ def table_schema schema = {}
56
+ @schema = schema
57
+ end
58
+
59
+ def get_table_schema
60
+ @schema[:table_name] = table_name
61
+ @schema
62
+ end
63
+
64
+ def global_index index_name, key, key_type, range_key=nil, range_key_type=nil
65
+ @global_index = {} if @global_index.nil?
66
+ @global_index[index_name] = [key, Dynamosaurus::DynamoBase::TYPES[key_type]]
67
+ @global_index[index_name] << range_key << Dynamosaurus::DynamoBase::TYPES[range_key_type] if range_key
68
+ end
69
+
70
+ def get_global_indexes
71
+ (@global_index) ? @global_index : {}
72
+
73
+ end
74
+ def get_global_index name
75
+ @global_index[name]
76
+ end
77
+
78
+ def secondary_index index_name, range_key=nil, range_key_type=nil
79
+ @secondary_index = {} if @secondary_index.nil?
80
+ @secondary_index[index_name.to_sym] = [@key[0], @key[1], range_key, Dynamosaurus::DynamoBase::TYPES[range_key_type]] if range_key
81
+ end
82
+
83
+ def get_secondary_indexes
84
+ @secondary_index ? @secondary_index : {}
85
+ end
86
+ def get_secondary_index name
87
+ @secondary_index[name]
88
+ end
89
+
90
+ def get_index hash
91
+ get_secondary_indexes.merge(get_global_indexes).each{|key, value|
92
+ if hash.size == 1 && hash.keys.first == value.first
93
+ return {
94
+ :index_name => key,
95
+ :keys => [value[0]]
96
+ }
97
+ else hash.size == 2 && hash.keys.sort == [value[0], value[2]].sort
98
+ return {
99
+ :index_name => key,
100
+ :keys => [value[0], value[2]]
101
+ }
102
+ end
103
+ }
104
+ nil
105
+ end
106
+
107
+ def key_list
108
+ keys = get_key + get_secondary_indexes.values.flatten
109
+ list = {}
110
+ until keys.empty?
111
+ convi = keys.shift(2)
112
+ list[convi[0]] = convi[1]
113
+ end
114
+ list
115
+ end
116
+
117
+ def res2hash hash
118
+ new_hash = {}
119
+ return new_hash if hash.nil?
120
+ hash
121
+ end
122
+
123
+
124
+ def query_without_index value, option
125
+ keys = {}
126
+
127
+ value.each_with_index{|(k,v), i|
128
+ keys[k] = {
129
+ :comparison_operator => "EQ",
130
+ :attribute_value_list => [v.to_s]
131
+ }
132
+ }
133
+ Dynamosaurus.logger << "query index #{table_name} #{keys}"
134
+ query keys, nil, option
135
+ end
136
+
137
+ def get_item_key value
138
+ if value.is_a? Array
139
+ {
140
+ get_key[0] => value[0].to_s,
141
+ get_key[2] => value[1].to_s,
142
+ }
143
+ else
144
+ {
145
+ get_key[0] => value.to_s,
146
+ }
147
+ end
148
+ end
149
+
150
+ def get_from_key value, option={}
151
+ return nil if value.nil?
152
+
153
+ item_key = get_item_key(value)
154
+ Dynamosaurus.logger << "get_item #{table_name} #{item_key}"
155
+
156
+ res = dynamo_db.get_item(
157
+ :table_name => table_name,
158
+ :key => item_key
159
+ )
160
+ if res.item
161
+ new :data => res.item
162
+ else
163
+ nil
164
+ end
165
+ end
166
+
167
+ def get_from_index hash, option={}
168
+ if index = get_index(hash)
169
+ keys = {}
170
+
171
+ index[:keys].each do |key|
172
+ keys[key] = {
173
+ :comparison_operator => "EQ",
174
+ :attribute_value_list => [hash[key]]
175
+ }
176
+ end
177
+ Dynamosaurus.logger << "query index #{table_name} #{keys}"
178
+ query keys, index[:index_name], option
179
+ end
180
+ end
181
+
182
+ def get_from_local_index hash, option={}
183
+ if index = hash.delete(:index)
184
+ keys = {}
185
+ hash.each do |k, v|
186
+ keys[k] = {
187
+ :comparison_operator => "EQ",
188
+ :attribute_value_list => [ v.to_s ]
189
+ }
190
+ end
191
+ Dynamosaurus.logger << "query local_index #{table_name} #{keys}"
192
+ query keys, index, option
193
+ end
194
+ end
195
+
196
+ def query keys, index=nil, option={}
197
+ query = {
198
+ :table_name => table_name,
199
+ :key_conditions => keys,
200
+ }
201
+ if index
202
+ query[:index_name] = index
203
+ end
204
+
205
+ query.merge!(option)
206
+ res = dynamo_db.query(query)
207
+ if query[:select] == "COUNT"
208
+ res[:count]
209
+ else
210
+ if res.items
211
+ return res.items.map{|item|
212
+ new :data => item
213
+ }
214
+ end
215
+ end
216
+ end
217
+
218
+ # public method
219
+ def get value, option={}
220
+ if value.is_a? Hash
221
+ if value[:index]
222
+ get_from_local_index value, option
223
+ elsif option[:noindex]
224
+ option.delete(:noindex)
225
+ query_without_index value, option
226
+ else
227
+ get_from_index value, option
228
+ end
229
+ else
230
+ get_from_key value, option
231
+ end
232
+ end
233
+
234
+ def batch_get_item keys
235
+ return nil if keys.nil? or keys.empty?
236
+ Dynamosaurus.logger << "batch_get_item #{table_name} #{keys}"
237
+ if get_key.size == 2
238
+ my_keys = keys.map{|_key| {get_key[0].to_s => _key.to_s } }
239
+ else
240
+ my_keys = []
241
+ keys[get_key[0]].each do |key1|
242
+ keys[get_key[2]].each do |key2|
243
+ my_keys << {
244
+ get_key[0].to_s => key1,
245
+ get_key[2].to_s => key2,
246
+ }
247
+ end
248
+ end
249
+ end
250
+
251
+ res = dynamo_db.batch_get_item(
252
+ :request_items => {
253
+ table_name => {
254
+ :keys => my_keys
255
+ }
256
+ })
257
+
258
+ if res.responses[table_name]
259
+ return res.responses[table_name].map{|item|
260
+ new :data => item
261
+ }
262
+ end
263
+ nil
264
+ end
265
+
266
+ def first
267
+ Dynamosaurus.logger << "first #{table_name}"
268
+ res = dynamo_db.scan({
269
+ :table_name => table_name,
270
+ :limit => 1
271
+ })
272
+ if res.items && res.count > 0
273
+ new :data => res.items[0]
274
+ else
275
+ nil
276
+ end
277
+ end
278
+
279
+ def all
280
+ Dynamosaurus.logger << "all #{table_name}"
281
+ res = dynamo_db.scan({
282
+ :table_name => table_name,
283
+ })
284
+ if res.items
285
+ res.items.map{|item|
286
+ new :data => item
287
+ }
288
+ end
289
+ end
290
+
291
+ def put hash, num_hash={}, return_values=nil
292
+
293
+ new_hash = {}
294
+ hash.each{|key, value|
295
+ new_hash[key] = value.to_s unless value.nil?
296
+ }
297
+ num_hash.merge({:updated_at => Time.now.to_i}).each{|key, value|
298
+ new_hash[key] = value.to_i unless value.nil?
299
+ } if num_hash
300
+
301
+ res = dynamo_db.put_item(
302
+ :table_name => table_name,
303
+ :item => new_hash,
304
+ :return_values => return_values || "NONE"
305
+ )
306
+ end
307
+
308
+ def save hash, num_hash={}, return_values=nil
309
+ put(hash, num_hash, return_values)
310
+ my_keys = get_key
311
+
312
+ if my_keys.size == 4
313
+ get([hash[my_keys[0]], hash[my_keys[2]]])
314
+ else
315
+ get(hash[my_keys[0]])
316
+ end
317
+ end
318
+
319
+ def add key=[], attribute_nums={}, options={}
320
+ Dynamosaurus.logger << "update"
321
+
322
+ attribute_updates = {}
323
+ attribute_nums.each do |k, v|
324
+ attribute_updates[k.to_s] = {
325
+ :value => v,
326
+ :action => "ADD"
327
+ }
328
+ end
329
+
330
+ class_key = get_key
331
+ keys = {
332
+ class_key[0].to_sym => key.is_a?(Array) ? key[0] : key
333
+ }
334
+ keys[class_key[2].to_sym] = key[1] if class_key.size > 2
335
+
336
+ query ={
337
+ :table_name => table_name,
338
+ :key => keys,
339
+ :attribute_updates => attribute_updates
340
+ }
341
+ query = query.merge(options)
342
+ res = dynamo_db.update_item(query)
343
+
344
+ end
345
+
346
+ def delete_item value
347
+ return nil if value.nil?
348
+
349
+ old_item = dynamo_db.delete_item(
350
+ :table_name => table_name,
351
+ :key => get_item_key(value),
352
+ :return_values => "ALL_OLD"
353
+ )
354
+ new :data => old_item.attributes
355
+ end
356
+ end
357
+ # end of self class
358
+ end
359
+ end
@@ -0,0 +1,3 @@
1
+ module Dynamosaurus
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,149 @@
1
+ require 'spec_helper'
2
+
3
+ describe Dynamosaurus do
4
+ before(:all) do
5
+ ENV['DYNAMODB_SUFFIX'] = "_local"
6
+
7
+ Dynamosaurus::Logger.new('log/vm.log', :debug)
8
+ Aws.config = {
9
+ :endpoint => "http://localhost:8000",
10
+ :region => 'local_test',
11
+ }
12
+
13
+ Dynamosaurus::DynamoBase.create_table
14
+ end
15
+
16
+ after(:all) do
17
+ connect = Dynamosaurus::DynamoBase.dynamo_db
18
+ Dynamosaurus::DynamoBase.tables.each do |table_name|
19
+ connect.delete_table(:table_name => table_name)
20
+ end
21
+ end
22
+
23
+ it 'should have a version number' do
24
+ expect(Dynamosaurus::VERSION).not_to be_nil
25
+ end
26
+
27
+ it 'simple ordered kvs test' do
28
+ expect(SimpleOrderedKVS.first).to be_nil
29
+
30
+ SimpleOrderedKVS.put({:simple_key => "key", :simple_id => "1"})
31
+
32
+ kvs = SimpleOrderedKVS.first
33
+ expect(kvs.simple_key).to eq "key"
34
+ expect(kvs.simple_id).to eq "1"
35
+
36
+ SimpleOrderedKVS.get(["key", "1"])
37
+ expect(kvs.simple_id).to eq "1"
38
+
39
+ sleep 1
40
+ SimpleOrderedKVS.put({:simple_key => "key", :simple_id => "3"})
41
+ sleep 1
42
+ SimpleOrderedKVS.put({:simple_key => "key", :simple_id => "2"})
43
+
44
+ orderd_items = SimpleOrderedKVS.get({
45
+ :index => "updated_at_index",
46
+ :simple_key => "key"
47
+ },{
48
+ :scan_index_forward => false,
49
+ :limit => 50,
50
+ })
51
+ expect(orderd_items[0].simple_id).to eq "2"
52
+ expect(orderd_items[1].simple_id).to eq "3"
53
+ expect(orderd_items[2].simple_id).to eq "1"
54
+
55
+ batch_items = SimpleOrderedKVS.batch_get_item({:simple_key => ["key"], :simple_id => ["1", "2", "3"]})
56
+ expect(orderd_items.size).to eq 3
57
+
58
+ kvs = SimpleOrderedKVS.first
59
+ kvs2 = SimpleOrderedKVS.get({:simple_key => kvs.simple_key, :updated_at => kvs.updated_at})
60
+ expect(kvs.simple_id).to eq kvs2[0].simple_id
61
+
62
+ end
63
+
64
+ it 'simple kvs test' do
65
+ expect(SimpleKVS.first).to be_nil
66
+ expect(SimpleKVS.get("key")).to be_nil
67
+
68
+ SimpleKVS.put({:simple_key => "key"}, {:num => 1})
69
+ expect(SimpleKVS.first.num).to eq 1
70
+
71
+ kvs = SimpleKVS.get("key")
72
+ expect(kvs.num).to eq 1
73
+
74
+ kvs = SimpleKVS.get("key2")
75
+ expect(kvs).to be_nil
76
+
77
+ kvs = SimpleKVS.first
78
+ expect(kvs.num).to eq 1
79
+
80
+ SimpleKVS.add("key", {:num => 1})
81
+ expect(SimpleKVS.first.num).to eq 2
82
+
83
+ SimpleKVS.put({:simple_key => "key"}, {:num => 1})
84
+ expect(SimpleKVS.first.num).to eq 1
85
+
86
+ kvs = SimpleKVS.first
87
+ kvs.update({}, {:test => 1})
88
+ expect(SimpleKVS.first.test).to eq 1
89
+
90
+ kvs = SimpleKVS.first
91
+ kvs.update({:test => "1"})
92
+ expect(SimpleKVS.first.test).to eq "1"
93
+
94
+ kvs.attr_delete(["test"])
95
+ expect(kvs["test"]).to be_nil
96
+
97
+ kvs = SimpleKVS.first
98
+ expect(kvs["test"]).to be_nil
99
+
100
+ kvs.num = 100
101
+ kvs.save
102
+
103
+ kvs = SimpleKVS.first
104
+ expect(kvs.num).to eq 100
105
+
106
+ SimpleKVS.put({:simple_key => "key1"}, {:num => 1})
107
+ SimpleKVS.put({:simple_key => "key2"}, {:num => 2})
108
+ SimpleKVS.put({:simple_key => "key3"}, {:num => 3})
109
+
110
+ expect(SimpleKVS.all.size).to eq 4
111
+
112
+ expect(SimpleKVS.batch_get_item(["key1"]).size).to eq 1
113
+ expect(SimpleKVS.batch_get_item(["key1", "key2"]).size).to eq 2
114
+ expect(SimpleKVS.batch_get_item(["key1", "key10"]).size).to eq 1
115
+
116
+ old_kvs = SimpleKVS.delete_item("key2")
117
+ expect(old_kvs.simple_key).to eq "key2"
118
+ expect(SimpleKVS.batch_get_item(["key1", "key2"]).size).to eq 1
119
+
120
+ kvs = SimpleKVS.get("key3")
121
+ kvs.delete
122
+ kvs = SimpleKVS.get("key3")
123
+ expect(kvs).to be_nil
124
+
125
+ end
126
+
127
+ it 'comment test' do
128
+ expect(Comment.first).to be_nil
129
+ expect(Comment.get(["1", "1"])).to be_nil
130
+
131
+ Comment.put({:content_id => "1", :message_id => "1", :user_id => "abc"})
132
+ expect(Comment.first.content_id).to eq "1"
133
+
134
+ kvs = Comment.get(["1", "1"])
135
+ expect(kvs.content_id).to eq "1"
136
+
137
+ kvs = Comment.get(["2", "2"])
138
+ expect(kvs).to be_nil
139
+
140
+ Comment.put({:content_id => "1", :message_id => "2", :user_id => "abc"})
141
+ Comment.put({:content_id => "1", :message_id => "3", :user_id => "xyz"})
142
+
143
+ # global secondary index
144
+ comments = Comment.get({:user_id => "abc"})
145
+ expect(comments.size).to eq 2
146
+
147
+ end
148
+
149
+ end
@@ -0,0 +1,3 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+ require 'dynamosaurus'
3
+ require 'testmodel'
@@ -0,0 +1,103 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ class SimpleKVS < Dynamosaurus::DynamoBase
4
+ key :simple_key, :string
5
+
6
+ table_schema :key_schema =>
7
+ [
8
+ { :key_type => "HASH",
9
+ :attribute_name => "simple_key"
10
+ }
11
+ ],
12
+ :provisioned_throughput => {
13
+ :read_capacity_units => 100,
14
+ :write_capacity_units => 10
15
+ },
16
+ :attribute_definitions => [
17
+ {:attribute_name => "simple_key", :attribute_type => "S"},
18
+ ]
19
+ end
20
+
21
+ class SimpleOrderedKVS < Dynamosaurus::DynamoBase
22
+ key :simple_key, :string, :simple_id, :string
23
+ secondary_index :updated_at_index, :updated_at, :number
24
+
25
+ table_schema :key_schema =>
26
+ [
27
+ { :key_type => "HASH",
28
+ :attribute_name => "simple_key"
29
+ },
30
+ { :key_type => "RANGE",
31
+ :attribute_name => "simple_id"},
32
+ ],
33
+ :provisioned_throughput => {
34
+ :read_capacity_units => 20,
35
+ :write_capacity_units => 10
36
+ },
37
+ :attribute_definitions => [
38
+ {:attribute_name => "simple_key", :attribute_type => "S"},
39
+ {:attribute_name => "simple_id", :attribute_type => "S"},
40
+ {:attribute_name => "updated_at", :attribute_type => "N"},
41
+ ],
42
+ :local_secondary_indexes => [
43
+ {
44
+ :index_name => "updated_at_index",
45
+ :key_schema => [
46
+ { :key_type => "HASH",
47
+ :attribute_name => "simple_key"},
48
+
49
+ {
50
+ :key_type => "RANGE",
51
+ :attribute_name => "updated_at"
52
+ }
53
+ ],
54
+ :projection => {
55
+ :projection_type => "ALL"
56
+ },
57
+ },
58
+ ]
59
+ end
60
+
61
+ class Comment < Dynamosaurus::DynamoBase
62
+ key :content_id, :string, :message_id, :string
63
+ global_index :user_index, :user_id, :string
64
+
65
+ table_schema :key_schema =>
66
+ [
67
+ {
68
+ :key_type => "HASH",
69
+ :attribute_name => "content_id"
70
+ },
71
+ {
72
+ :key_type => "RANGE",
73
+ :attribute_name => "message_id"
74
+ }
75
+ ],
76
+ :provisioned_throughput => {
77
+ :read_capacity_units => 100,
78
+ :write_capacity_units => 10
79
+ },
80
+ :attribute_definitions => [
81
+ {:attribute_name => "content_id", :attribute_type => "S"},
82
+ {:attribute_name => "message_id", :attribute_type => "S"},
83
+ {:attribute_name => "user_id", :attribute_type => "S"},
84
+ ],
85
+ :global_secondary_indexes => [
86
+ {
87
+ :index_name => "user_index",
88
+ :key_schema => [
89
+ {
90
+ :key_type => "HASH",
91
+ :attribute_name => "user_id"
92
+ },
93
+ ],
94
+ :projection => {
95
+ :projection_type => "KEYS_ONLY",
96
+ },
97
+ :provisioned_throughput => {
98
+ :read_capacity_units => 50,
99
+ :write_capacity_units => 10
100
+ },
101
+ },
102
+ ]
103
+ end
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dynamosaurus
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Isamu Arimoto
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-08-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
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: aws-sdk-core
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: 2.0.20
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: 2.0.20
69
+ description: Dynamodb simple ORM
70
+ email:
71
+ - isamu.a@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .gitignore
77
+ - .rspec
78
+ - .travis.yml
79
+ - Gemfile
80
+ - LICENSE.txt
81
+ - README.md
82
+ - Rakefile
83
+ - dynamosaurus.gemspec
84
+ - lib/dynamosaurus.rb
85
+ - lib/dynamosaurus/dynamo_base.rb
86
+ - lib/dynamosaurus/dynamo_class.rb
87
+ - lib/dynamosaurus/version.rb
88
+ - spec/dynamosaurus_spec.rb
89
+ - spec/spec_helper.rb
90
+ - spec/testmodel.rb
91
+ homepage: ''
92
+ licenses:
93
+ - MIT
94
+ metadata: {}
95
+ post_install_message:
96
+ rdoc_options: []
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubyforge_project:
111
+ rubygems_version: 2.4.6
112
+ signing_key:
113
+ specification_version: 4
114
+ summary: Dynamodb simple ORM
115
+ test_files:
116
+ - spec/dynamosaurus_spec.rb
117
+ - spec/spec_helper.rb
118
+ - spec/testmodel.rb