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