mongoid-bolt 1.0.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.
data/README.md ADDED
@@ -0,0 +1,45 @@
1
+ NAME
2
+ ----
3
+ mongoid-bolt
4
+
5
+ INSTALL
6
+ -------
7
+ gem install mongoid-bolt
8
+
9
+ SYNOPSIS
10
+ --------
11
+
12
+ ````ruby
13
+
14
+ require 'mongoid-bolt'
15
+
16
+ Bolt.for(:shared_resource) do
17
+ ioslated!
18
+ end
19
+
20
+
21
+ class A
22
+ include Mongoid::Document
23
+ include Mongoid::Bolt
24
+ end
25
+
26
+ a = A.new
27
+
28
+ a.lock!
29
+
30
+ a.unlock!
31
+
32
+ a.lock do
33
+ isolated!
34
+ end
35
+
36
+
37
+
38
+ ````
39
+
40
+ DESCRIPTION
41
+ -----------
42
+
43
+ mongoid-bolt is a concrete lock implementation and mixin.
44
+
45
+ it is process safe and atomic in a mongoid cluster.
data/Rakefile ADDED
@@ -0,0 +1,446 @@
1
+ This.name =
2
+ "Mongoid::Bolt"
3
+
4
+ This.synopsis =
5
+ "a mongoid 3/moped compatible lock implementation and mixin"
6
+
7
+ This.rubyforge_project = 'codeforpeople'
8
+ This.author = "Ara T. Howard"
9
+ This.email = "ara.t.howard@gmail.com"
10
+ This.homepage = "https://github.com/ahoward/#{ This.lib }"
11
+
12
+ This.setup!
13
+
14
+
15
+
16
+ task :default do
17
+ puts((Rake::Task.tasks.map{|task| task.name.gsub(/::/,':')} - ['default']).sort)
18
+ end
19
+
20
+ task :test do
21
+ This.run_tests!
22
+ end
23
+
24
+ namespace :test do
25
+ task(:unit){ This.run_tests!(:unit) }
26
+ task(:functional){ This.run_tests!(:functional) }
27
+ task(:integration){ This.run_tests!(:integration) }
28
+ end
29
+
30
+ def This.run_tests!(which = nil)
31
+ which ||= '**'
32
+ test_dir = File.join(This.dir, "test")
33
+ test_glob ||= File.join(test_dir, "#{ which }/**_test.rb")
34
+ test_rbs = Dir.glob(test_glob).sort
35
+
36
+ div = ('=' * 119)
37
+ line = ('-' * 119)
38
+
39
+ test_rbs.each_with_index do |test_rb, index|
40
+ testno = index + 1
41
+ command = "#{ File.basename(This.ruby) } -I ./lib -I ./test/lib #{ test_rb }"
42
+
43
+ puts
44
+ This.say(div, :color => :cyan, :bold => true)
45
+ This.say("@#{ testno } => ", :bold => true, :method => :print)
46
+ This.say(command, :color => :cyan, :bold => true)
47
+ This.say(line, :color => :cyan, :bold => true)
48
+
49
+ system(command)
50
+
51
+ This.say(line, :color => :cyan, :bold => true)
52
+
53
+ status = $?.exitstatus
54
+
55
+ if status.zero?
56
+ This.say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print)
57
+ This.say("SUCCESS", :color => :green, :bold => true)
58
+ else
59
+ This.say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print)
60
+ This.say("FAILURE", :color => :red, :bold => true)
61
+ end
62
+ This.say(line, :color => :cyan, :bold => true)
63
+
64
+ exit(status) unless status.zero?
65
+ end
66
+ end
67
+
68
+
69
+ task :gemspec do
70
+ ignore_extensions = ['git', 'svn', 'tmp', /sw./, 'bak', 'gem']
71
+ ignore_directories = ['pkg', 'db']
72
+ ignore_files = ['test/log', 'test/db.yml', 'a.rb', 'b.rb'] + Dir['db/*'] + %w'db'
73
+
74
+ shiteless =
75
+ lambda do |list|
76
+ list.delete_if do |entry|
77
+ next unless test(?e, entry)
78
+ extension = File.basename(entry).split(%r/[.]/).last
79
+ ignore_extensions.any?{|ext| ext === extension}
80
+ end
81
+ list.delete_if do |entry|
82
+ next unless test(?d, entry)
83
+ dirname = File.expand_path(entry)
84
+ ignore_directories.any?{|dir| File.expand_path(dir) == dirname}
85
+ end
86
+ list.delete_if do |entry|
87
+ next unless test(?f, entry)
88
+ filename = File.expand_path(entry)
89
+ ignore_files.any?{|file| File.expand_path(file) == filename}
90
+ end
91
+ end
92
+
93
+ lib = This.lib
94
+ object = This.object
95
+ version = This.version
96
+ files = shiteless[Dir::glob("**/**")]
97
+ executables = shiteless[Dir::glob("bin/*")].map{|exe| File.basename(exe)}
98
+ #has_rdoc = true #File.exist?('doc')
99
+ test_files = test(?e, "test/#{ lib }.rb") ? "test/#{ lib }.rb" : nil
100
+ summary = This.summary || This.synopsis || "#{ lib } kicks the ass"
101
+ description = This.description || summary
102
+
103
+ if This.extensions.nil?
104
+ This.extensions = []
105
+ extensions = This.extensions
106
+ %w( Makefile configure extconf.rb ).each do |ext|
107
+ extensions << ext if File.exists?(ext)
108
+ end
109
+ end
110
+ extensions = [extensions].flatten.compact
111
+
112
+ # TODO
113
+ if This.dependencies.nil?
114
+ dependencies = []
115
+ else
116
+ case This.dependencies
117
+ when Hash
118
+ dependencies = This.dependencies.values
119
+ when Array
120
+ dependencies = This.dependencies
121
+ end
122
+ end
123
+
124
+ template =
125
+ if test(?e, 'gemspec.erb')
126
+ This.template_for{ IO.read('gemspec.erb') }
127
+ else
128
+ This.template_for {
129
+ <<-__
130
+ ## <%= lib %>.gemspec
131
+ #
132
+
133
+ Gem::Specification::new do |spec|
134
+ spec.name = <%= lib.inspect %>
135
+ spec.version = <%= version.inspect %>
136
+ spec.platform = Gem::Platform::RUBY
137
+ spec.summary = <%= lib.inspect %>
138
+ spec.description = <%= description.inspect %>
139
+
140
+ spec.files =\n<%= files.sort.pretty_inspect %>
141
+ spec.executables = <%= executables.inspect %>
142
+
143
+ spec.require_path = "lib"
144
+
145
+ spec.test_files = <%= test_files.inspect %>
146
+
147
+ <% dependencies.each do |lib_version| %>
148
+ spec.add_dependency(*<%= Array(lib_version).flatten.inspect %>)
149
+ <% end %>
150
+
151
+ spec.extensions.push(*<%= extensions.inspect %>)
152
+
153
+ spec.rubyforge_project = <%= This.rubyforge_project.inspect %>
154
+ spec.author = <%= This.author.inspect %>
155
+ spec.email = <%= This.email.inspect %>
156
+ spec.homepage = <%= This.homepage.inspect %>
157
+ end
158
+ __
159
+ }
160
+ end
161
+
162
+ FileUtils.mkdir_p(This.pkgdir)
163
+ gemspec = "#{ lib }.gemspec"
164
+ open(gemspec, "w"){|fd| fd.puts(template)}
165
+ This.gemspec = gemspec
166
+ end
167
+
168
+ task :gem => [:clean, :gemspec] do
169
+ FileUtils.mkdir_p(This.pkgdir)
170
+ before = Dir['*.gem']
171
+ cmd = "gem build #{ This.gemspec }"
172
+ `#{ cmd }`
173
+ after = Dir['*.gem']
174
+ gem = ((after - before).first || after.first) or abort('no gem!')
175
+ FileUtils.mv(gem, This.pkgdir)
176
+ This.gem = File.join(This.pkgdir, File.basename(gem))
177
+ end
178
+
179
+ task :readme do
180
+ samples = ''
181
+ prompt = '~ > '
182
+ lib = This.lib
183
+ version = This.version
184
+
185
+ Dir['sample*/*'].sort.each do |sample|
186
+ samples << "\n" << " <========< #{ sample } >========>" << "\n\n"
187
+
188
+ cmd = "cat #{ sample }"
189
+ samples << This.util.indent(prompt + cmd, 2) << "\n\n"
190
+ samples << This.util.indent(`#{ cmd }`, 4) << "\n"
191
+
192
+ cmd = "ruby #{ sample }"
193
+ samples << This.util.indent(prompt + cmd, 2) << "\n\n"
194
+
195
+ cmd = "ruby -e'STDOUT.sync=true; exec %(ruby -I ./lib #{ sample })'"
196
+ samples << This.util.indent(`#{ cmd } 2>&1`, 4) << "\n"
197
+ end
198
+
199
+ template =
200
+ if test(?e, 'readme.erb')
201
+ This.template_for{ IO.read('readme.erb') }
202
+ else
203
+ This.template_for {
204
+ <<-__
205
+ NAME
206
+ #{ lib }
207
+
208
+ DESCRIPTION
209
+
210
+ INSTALL
211
+ gem install #{ lib }
212
+
213
+ SAMPLES
214
+ #{ samples }
215
+ __
216
+ }
217
+ end
218
+
219
+ open("README", "w"){|fd| fd.puts template}
220
+ end
221
+
222
+
223
+ task :clean do
224
+ Dir[File.join(This.pkgdir, '**/**')].each{|entry| FileUtils.rm_rf(entry)}
225
+ end
226
+
227
+
228
+ task :release => [:clean, :gemspec, :gem] do
229
+ gems = Dir[File.join(This.pkgdir, '*.gem')].flatten
230
+ raise "which one? : #{ gems.inspect }" if gems.size > 1
231
+ raise "no gems?" if gems.size < 1
232
+
233
+ cmd = "gem push #{ This.gem }"
234
+ puts cmd
235
+ puts
236
+ system(cmd)
237
+ abort("cmd(#{ cmd }) failed with (#{ $?.inspect })") unless $?.exitstatus.zero?
238
+
239
+ #cmd = "rubyforge login && rubyforge add_release #{ This.rubyforge_project } #{ This.lib } #{ This.version } #{ This.gem }"
240
+ #puts cmd
241
+ #puts
242
+ #system(cmd)
243
+ #abort("cmd(#{ cmd }) failed with (#{ $?.inspect })") unless $?.exitstatus.zero?
244
+ end
245
+
246
+
247
+
248
+
249
+
250
+ BEGIN {
251
+ # support for this rakefile
252
+ #
253
+ $VERBOSE = nil
254
+
255
+ require 'erb'
256
+ require 'fileutils'
257
+ require 'rbconfig'
258
+ require 'pp'
259
+
260
+ # cache a bunch of stuff about this rakefile/environment
261
+ #
262
+
263
+ This =
264
+ Class.new(Hash) do
265
+
266
+ def method_missing(method, *args, &block)
267
+ if method.to_s =~ /=/
268
+ key = method.to_s.chomp('=')
269
+ value = block ? block : args.shift
270
+ self[key] = value
271
+ else
272
+ key = method.to_s
273
+ if block
274
+ value = block
275
+ self[key] = value
276
+ else
277
+ value = self[key]
278
+
279
+ if value.respond_to?(:call)
280
+ self[key] = value.call()
281
+ else
282
+ value
283
+ end
284
+ end
285
+ end
286
+ end
287
+
288
+ def inspect
289
+ expand!
290
+ PP.pp(self, '')
291
+ end
292
+
293
+ def expand!
294
+ keys.each do |key|
295
+ value = self[key]
296
+ if value.respond_to?(:call)
297
+ self[key] = value.call()
298
+ end
299
+ end
300
+ end
301
+
302
+ end.new()
303
+
304
+ This.file = File.expand_path(__FILE__)
305
+ This.dir = File.dirname(This.file)
306
+ This.pkgdir = File.join(This.dir, 'pkg')
307
+
308
+ # defaults
309
+ #
310
+ This.lib do
311
+ File.basename(Dir.pwd)
312
+ end
313
+
314
+ def This.setup!
315
+ begin
316
+ require "./lib/#{ This.lib }"
317
+ rescue LoadError
318
+ abort("could not load #{ This.lib }")
319
+ end
320
+ end
321
+
322
+ This.name do
323
+ This.name = This.lib.capitalize
324
+ end
325
+
326
+ This.object do
327
+ begin
328
+ This.object = eval(This.name)
329
+ rescue Object
330
+ abort("could not determine object from #{ This.name }")
331
+ end
332
+ end
333
+
334
+ This.version do
335
+ This.object.send(:version)
336
+ end
337
+
338
+ This.dependencies do
339
+ if This.object.respond_to?(:dependencies)
340
+ This.object.dependencies
341
+ end
342
+ end
343
+
344
+ This.ruby do
345
+ c = Config::CONFIG
346
+ bindir = c["bindir"] || c['BINDIR']
347
+ ruby_install_name = c['ruby_install_name'] || c['RUBY_INSTALL_NAME'] || 'ruby'
348
+ ruby_ext = c['EXEEXT'] || ''
349
+ File.join(bindir, (ruby_install_name + ruby_ext))
350
+ end
351
+
352
+ # some utils
353
+ #
354
+ This.util = Module.new do
355
+ def indent(s, n = 2)
356
+ s = unindent(s)
357
+ ws = ' ' * n
358
+ s.gsub(%r/^/, ws)
359
+ end
360
+
361
+ def unindent(s)
362
+ indent = nil
363
+ s.each_line do |line|
364
+ next if line =~ %r/^\s*$/
365
+ indent = line[%r/^\s*/] and break
366
+ end
367
+ indent ? s.gsub(%r/^#{ indent }/, "") : s
368
+ end
369
+
370
+ extend self
371
+ end
372
+
373
+ # template support
374
+ #
375
+ This.template = Class.new do
376
+ def initialize(&block)
377
+ @block = block
378
+ @template = block.call.to_s
379
+ end
380
+
381
+ def expand(b=nil)
382
+ ERB.new(This.util.unindent(@template)).result((b||@block).binding)
383
+ end
384
+
385
+ alias_method 'to_s', 'expand'
386
+ end
387
+
388
+ def This.template_for(*args, &block)
389
+ This.template.new(*args, &block)
390
+ end
391
+
392
+ # colored console output support
393
+ #
394
+ This.ansi = {
395
+ :clear => "\e[0m",
396
+ :reset => "\e[0m",
397
+ :erase_line => "\e[K",
398
+ :erase_char => "\e[P",
399
+ :bold => "\e[1m",
400
+ :dark => "\e[2m",
401
+ :underline => "\e[4m",
402
+ :underscore => "\e[4m",
403
+ :blink => "\e[5m",
404
+ :reverse => "\e[7m",
405
+ :concealed => "\e[8m",
406
+ :black => "\e[30m",
407
+ :red => "\e[31m",
408
+ :green => "\e[32m",
409
+ :yellow => "\e[33m",
410
+ :blue => "\e[34m",
411
+ :magenta => "\e[35m",
412
+ :cyan => "\e[36m",
413
+ :white => "\e[37m",
414
+ :on_black => "\e[40m",
415
+ :on_red => "\e[41m",
416
+ :on_green => "\e[42m",
417
+ :on_yellow => "\e[43m",
418
+ :on_blue => "\e[44m",
419
+ :on_magenta => "\e[45m",
420
+ :on_cyan => "\e[46m",
421
+ :on_white => "\e[47m"
422
+ }
423
+
424
+ def This.say(something, *args)
425
+ options = args.last.is_a?(Hash) ? args.pop : {}
426
+ options[:color] = args.shift.to_s.to_sym unless args.empty?
427
+ keys = options.keys
428
+ keys.each{|key| options[key.to_s.to_sym] = options.delete(key)}
429
+
430
+ color = options[:color]
431
+ bold = options.has_key?(:bold)
432
+
433
+ parts = [something]
434
+ parts.unshift(This.ansi[color]) if color
435
+ parts.unshift(This.ansi[:bold]) if bold
436
+ parts.push(This.ansi[:clear]) if parts.size > 1
437
+
438
+ method = options[:method] || :puts
439
+
440
+ Kernel.send(method, parts.join)
441
+ end
442
+
443
+ # always run out of the project dir
444
+ #
445
+ Dir.chdir(This.dir)
446
+ }
@@ -0,0 +1,349 @@
1
+ # encoding: utf-8
2
+
3
+ ##
4
+ #
5
+ module Mongoid
6
+ class Bolt
7
+ const_set :Version, '1.0.0'
8
+
9
+ class << Bolt
10
+ def version
11
+ const_get :Version
12
+ end
13
+
14
+ def dependencies
15
+ {
16
+ 'mongoid' => [ 'mongoid' , ' >= 3.0.1' ] ,
17
+ }
18
+ end
19
+
20
+ def libdir(*args, &block)
21
+ @libdir ||= File.expand_path(__FILE__).sub(/\.rb$/,'')
22
+ args.empty? ? @libdir : File.join(@libdir, *args)
23
+ ensure
24
+ if block
25
+ begin
26
+ $LOAD_PATH.unshift(@libdir)
27
+ block.call()
28
+ ensure
29
+ $LOAD_PATH.shift()
30
+ end
31
+ end
32
+ end
33
+
34
+ def load(*libs)
35
+ libs = libs.join(' ').scan(/[^\s+]+/)
36
+ libdir{ libs.each{|lib| Kernel.load(lib) } }
37
+ end
38
+ end
39
+
40
+ begin
41
+ require 'rubygems'
42
+ rescue LoadError
43
+ nil
44
+ end
45
+
46
+ if defined?(gem)
47
+ dependencies.each do |lib, dependency|
48
+ gem(*dependency)
49
+ require(lib)
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ ##
56
+ #
57
+ module Mongoid
58
+ class Bolt
59
+ module Ability
60
+ Code = proc do
61
+ ## embedded lock class and associations
62
+ #
63
+ target_class = self
64
+
65
+ class << target_class
66
+ attr_accessor :lock_class
67
+ end
68
+
69
+ const_set(:Bolt, Class.new)
70
+ lock_class = const_get(:Bolt)
71
+
72
+ target_class.lock_class = lock_class
73
+
74
+ lock_class.class_eval do
75
+ define_method(:target_class){ target_class }
76
+ define_method(:lock_class){ lock_class }
77
+
78
+ include Mongoid::Document
79
+ include Mongoid::Timestamps
80
+
81
+ field(:hostname, :default => proc{ ::Mongoid::Bolt.hostname })
82
+ field(:ppid, :default => proc{ ::Mongoid::Bolt.ppid })
83
+ field(:pid, :default => proc{ ::Mongoid::Bolt.pid })
84
+
85
+ attr_accessor :stolen
86
+ alias_method :stolen?, :stolen
87
+
88
+ def initialize(*args, &block)
89
+ super
90
+ ensure
91
+ now = Time.now
92
+ self.created_at ||= now
93
+ self.updated_at ||= now
94
+ @locked = false
95
+ end
96
+
97
+ def localhost?
98
+ ::Mongoid::Bolt.hostname == hostname
99
+ end
100
+
101
+ def alive?
102
+ return true unless localhost?
103
+ ::Mongoid::Bolt.alive?(ppid, pid)
104
+ end
105
+
106
+ def relock!
107
+ reload
108
+
109
+ conditions = {
110
+ '_lock._id' => id,
111
+ '_lock.hostname' => hostname,
112
+ '_lock.ppid' => ppid,
113
+ '_lock.pid' => pid
114
+ }
115
+
116
+ update = {
117
+ '$set' => {
118
+ '_lock.hostname' => ::Mongoid::Bolt.hostname,
119
+ '_lock.ppid' => ::Mongoid::Bolt.ppid,
120
+ '_lock.pid' => ::Mongoid::Bolt.pid,
121
+ '_lock.updated_at' => Time.now.utc
122
+ }
123
+ }
124
+
125
+ result =
126
+ target_class.
127
+ with(safe: true).
128
+ where(conditions).
129
+ find_and_modify(update, new: false)
130
+
131
+ ensure
132
+ reload
133
+ end
134
+
135
+ def steal!
136
+ self.stolen = !!relock!
137
+ end
138
+
139
+ def stale?
140
+ localhost? and not alive?
141
+ end
142
+
143
+ def owner?
144
+ ::Mongoid::Bolt.identifier == identifier
145
+ end
146
+
147
+ def identifier
148
+ {:hostname => hostname, :ppid => ppid, :pid => pid}
149
+ end
150
+ end
151
+
152
+ target_association_name = "_" + target_class.name.underscore.split(%r{/}).last
153
+
154
+ lock_class.class_eval do
155
+ embedded_in(target_association_name, :class_name => "::#{ target_class.name }")
156
+ end
157
+
158
+ embeds_one(:_lock, :class_name => "::#{ lock_class.name }")
159
+
160
+ ## locking methods
161
+ #
162
+ def target_class.lock!(conditions = {}, update = {})
163
+ conditions.to_options!
164
+ update.to_options!
165
+
166
+ conditions[:_lock] = nil
167
+
168
+ update[:$set] = {:_lock => lock_class.new.attributes}
169
+
170
+ with(safe: true).
171
+ where(conditions).
172
+ find_and_modify(update, new: true)
173
+ end
174
+
175
+ def lock!(conditions = {})
176
+ conditions.to_options!
177
+
178
+ begin
179
+ if _lock and _lock.stale? and _lock.steal!
180
+ return _lock
181
+ end
182
+ rescue
183
+ nil
184
+ end
185
+
186
+ conditions[:_id] = id
187
+
188
+ if self.class.lock!(conditions)
189
+ reload
190
+
191
+ begin
192
+ @locked = _lock && _lock.owner?
193
+ rescue
194
+ nil
195
+ end
196
+ else
197
+ false
198
+ end
199
+ end
200
+
201
+ def unlock!
202
+ unlocked = false
203
+
204
+ if _lock
205
+ begin
206
+ _lock.destroy if _lock.owner?
207
+ @locked = false
208
+ unlocked = true
209
+ rescue
210
+ nil
211
+ end
212
+
213
+ reload
214
+ end
215
+
216
+ unlocked
217
+ end
218
+
219
+ def relock!
220
+ raise(::Mongoid::Bolt::Error, "#{ name } is not locked!") unless @locked
221
+
222
+ _lock.relock!
223
+ end
224
+
225
+ def locked?
226
+ begin
227
+ _lock and _lock.owner?
228
+ rescue
229
+ nil
230
+ end
231
+ end
232
+
233
+ def lock(options = {}, &block)
234
+ options.to_options!
235
+
236
+ return block.call(_lock) if locked?
237
+
238
+ loop do
239
+ if lock!
240
+ return _lock unless block
241
+
242
+ begin
243
+ return block.call(_lock)
244
+ ensure
245
+ unlock!
246
+ end
247
+ else
248
+ if options[:blocking] == false
249
+ if block
250
+ raise(::Mongoid::Bolt::Error, name)
251
+ else
252
+ return(false)
253
+ end
254
+ end
255
+
256
+ if options[:waiting]
257
+ options[:waiting].call(reload._lock)
258
+ end
259
+
260
+ sleep(rand)
261
+ end
262
+ end
263
+ end
264
+ end
265
+
266
+ def Ability.included(other)
267
+ super
268
+ ensure
269
+ other.module_eval(&Code)
270
+ end
271
+ end
272
+
273
+ def Bolt.ability
274
+ Ability
275
+ end
276
+
277
+ ##
278
+ #
279
+ include Mongoid::Document
280
+ include Mongoid::Timestamps
281
+
282
+ ##
283
+ #
284
+ class Error < ::StandardError; end
285
+
286
+ ##
287
+ #
288
+ def Bolt.for(name, options = {}, &block)
289
+ name = name.to_s
290
+ conditions = {:name => name}
291
+
292
+ attributes = conditions.dup
293
+ attributes[:created_at] || attributes[:updated_at] = Time.now.utc
294
+
295
+ lock =
296
+ begin
297
+ where(conditions).first or create!(attributes)
298
+ rescue Object => e
299
+ sleep(rand)
300
+ where(conditions).first or create!(attributes)
301
+ end
302
+
303
+ block ? lock.lock(options, &block) : lock
304
+ end
305
+
306
+ ##
307
+ #
308
+ field(:name)
309
+
310
+ validates_presence_of(:name)
311
+ validates_uniqueness_of(:name)
312
+
313
+ index({:name => 1}, {:unique => true})
314
+
315
+ ##
316
+ #
317
+ def Bolt.hostname
318
+ Socket.gethostname
319
+ end
320
+
321
+ def Bolt.ppid
322
+ Process.ppid
323
+ end
324
+
325
+ def Bolt.pid
326
+ Process.pid
327
+ end
328
+
329
+ def Bolt.identifier
330
+ {:hostname => hostname, :ppid => ppid, :pid => pid}
331
+ end
332
+
333
+ def Bolt.alive?(*pids)
334
+ pids.flatten.compact.all? do |pid|
335
+ begin
336
+ Process.kill(0, Integer(pid))
337
+ true
338
+ rescue Object
339
+ false
340
+ end
341
+ end
342
+ end
343
+
344
+ ##
345
+ #
346
+ include Bolt.ability
347
+ end
348
+ end
349
+
@@ -0,0 +1,38 @@
1
+ ## mongoid-bolt.gemspec
2
+ #
3
+
4
+ Gem::Specification::new do |spec|
5
+ spec.name = "mongoid-bolt"
6
+ spec.version = "1.0.0"
7
+ spec.platform = Gem::Platform::RUBY
8
+ spec.summary = "mongoid-bolt"
9
+ spec.description = "a mongoid 3/moped compatible lock implementation and mixin"
10
+
11
+ spec.files =
12
+ ["README.md",
13
+ "Rakefile",
14
+ "lib",
15
+ "lib/mongoid-bolt.rb",
16
+ "mongoid-bolt.gemspec",
17
+ "test",
18
+ "test/helper.rb",
19
+ "test/mongoid-bolt_test.rb",
20
+ "test/testing.rb"]
21
+
22
+ spec.executables = []
23
+
24
+ spec.require_path = "lib"
25
+
26
+ spec.test_files = nil
27
+
28
+
29
+ spec.add_dependency(*["mongoid", " >= 3.0.1"])
30
+
31
+
32
+ spec.extensions.push(*[])
33
+
34
+ spec.rubyforge_project = "codeforpeople"
35
+ spec.author = "Ara T. Howard"
36
+ spec.email = "ara.t.howard@gmail.com"
37
+ spec.homepage = "https://github.com/ahoward/mongoid-bolt"
38
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,7 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require_relative 'testing'
3
+ require_relative '../lib/mongoid-bolt.rb'
4
+
5
+ Mongoid.configure do |config|
6
+ config.connect_to('mongoid-bolt_test')
7
+ end
@@ -0,0 +1,70 @@
1
+ require_relative 'helper'
2
+
3
+ Testing Mongoid::Bolt do
4
+ ##
5
+ #
6
+ Bolt = Mongoid::Bolt
7
+
8
+ class A
9
+ include Mongoid::Document
10
+ include Bolt::Ability
11
+ end
12
+
13
+ setup do
14
+ Bolt.destroy_all
15
+ A.destroy_all
16
+ end
17
+
18
+ test 'that locks can be acquired by name' do
19
+ lock = assert{ Bolt.for(:shared_resource) }
20
+ end
21
+
22
+ test 'that locks can be locked' do
23
+ lock = assert{ Bolt.for(:shared_resource) }
24
+ assert{ lock.lock! }
25
+ end
26
+
27
+ test 'that locks cannot be locked twice' do
28
+ lock = assert{ Bolt.for(:shared_resource) }
29
+ assert{ lock.lock! }
30
+ assert{ !lock.lock! }
31
+ end
32
+
33
+ test 'that locks can be re-locked' do
34
+ lock = assert{ Bolt.for(:shared_resource) }
35
+ assert{ lock.lock! }
36
+ a = lock._lock.updated_at
37
+ sleep(0.042)
38
+ assert{ lock.relock! }
39
+ b = lock._lock.updated_at
40
+ assert{ b > a }
41
+ end
42
+
43
+ test 'that locks can be un-locked' do
44
+ lock = assert{ Bolt.for(:shared_resource) }
45
+ assert{ lock.lock! }
46
+ assert{ lock.unlock! }
47
+ end
48
+
49
+ test 'that locks know when they are locked' do
50
+ lock = assert{ Bolt.for(:shared_resource) }
51
+ assert{ !lock.locked? }
52
+ assert{ lock.lock! }
53
+ assert{ lock.locked? }
54
+ end
55
+
56
+ test 'that #lock takes a block' do
57
+ lock = assert{ Bolt.for(:shared_resource) }
58
+ assert do
59
+ assert{ !lock.locked? }
60
+ lock.lock{ assert{ lock.locked? } }
61
+ assert{ !lock.locked? }
62
+ end
63
+ end
64
+
65
+ test 'that other classes can mix-in lockability' do
66
+ locked = false
67
+ assert{ A.create.lock{ locked = true } }
68
+ assert{ locked }
69
+ end
70
+ end
data/test/testing.rb ADDED
@@ -0,0 +1,196 @@
1
+ # -*- encoding : utf-8 -*-
2
+ require 'test/unit'
3
+
4
+ testdir = File.expand_path(File.dirname(__FILE__))
5
+ rootdir = File.dirname(testdir)
6
+ libdir = File.join(rootdir, 'lib')
7
+
8
+ STDOUT.sync = true
9
+
10
+ $:.unshift(testdir) unless $:.include?(testdir)
11
+ $:.unshift(libdir) unless $:.include?(libdir)
12
+ $:.unshift(rootdir) unless $:.include?(rootdir)
13
+
14
+ class Testing
15
+ class Slug < ::String
16
+ def Slug.for(*args)
17
+ string = args.flatten.compact.join('-')
18
+ words = string.to_s.scan(%r/\w+/)
19
+ words.map!{|word| word.gsub %r/[^0-9a-zA-Z_-]/, ''}
20
+ words.delete_if{|word| word.nil? or word.strip.empty?}
21
+ new(words.join('-').downcase)
22
+ end
23
+ end
24
+
25
+ class Context
26
+ attr_accessor :name
27
+
28
+ def initialize(name, *args)
29
+ @name = name
30
+ end
31
+
32
+ def to_s
33
+ Slug.for(name)
34
+ end
35
+ end
36
+ end
37
+
38
+ def Testing(*args, &block)
39
+ Class.new(::Test::Unit::TestCase) do
40
+
41
+ ## class methods
42
+ #
43
+ class << self
44
+ def contexts
45
+ @contexts ||= []
46
+ end
47
+
48
+ def context(*args, &block)
49
+ return contexts.last if(args.empty? and block.nil?)
50
+
51
+ context = Testing::Context.new(*args)
52
+ contexts.push(context)
53
+
54
+ begin
55
+ block.call(context)
56
+ ensure
57
+ contexts.pop
58
+ end
59
+ end
60
+
61
+ def slug_for(*args)
62
+ string = [context, args].flatten.compact.join('-')
63
+ words = string.to_s.scan(%r/\w+/)
64
+ words.map!{|word| word.gsub %r/[^0-9a-zA-Z_-]/, ''}
65
+ words.delete_if{|word| word.nil? or word.strip.empty?}
66
+ words.join('-').downcase.sub(/_$/, '')
67
+ end
68
+
69
+ def name() const_get(:Name) end
70
+
71
+ def testno()
72
+ '%05d' % (@testno ||= 0)
73
+ ensure
74
+ @testno += 1
75
+ end
76
+
77
+ def testing(*args, &block)
78
+ method = ["test", testno, slug_for(*args)].delete_if{|part| part.empty?}.join('_')
79
+ define_method(method, &block)
80
+ end
81
+
82
+ def test(*args, &block)
83
+ testing(*args, &block)
84
+ end
85
+
86
+ def setup(&block)
87
+ define_method(:setup, &block) if block
88
+ end
89
+
90
+ def teardown(&block)
91
+ define_method(:teardown, &block) if block
92
+ end
93
+
94
+ def prepare(&block)
95
+ @prepare ||= []
96
+ @prepare.push(block) if block
97
+ @prepare
98
+ end
99
+
100
+ def cleanup(&block)
101
+ @cleanup ||= []
102
+ @cleanup.push(block) if block
103
+ @cleanup
104
+ end
105
+ end
106
+
107
+ ## configure the subclass!
108
+ #
109
+ const_set(:Testno, '0')
110
+ slug = slug_for(*args).gsub(%r/-/,'_')
111
+ name = ['TESTING', '%03d' % const_get(:Testno), slug].delete_if{|part| part.empty?}.join('_')
112
+ name = name.upcase!
113
+ const_set(:Name, name)
114
+ const_set(:Missing, Object.new.freeze)
115
+
116
+ ## instance methods
117
+ #
118
+ alias_method('__assert__', 'assert')
119
+
120
+ def assert(*args, &block)
121
+ if args.size == 1 and args.first.is_a?(Hash)
122
+ options = args.first
123
+ expected = getopt(:expected, options){ missing }
124
+ actual = getopt(:actual, options){ missing }
125
+ if expected == missing and actual == missing
126
+ actual, expected, *ignored = options.to_a.flatten
127
+ end
128
+ expected = expected.call() if expected.respond_to?(:call)
129
+ actual = actual.call() if actual.respond_to?(:call)
130
+ assert_equal(expected, actual)
131
+ end
132
+
133
+ if block
134
+ label = "assert(#{ args.join(' ') })"
135
+ result = nil
136
+ assert_nothing_raised{ result = block.call }
137
+ __assert__(result, label)
138
+ result
139
+ else
140
+ result = args.shift
141
+ label = "assert(#{ args.join(' ') })"
142
+ __assert__(result, label)
143
+ result
144
+ end
145
+ end
146
+
147
+ def missing
148
+ self.class.const_get(:Missing)
149
+ end
150
+
151
+ def getopt(opt, hash, options = nil, &block)
152
+ [opt.to_s, opt.to_s.to_sym].each do |key|
153
+ return hash[key] if hash.has_key?(key)
154
+ end
155
+ default =
156
+ if block
157
+ block.call
158
+ else
159
+ options.is_a?(Hash) ? options[:default] : nil
160
+ end
161
+ return default
162
+ end
163
+
164
+ def subclass_of exception
165
+ class << exception
166
+ def ==(other) super or self > other end
167
+ end
168
+ exception
169
+ end
170
+
171
+ ##
172
+ #
173
+ module_eval(&block)
174
+
175
+ self.setup()
176
+ self.prepare.each{|b| b.call()}
177
+
178
+ at_exit{
179
+ self.teardown()
180
+ self.cleanup.each{|b| b.call()}
181
+ }
182
+
183
+ self
184
+ end
185
+ end
186
+
187
+
188
+ if $0 == __FILE__
189
+
190
+ Testing 'Testing' do
191
+ testing('foo'){ assert true }
192
+ test{ assert true }
193
+ p instance_methods.grep(/test/)
194
+ end
195
+
196
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mongoid-bolt
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ara T. Howard
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-07-24 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: mongoid
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 3.0.1
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 3.0.1
30
+ description: a mongoid 3/moped compatible lock implementation and mixin
31
+ email: ara.t.howard@gmail.com
32
+ executables: []
33
+ extensions: []
34
+ extra_rdoc_files: []
35
+ files:
36
+ - README.md
37
+ - Rakefile
38
+ - lib/mongoid-bolt.rb
39
+ - mongoid-bolt.gemspec
40
+ - test/helper.rb
41
+ - test/mongoid-bolt_test.rb
42
+ - test/testing.rb
43
+ homepage: https://github.com/ahoward/mongoid-bolt
44
+ licenses: []
45
+ post_install_message:
46
+ rdoc_options: []
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ! '>='
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ requirements: []
62
+ rubyforge_project: codeforpeople
63
+ rubygems_version: 1.8.24
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: mongoid-bolt
67
+ test_files: []