beanstalker 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.
- data/.gitignore +1 -0
- data/COPYING +674 -0
- data/README +27 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/bin/worker +70 -0
- data/init.rb +1 -0
- data/lib/async_observer/daemonize.rb +35 -0
- data/lib/async_observer/extend.rb +140 -0
- data/lib/async_observer/queue.rb +182 -0
- data/lib/async_observer/util.rb +31 -0
- data/lib/async_observer/worker.rb +263 -0
- metadata +91 -0
data/README
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
This is Async Observer -- a Rails plugin that provides deep integration with
|
2
|
+
Beanstalk.
|
3
|
+
|
4
|
+
For more information, see http://async-observer.rubyforge.org/.
|
5
|
+
|
6
|
+
For more information on Beanstalk, see its home page at
|
7
|
+
http://xph.us/software/beanstalkd/.
|
8
|
+
|
9
|
+
|
10
|
+
Worker Options:
|
11
|
+
-d : daemonize
|
12
|
+
--pid [path to pidfile] : drop a pid file to a path
|
13
|
+
-e [test,production,development] : set the rails environment
|
14
|
+
|
15
|
+
Example Usage:
|
16
|
+
|
17
|
+
start 3 workers
|
18
|
+
./vendor/plugins/async_observer/bin/worker -d --pid log/worker1.pid -e production
|
19
|
+
./vendor/plugins/async_observer/bin/worker -d --pid log/worker2.pid -e production
|
20
|
+
./vendor/plugins/async_observer/bin/worker -d --pid log/worker3.pid -e production
|
21
|
+
|
22
|
+
kill one
|
23
|
+
kill -s INT `cat log/worker1.pid`
|
24
|
+
|
25
|
+
Remember kill a worker will cause it to go into a shutdown phase.
|
26
|
+
Run the above again to kill immediately, but remember all jobs in
|
27
|
+
the workers queue is lost at that point...
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "beanstalker"
|
8
|
+
gem.summary = %Q{Beanstalker provides deep integration with Beanstalk. Fork from http://github.com/kristjan/async_observer}
|
9
|
+
gem.description = %Q{Beanstalker provides deep integration with Beanstalk. Fork from http://github.com/kristjan/async_observer}
|
10
|
+
gem.email = "glebpom@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/glebpom/beanstalker"
|
12
|
+
gem.authors = ["Gleb Pomykalov"]
|
13
|
+
# gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
14
|
+
gem.add_dependency "daemonizer"
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'rake/testtask'
|
22
|
+
Rake::TestTask.new(:test) do |test|
|
23
|
+
test.libs << 'lib' << 'test'
|
24
|
+
test.pattern = 'test/**/test_*.rb'
|
25
|
+
test.verbose = true
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
require 'rcov/rcovtask'
|
30
|
+
Rcov::RcovTask.new do |test|
|
31
|
+
test.libs << 'test'
|
32
|
+
test.pattern = 'test/**/test_*.rb'
|
33
|
+
test.verbose = true
|
34
|
+
end
|
35
|
+
rescue LoadError
|
36
|
+
task :rcov do
|
37
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
task :test => :check_dependencies
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
require 'rake/rdoctask'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "beanstalker #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/bin/worker
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# async-observer - Rails plugin for asynchronous job execution
|
3
|
+
|
4
|
+
# Copyright (C) 2007 Philotic Inc.
|
5
|
+
|
6
|
+
# This program is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
|
20
|
+
# Use the same pointer (and therefore same buffer) for stdout and stderr.
|
21
|
+
$VERBOSE = nil; STDERR = $stderr = STDOUT = $stdout; $VERBOSE = false
|
22
|
+
|
23
|
+
require 'time'
|
24
|
+
|
25
|
+
def load_rails_and_run
|
26
|
+
# Rails initialization.
|
27
|
+
# We do this here instead of using script/runner because script/runner
|
28
|
+
# breaks __FILE__, which we use below.
|
29
|
+
begin
|
30
|
+
puts "#!load-rails!begin!#{Time.now.utc.xmlschema(6)}"
|
31
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../../../config/boot')
|
32
|
+
puts "RAILS_ROOT=#{RAILS_ROOT.inspect}"
|
33
|
+
require RAILS_ROOT + '/config/environment'
|
34
|
+
ensure
|
35
|
+
puts "#!load-rails!end!#{Time.now.utc.xmlschema(6)}"
|
36
|
+
end
|
37
|
+
require 'async_observer/worker'
|
38
|
+
AsyncObserver::Worker.new(binding).run()
|
39
|
+
end
|
40
|
+
|
41
|
+
# set environment
|
42
|
+
if ARGV.include?('-e') # check rails env
|
43
|
+
env=nil
|
44
|
+
ARGV.each_with_index{|arg,i| env = ARGV[i+1] if arg == '-e' }
|
45
|
+
RAILS_ENV=env unless env.nil?
|
46
|
+
end
|
47
|
+
|
48
|
+
if ARGV.include?('-d')
|
49
|
+
pidpath = 'log/worker.pid'
|
50
|
+
if ARGV.include?('--pid') # look up the pid path
|
51
|
+
ARGV.each_with_index{|arg,i| pidpath = ARGV[i+1] if arg == '--pid' }
|
52
|
+
STDERR.puts "Missing pid file path!" and exit(1) if pidpath.nil?
|
53
|
+
end
|
54
|
+
unless File.writable?(File.dirname(pidpath))
|
55
|
+
STDERR.puts "#{pidpath} not writable!"
|
56
|
+
exit(1)
|
57
|
+
end
|
58
|
+
|
59
|
+
if File.exist?(pidpath)
|
60
|
+
STDERR.puts "#{pidpath} exits! Make sure the worker isn't still running and try again after rm #{pidpath}"
|
61
|
+
exit(1)
|
62
|
+
end
|
63
|
+
|
64
|
+
require File.dirname(__FILE__) + '/../lib/async_observer/daemonize'
|
65
|
+
AsyncObserver::Daemonize.detach(pidpath) do
|
66
|
+
load_rails_and_run
|
67
|
+
end
|
68
|
+
else
|
69
|
+
load_rails_and_run
|
70
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'async_observer/extend'
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# async-observer - Rails plugin for asynchronous job execution
|
2
|
+
# Copyright (C) 2009 Todd A. Fisher.
|
3
|
+
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
|
+
module AsyncObserver; end
|
17
|
+
|
18
|
+
class AsyncObserver::Daemonize
|
19
|
+
def self.detach(pidfile='log/worker.pid',&block)
|
20
|
+
# daemonize, create a pipe to send status to the parent process, after the child has successfully started or failed
|
21
|
+
fork do
|
22
|
+
Process.setsid
|
23
|
+
fork do
|
24
|
+
Process.setsid
|
25
|
+
File.open(pidfile, 'wb') {|f| f << Process.pid}
|
26
|
+
at_exit { File.unlink(pidfile) }
|
27
|
+
File.umask 0000
|
28
|
+
STDIN.reopen "/dev/null"
|
29
|
+
STDOUT.reopen "/dev/null", "a"
|
30
|
+
STDERR.reopen STDOUT
|
31
|
+
block.call
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# async-observer - Rails plugin for asynchronous job execution
|
2
|
+
|
3
|
+
# Copyright (C) 2007 Philotic Inc.
|
4
|
+
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
|
15
|
+
# You should have received a copy of the GNU General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
|
18
|
+
require 'async_observer/queue'
|
19
|
+
|
20
|
+
CLASSES_TO_EXTEND = [
|
21
|
+
ActiveRecord::Base,
|
22
|
+
Array,
|
23
|
+
Hash,
|
24
|
+
Module,
|
25
|
+
Numeric,
|
26
|
+
Range,
|
27
|
+
String,
|
28
|
+
Symbol,
|
29
|
+
]
|
30
|
+
|
31
|
+
module AsyncObserver::Extensions
|
32
|
+
def async_send(selector, *args)
|
33
|
+
async_send_opts(selector, {}, *args)
|
34
|
+
end
|
35
|
+
|
36
|
+
def async_send_opts(selector, opts, *args)
|
37
|
+
AsyncObserver::Queue.put_call!(self, selector, opts, args)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
CLASSES_TO_EXTEND.each do |c|
|
42
|
+
c.send :include, AsyncObserver::Extensions
|
43
|
+
end
|
44
|
+
|
45
|
+
class Range
|
46
|
+
DEFAULT_FANOUT_FUZZ = 0
|
47
|
+
DEFAULT_FANOUT_DEGREE = 1000
|
48
|
+
|
49
|
+
def split_to(n)
|
50
|
+
split_by((size + n - 1) / n) { |group| yield(group) }
|
51
|
+
end
|
52
|
+
|
53
|
+
def split_by(n)
|
54
|
+
raise ArgumentError.new('invalid slice size') if n < 1
|
55
|
+
n -= 1 if !exclude_end?
|
56
|
+
i = first
|
57
|
+
while member?(i)
|
58
|
+
j = [i + n, last].min
|
59
|
+
yield(Range.new(i, j, exclude_end?))
|
60
|
+
i = j + (exclude_end? ? 0 : 1)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def size
|
65
|
+
last - first + (exclude_end? ? 0 : 1)
|
66
|
+
end
|
67
|
+
|
68
|
+
def async_each_opts(rcv, selector, opts, *extra)
|
69
|
+
fanout_degree = opts.fetch(:fanout_degree, DEFAULT_FANOUT_DEGREE)
|
70
|
+
if size <= fanout_degree
|
71
|
+
each {|i| rcv.async_send_opts(selector, opts, i, *extra)}
|
72
|
+
else
|
73
|
+
fanout_opts = opts.merge(:fuzz => opts.fetch(:fanout_fuzz,
|
74
|
+
DEFAULT_FANOUT_FUZZ))
|
75
|
+
fanout_opts[:pri] = opts[:fanout_pri] || opts[:pri]
|
76
|
+
fanout_opts = fanout_opts.reject_hash{|k,v| nil.equal?(v)}
|
77
|
+
split_to(fanout_degree) do |subrange|
|
78
|
+
subrange.async_send_opts(:async_each_opts, fanout_opts, rcv, selector,
|
79
|
+
opts, *extra)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def async_each(rcv, selector, *extra)
|
85
|
+
async_each_opts(rcv, selector, {}, *extra)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
HOOKS = [:after_create, :after_update, :after_save]
|
90
|
+
|
91
|
+
class << ActiveRecord::Base
|
92
|
+
HOOKS.each do |hook|
|
93
|
+
code = %Q{def async_#{hook}(*methods, &b) add_async_hook(#{hook.inspect}, *methods, &b) end}
|
94
|
+
class_eval(code, __FILE__, __LINE__ - 1)
|
95
|
+
end
|
96
|
+
|
97
|
+
def add_async_hook(hook, *args, &block)
|
98
|
+
if args && args.first.is_a?(Symbol)
|
99
|
+
method = args.shift
|
100
|
+
async_hooks[hook] << lambda{|o| o.send(method)}
|
101
|
+
else
|
102
|
+
async_hooks[hook] << block
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def async_hooks
|
107
|
+
@async_hooks ||= Hash.new do |hash, hook|
|
108
|
+
ahook = :"_async_#{hook}"
|
109
|
+
|
110
|
+
# This is for the producer's benefit
|
111
|
+
send(hook){|o| async_send(ahook, o)}
|
112
|
+
|
113
|
+
# This is for the worker's benefit
|
114
|
+
code = "def #{ahook}(o) run_async_hooks(#{hook.inspect}, o) end"
|
115
|
+
instance_eval(code, __FILE__, __LINE__ - 1)
|
116
|
+
|
117
|
+
hash[hook] = []
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def run_async_hooks(hook, o)
|
122
|
+
async_hooks[hook].each{|b| b.call(o)}
|
123
|
+
end
|
124
|
+
|
125
|
+
def send_to_instance(id, selector, *args)
|
126
|
+
x = find_by_id(id)
|
127
|
+
x.send(selector, *args) if x
|
128
|
+
end
|
129
|
+
|
130
|
+
def async_each_opts(selector, opts, *args)
|
131
|
+
min = opts.fetch(:min, minimum(:id))
|
132
|
+
max = opts.fetch(:max, maximum(:id))
|
133
|
+
|
134
|
+
(min..max).async_each_opts(self, :send_to_instance, opts, selector, *args)
|
135
|
+
end
|
136
|
+
|
137
|
+
def async_each(selector, *args)
|
138
|
+
async_each_opts(selector, {}, *args)
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
# async-observer - Rails plugin for asynchronous job execution
|
2
|
+
|
3
|
+
# Copyright (C) 2007 Philotic Inc.
|
4
|
+
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
|
15
|
+
# You should have received a copy of the GNU General Public License
|
16
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
|
18
|
+
|
19
|
+
require 'beanstalk-client'
|
20
|
+
|
21
|
+
module AsyncObserver; end
|
22
|
+
|
23
|
+
class AsyncObserver::Queue; end
|
24
|
+
|
25
|
+
class << AsyncObserver::Queue
|
26
|
+
DEFAULT_PRI = 512
|
27
|
+
DEFAULT_FUZZ = 0
|
28
|
+
DEFAULT_DELAY = 0
|
29
|
+
DEFAULT_TTR = 120
|
30
|
+
DEFAULT_TUBE = 'default'
|
31
|
+
|
32
|
+
attr_accessor :queue, :app_version, :after_put
|
33
|
+
|
34
|
+
# This is a fake worker instance for running jobs synchronously.
|
35
|
+
def sync_worker()
|
36
|
+
require 'async_observer/worker'
|
37
|
+
@sync_worker ||= AsyncObserver::Worker.new(binding)
|
38
|
+
end
|
39
|
+
|
40
|
+
# This runs jobs synchronously; it's used when no queue is configured.
|
41
|
+
def sync_run(obj)
|
42
|
+
body = YAML.dump(obj)
|
43
|
+
job = Beanstalk::Job.new(AsyncObserver::FakeConn.new(), 0, body)
|
44
|
+
sync_worker.dispatch(job)
|
45
|
+
sync_worker.do_all_work()
|
46
|
+
return 0, '0.0.0.0'
|
47
|
+
end
|
48
|
+
|
49
|
+
def put!(obj, pri=DEFAULT_PRI, delay=DEFAULT_DELAY, ttr=DEFAULT_TTR,
|
50
|
+
tube=DEFAULT_TUBE)
|
51
|
+
return sync_run(obj) if (:direct.equal?(pri) or !queue)
|
52
|
+
queue.connect()
|
53
|
+
queue.use(tube)
|
54
|
+
info = [queue.yput(obj, pri, delay, ttr), queue.last_server]
|
55
|
+
f = AsyncObserver::Queue.after_put
|
56
|
+
f.call(*info) if f
|
57
|
+
return info
|
58
|
+
end
|
59
|
+
|
60
|
+
SUBMIT_OPTS = [:pri, :fuzz, :delay, :ttr, :tube]
|
61
|
+
|
62
|
+
def put_call!(obj, sel, opts, args=[])
|
63
|
+
pri = opts.fetch(:pri, DEFAULT_PRI)
|
64
|
+
fuzz = opts.fetch(:fuzz, DEFAULT_FUZZ)
|
65
|
+
delay = opts.fetch(:delay, DEFAULT_DELAY)
|
66
|
+
ttr = opts.fetch(:ttr, DEFAULT_TTR)
|
67
|
+
tube = opts.fetch(:tube, (app_version or DEFAULT_TUBE))
|
68
|
+
worker_opts = opts.reject{|k,v| SUBMIT_OPTS.include?(k)}
|
69
|
+
|
70
|
+
pri = pri + rand(fuzz + 1) if !:direct.equal?(pri)
|
71
|
+
|
72
|
+
code = gen(obj, sel, args)
|
73
|
+
RAILS_DEFAULT_LOGGER.info("put #{pri} #{code}")
|
74
|
+
put!(pkg(code, worker_opts), pri, delay, ttr, tube)
|
75
|
+
end
|
76
|
+
|
77
|
+
def pkg(code, opts)
|
78
|
+
opts.merge(:type => :rails, :code => code)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Be careful not to pass in a selector that's not valid ruby source.
|
82
|
+
def gen(obj, selector, args)
|
83
|
+
obj.rrepr + '.' + selector.to_s + '(' + gen_args(args) + ')'
|
84
|
+
end
|
85
|
+
|
86
|
+
def gen_args(args)
|
87
|
+
args.rrepr[1...-1]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
class AsyncObserver::FakeConn
|
92
|
+
def delete(x)
|
93
|
+
end
|
94
|
+
|
95
|
+
def release(x, y, z)
|
96
|
+
end
|
97
|
+
|
98
|
+
def bury(x, y)
|
99
|
+
end
|
100
|
+
|
101
|
+
def addr
|
102
|
+
'<none>'
|
103
|
+
end
|
104
|
+
|
105
|
+
def job_stats(id)
|
106
|
+
{
|
107
|
+
'id' => id,
|
108
|
+
'state' => 'reserved',
|
109
|
+
'age' => 0,
|
110
|
+
'delay' => 0,
|
111
|
+
'time-left' => 5000,
|
112
|
+
'timeouts' => 0,
|
113
|
+
'releases' => 0,
|
114
|
+
'buries' => 0,
|
115
|
+
'kicks' => 0,
|
116
|
+
}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# This is commented out to workaround (what we think is) a ruby bug in method
|
121
|
+
# lookup. Somehow the methods defined here are being used instead of ones in
|
122
|
+
# ActiveRecord::Base.
|
123
|
+
#class Object
|
124
|
+
# def rrepr()
|
125
|
+
# raise ArgumentError.new('no consistent external repr for ' + self.inspect)
|
126
|
+
# end
|
127
|
+
#end
|
128
|
+
|
129
|
+
class Symbol
|
130
|
+
def rrepr() inspect end
|
131
|
+
end
|
132
|
+
|
133
|
+
class Module
|
134
|
+
def rrepr() name end
|
135
|
+
end
|
136
|
+
|
137
|
+
class NilClass
|
138
|
+
def rrepr() inspect end
|
139
|
+
end
|
140
|
+
|
141
|
+
class FalseClass
|
142
|
+
def rrepr() inspect end
|
143
|
+
end
|
144
|
+
|
145
|
+
class TrueClass
|
146
|
+
def rrepr() inspect end
|
147
|
+
end
|
148
|
+
|
149
|
+
class Numeric
|
150
|
+
def rrepr() inspect end
|
151
|
+
end
|
152
|
+
|
153
|
+
class String
|
154
|
+
def rrepr() inspect end
|
155
|
+
end
|
156
|
+
|
157
|
+
class Array
|
158
|
+
def rrepr() '[' + map(&:rrepr).join(', ') + ']' end
|
159
|
+
end
|
160
|
+
|
161
|
+
class Hash
|
162
|
+
def rrepr() '{' + map{|k,v| k.rrepr + '=>' + v.rrepr}.join(', ') + '}' end
|
163
|
+
end
|
164
|
+
|
165
|
+
class Range
|
166
|
+
def rrepr() "(#{first.rrepr}#{exclude_end? ? '...' : '..'}#{last.rrepr})" end
|
167
|
+
end
|
168
|
+
|
169
|
+
class Time
|
170
|
+
def rrepr() "Time.parse('#{self.inspect}')" end
|
171
|
+
end
|
172
|
+
|
173
|
+
class Date
|
174
|
+
def rrepr() "Date.parse('#{self.inspect}')" end
|
175
|
+
end
|
176
|
+
|
177
|
+
module AsyncObserver::Extensions
|
178
|
+
def rrepr()
|
179
|
+
method = (respond_to? :get_cache) ? 'get_cache' : 'find'
|
180
|
+
"#{self.class.rrepr}.#{method}(#{id.rrepr})"
|
181
|
+
end
|
182
|
+
end
|