dynamosaurus 0.0.1

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