tsukasaoishi-miyazakiresistance 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2009-03-21
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/Manifest.txt ADDED
@@ -0,0 +1,5 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.rdoc
4
+ Rakefile
5
+ lib/miyazakiresistance.rb
data/README.rdoc ADDED
@@ -0,0 +1,53 @@
1
+ = miyazakiresistance
2
+
3
+ * http://www.kaeruspoon.net/keywords/MiyazakiResistance
4
+
5
+ == DESCRIPTION:
6
+
7
+ MiyazakiResistance is a library like ActiveRecord to use Tokyo Tyrant.
8
+
9
+ == SYNOPSIS:
10
+
11
+ require 'miyazaki_resistance'
12
+ class Example < MiyazakiResistance::Base
13
+ host_and_port "localhost", 1975
14
+ host_and_port "slave", 1975, :readonly
15
+ set_timeout 5
16
+ set_column :name, :string
17
+ set_column :age, :integer
18
+ set_column :birthday, :date
19
+ set_column :created_at, :datetime
20
+ end
21
+
22
+ == REQUIREMENTS:
23
+
24
+ Tokyo Tyrant
25
+
26
+ == INSTALL:
27
+
28
+ * sudo gem install tsukasaoishi-miyazakiresistance
29
+
30
+ == LICENSE:
31
+
32
+ (The MIT License)
33
+
34
+ Copyright (c) 2009 Tsukasa OISHI
35
+
36
+ Permission is hereby granted, free of charge, to any person obtaining
37
+ a copy of this software and associated documentation files (the
38
+ 'Software'), to deal in the Software without restriction, including
39
+ without limitation the rights to use, copy, modify, merge, publish,
40
+ distribute, sublicense, and/or sell copies of the Software, and to
41
+ permit persons to whom the Software is furnished to do so, subject to
42
+ the following conditions:
43
+
44
+ The above copyright notice and this permission notice shall be
45
+ included in all copies or substantial portions of the Software.
46
+
47
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
48
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
49
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
50
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
51
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
52
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
53
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ %w[rubygems rake rake/clean fileutils newgem rubigen].each { |f| require f }
2
+ require File.dirname(__FILE__) + '/lib/miyazakiresistance'
3
+
4
+ # Generate all the Rake tasks
5
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
6
+ $hoe = Hoe.new('miyazakiresistance', MiyazakiResistance::VERSION) do |p|
7
+ p.developer('FIXME full name', 'FIXME email')
8
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
9
+ p.post_install_message = 'PostInstall.txt' # TODO remove if post-install message not required
10
+ p.rubyforge_name = p.name # TODO this is default value
11
+ # p.extra_deps = [
12
+ # ['activesupport','>= 2.0.2'],
13
+ # ]
14
+ p.extra_dev_deps = [
15
+ ['newgem', ">= #{::Newgem::VERSION}"]
16
+ ]
17
+
18
+ p.clean_globs |= %w[**/.DS_Store tmp *.log]
19
+ path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
20
+ p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
21
+ p.rsync_args = '-av --delete --ignore-errors'
22
+ end
23
+
24
+ require 'newgem/tasks' # load /tasks/*.rake
25
+ Dir['tasks/**/*.rake'].each { |t| load t }
26
+
27
+ # TODO - want other tests/tasks run by default? Add them to the list
28
+ # task :default => [:spec, :features]
@@ -0,0 +1,359 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'tokyotyrant'
5
+ require 'timeout'
6
+
7
+ module MiyazakiResistance
8
+ VERSION = '0.0.1'
9
+
10
+ class NewRecordError < StandardError; end
11
+ class QueryError < StandardError; end
12
+ class AllTimeoutORConnectionPoolEmpty < StandardError; end
13
+
14
+ module Connection
15
+ def self.included(base)
16
+ base.extend ClassMethods
17
+ base.__send__(:include, InstanceMethods)
18
+ end
19
+
20
+ module ClassMethods
21
+ attr_accessor :connection_pool
22
+ attr_accessor :all_columns
23
+ attr_accessor :all_indexes
24
+ attr_accessor :timeout_time
25
+
26
+ def host_and_port(host, port, target = :write)
27
+ self.connection_pool ||= {:read => [], :write => []}
28
+ rdb = TokyoTyrant::RDBTBL.new
29
+ return unless rdb.open(host, port)
30
+ self.connection_pool[:read] << rdb
31
+ self.connection_pool[:write] << rdb if target == :write
32
+ end
33
+
34
+ def set_timeout(seconds)
35
+ self.timeout_time = seconds.to_i
36
+ end
37
+
38
+ def set_column(name, type, index = :no_index)
39
+ name = name.to_s
40
+ self.__send__(:attr_accessor, name)
41
+ self.all_indexes ||= []
42
+ self.all_columns ||= {}
43
+ self.all_columns.update(name => type)
44
+ if index == :index
45
+ index_type =
46
+ if [:integer, :datetime, :date].include?(type)
47
+ TokyoTyrant::RDBTBL::ITDECIMAL
48
+ elsif type == :string
49
+ TokyoTyrant::RDBTBL::ITLEXICAL
50
+ end
51
+ self.all_indexes << name
52
+ con = connection(:write)
53
+ begin
54
+ con.setindex(name, index_type)
55
+ rescue TimeoutError
56
+ remove_pool(con)
57
+ retry
58
+ end
59
+ end
60
+ end
61
+
62
+ def connection(target = :read)
63
+ check_pool(target)
64
+ self.connection_pool[target].sort_by{rand}.first
65
+ end
66
+
67
+ def remove_pool(rdb)
68
+ [:read, :write].each do |target|
69
+ self.connection_pool[target].delete_if{|pool| pool == rdb}
70
+ check_pool(target)
71
+ end
72
+ end
73
+
74
+ def kaeru_timeout(&block)
75
+ ret = nil
76
+ thread = Thread.new{ret = yield}
77
+ raise TimeoutError unless thread.join(self.timeout_time)
78
+ ret
79
+ end
80
+
81
+ private
82
+
83
+ def check_pool(target)
84
+ raise AllTimeoutORConnectionPoolEmpty if self.connection_pool[target].empty?
85
+ end
86
+ end
87
+
88
+ module InstanceMethods
89
+ def connection(target = :read)
90
+ self.class.connection(target)
91
+ end
92
+
93
+ def remove_pool(rdb)
94
+ self.class.remove_pool(rdb)
95
+ end
96
+
97
+ def kaeru_timeout(&block)
98
+ self.class.kaeru_timeout(&block)
99
+ end
100
+ end
101
+ end
102
+
103
+ class Base
104
+ include Connection
105
+
106
+ OPERATIONS = {
107
+ "=" => {:string => TokyoTyrant::RDBQRY::QCSTREQ, :integer => TokyoTyrant::RDBQRY::QCNUMEQ},
108
+ "include" => {:string => TokyoTyrant::RDBQRY::QCSTRINC},
109
+ "begin" => {:string => TokyoTyrant::RDBQRY::QCSTRBW},
110
+ "end" => {:string => TokyoTyrant::RDBQRY::QCSTREW},
111
+ "allinclude" => {:string => TokyoTyrant::RDBQRY::QCSTRAND},
112
+ "anyinclude" => {:string => TokyoTyrant::RDBQRY::QCSTROR},
113
+ "in" => {:string => TokyoTyrant::RDBQRY::QCSTROREQ, :integer => TokyoTyrant::RDBQRY::QCNUMOREQ},
114
+ "=~" => {:string => TokyoTyrant::RDBQRY::QCSTRRX},
115
+ ">" => {:integer => TokyoTyrant::RDBQRY::QCNUMGT},
116
+ ">=" => {:integer => TokyoTyrant::RDBQRY::QCNUMGE},
117
+ "<" => {:integer => TokyoTyrant::RDBQRY::QCNUMLT},
118
+ "<=" => {:integer => TokyoTyrant::RDBQRY::QCNUMLE},
119
+ "between" => {:integer => TokyoTyrant::RDBQRY::QCNUMBT}
120
+ }
121
+ DATE_TYPE = [:datetime, :date]
122
+
123
+ attr_accessor :id
124
+
125
+ def initialize(args)
126
+ args ||= {}
127
+ self.id = nil
128
+ args.each do |key, value|
129
+ case self.class.all_columns[key.to_s]
130
+ when :integer
131
+ value = value.to_i
132
+ when :string
133
+ value = value.to_s
134
+ when :datetime
135
+ value = Time.at(value.to_i) unless value.is_a?(Time)
136
+ when :date
137
+ unless value.is_a?(Date)
138
+ time = Time.at(value.to_i)
139
+ value = Date.new(time.year, time.month, time.day)
140
+ end
141
+ end
142
+
143
+ self.__send__("#{key}=", value)
144
+ end
145
+ end
146
+
147
+ def save
148
+ time_column_check
149
+ con = connection(:write)
150
+ self.id = kaeru_timeout{con.genuid.to_i} if new_record?
151
+ kaeru_timeout do
152
+ con.put(self.id, raw_attributes)
153
+ self.class.all_indexes.each {|index| con.setindex(index, TokyoTyrant::RDBTBL::ITOPT)}
154
+ end
155
+ rescue TimeoutError
156
+ remove_pool(con)
157
+ retry
158
+ end
159
+
160
+
161
+ def update_attributes(args)
162
+ raise NewRecordError if new_record?
163
+ attributes = args
164
+ save
165
+ end
166
+
167
+ def destroy
168
+ raise NewRecordError if new_record?
169
+ con = connection(:write)
170
+ kaeru_timeout{con.out(self.id)}
171
+ rescue TimeoutError
172
+ remove_pool(con)
173
+ retry
174
+ end
175
+
176
+ def attributes
177
+ hash = {}
178
+ self.class.all_columns.keys.each{|key| hash.update(key => self.__send__(key))}
179
+ hash
180
+ end
181
+
182
+ def attributes=(args)
183
+ args.each {|key, value| self.__send__("#{key}=", value)}
184
+ end
185
+
186
+ def new_record?
187
+ self.id.nil?
188
+ end
189
+
190
+ class << self
191
+ def find(first, args={})
192
+ case first.class.name
193
+ when "Fixnum"
194
+ find_by_id(first)
195
+ when "Array"
196
+ find_by_ids(first)
197
+ when "Symbol"
198
+ find_by_query(first, args)
199
+ else
200
+ raise ArgumentError
201
+ end
202
+ end
203
+
204
+ def find_by_query(mode, args={})
205
+ con = connection
206
+ query = TokyoTyrant::RDBQRY.new(con)
207
+
208
+ limit = (mode == :first ? 1 : args[:limit])
209
+ query = make_limit(query, limit, args[:offset])
210
+ query = make_order(query, args[:order])
211
+ query = make_conditions(query, args[:conditions])
212
+
213
+ ids = kaeru_timeout{query.search}
214
+ ret = find_by_ids(ids)
215
+ ret = ret.first if limit.to_i == 1
216
+ ret
217
+ rescue TimeoutError
218
+ remove_pool(con)
219
+ retry
220
+ end
221
+
222
+ def find_by_id(target)
223
+ find_by_ids([target])
224
+ end
225
+
226
+ def count(args = {})
227
+ con = connection
228
+ if args.empty?
229
+ kaeru_timeout{con.rnum}
230
+ else
231
+ query = TokyoTyrant::RDBQRY.new(con)
232
+ query = make_conditions(query, args[:conditions])
233
+ kaeru_timeout{query.search}.size
234
+ end
235
+ rescue TimeoutError
236
+ remove_pool(con)
237
+ retry
238
+ end
239
+
240
+ def create(args)
241
+ inst = self.new(args)
242
+ inst.save
243
+ inst
244
+ end
245
+ end
246
+
247
+ private
248
+
249
+ def raw_attributes
250
+ hash = {}
251
+ self.class.all_columns.each do |col, type|
252
+ value = self.__send__(col)
253
+ value = self.class.convert_date_to_i(value, type)
254
+ hash.update(col.to_s => value)
255
+ end
256
+ hash
257
+ end
258
+
259
+ def time_column_check
260
+ time_columns = ["updated"]
261
+ time_columns << "created" if new_record?
262
+ time_columns.each do |col|
263
+ %w|at on|.each do |type|
264
+ if self.class.all_columns.keys.include?("#{col}_#{type}")
265
+ now = Time.now
266
+ now = Date.new(now.year, now.month, now.day) if type == "on"
267
+ self.__send__("#{col}_#{type}=", now) if self.__send__("#{col}_#{type}").nil? || col == "updated"
268
+ end
269
+ end
270
+ end
271
+ end
272
+
273
+ def self.type_upcase(type)
274
+ case type
275
+ when :integer, :datetime, :date
276
+ "NUM"
277
+ when :string
278
+ "STR"
279
+ end
280
+ end
281
+
282
+ def self.find_by_ids(targets)
283
+ targets.map do |key|
284
+ begin
285
+ con = connection
286
+ data = kaeru_timeout{con.get(key)}
287
+ inst = self.new(data)
288
+ inst.id = key.to_i
289
+ inst
290
+ rescue TimeoutError
291
+ remove_pool(con)
292
+ retry
293
+ end
294
+ end
295
+ end
296
+
297
+ def self.make_limit(query, limit, offset)
298
+ query.setlimit(limit, offset) if limit
299
+ query
300
+ end
301
+
302
+ def self.make_order(query, order)
303
+ if order
304
+ target, order_type = order.split
305
+ if target == "id" || self.all_columns.keys.include?(target)
306
+ type = (target == "id" ? :integer : self.all_columns[target])
307
+ target = "" if target == "id"
308
+ order_type ||= "asc"
309
+ eval(%Q|query.setorder(target, TokyoTyrant::RDBQRY::QO#{type_upcase(type)}#{order_type.upcase})|)
310
+ end
311
+ end
312
+ query
313
+ end
314
+
315
+ def self.make_conditions(query, conditions)
316
+ if conditions
317
+ cond = conditions.first
318
+ param = conditions[1..-1]
319
+
320
+ col, ope, exp, type = nil, nil, nil, nil
321
+ cond.split.each do |item|
322
+ if self.all_columns.keys.include?(item)
323
+ col = item
324
+ type = self.all_columns[item]
325
+ elsif item == "id"
326
+ col = ""
327
+ type = :integer
328
+ elsif OPERATIONS.keys.include?(item)
329
+ raise QueryError if col.nil? || type.nil?
330
+ work = type
331
+ work = :integer if DATE_TYPE.include?(work)
332
+ ope = OPERATIONS[item][work]
333
+ else
334
+ raise QueryError if col.nil? || type.nil? || ope.nil?
335
+ exp = (item == "?" ? param.shift : item)
336
+ exp = convert_date_to_i(exp, type)
337
+ end
338
+
339
+ if col && type && ope && exp
340
+ query.addcond(col, ope, exp.to_s)
341
+ col, ope, exp, type = nil, nil, nil, nil
342
+ end
343
+ end
344
+ end
345
+ query
346
+ end
347
+
348
+ def self.convert_date_to_i(value, type)
349
+ case type
350
+ when :datetime
351
+ value.to_i
352
+ when :date
353
+ Time.local(value.year, value.month, value.day).to_i
354
+ else
355
+ value
356
+ end
357
+ end
358
+ end
359
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tsukasaoishi-miyazakiresistance
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - FIXME full name
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-03-21 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: newgem
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.2.3
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: hoe
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.8.0
34
+ version:
35
+ description: MiyazakiResistance is a library like ActiveRecord to use Tokyo Tyrant.
36
+ email:
37
+ - FIXME email
38
+ executables: []
39
+
40
+ extensions: []
41
+
42
+ extra_rdoc_files:
43
+ - History.txt
44
+ - Manifest.txt
45
+ - README.rdoc
46
+ files:
47
+ - History.txt
48
+ - Manifest.txt
49
+ - README.rdoc
50
+ - Rakefile
51
+ - lib/miyazakiresistance.rb
52
+ has_rdoc: true
53
+ homepage: http://www.kaeruspoon.net/keywords/MiyazakiResistance
54
+ post_install_message: PostInstall.txt
55
+ rdoc_options:
56
+ - --main
57
+ - README.rdoc
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: "0"
65
+ version:
66
+ required_rubygems_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: "0"
71
+ version:
72
+ requirements: []
73
+
74
+ rubyforge_project: miyazakiresistance
75
+ rubygems_version: 1.2.0
76
+ signing_key:
77
+ specification_version: 2
78
+ summary: MiyazakiResistance is a library like ActiveRecord to use Tokyo Tyrant.
79
+ test_files: []
80
+