delayed_job 4.0.2 → 4.0.3
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.
- checksums.yaml +5 -13
- data/CHANGELOG.md +7 -0
- data/README.md +12 -7
- data/Rakefile +6 -1
- data/delayed_job.gemspec +5 -5
- data/lib/delayed/backend/base.rb +18 -20
- data/lib/delayed/backend/shared_spec.rb +139 -138
- data/lib/delayed/command.rb +75 -40
- data/lib/delayed/exceptions.rb +2 -1
- data/lib/delayed/lifecycle.rb +12 -11
- data/lib/delayed/message_sending.rb +9 -10
- data/lib/delayed/performable_mailer.rb +2 -2
- data/lib/delayed/performable_method.rb +2 -2
- data/lib/delayed/psych_ext.rb +29 -93
- data/lib/delayed/recipes.rb +5 -5
- data/lib/delayed/serialization/active_record.rb +11 -9
- data/lib/delayed/syck_ext.rb +3 -3
- data/lib/delayed/tasks.rb +5 -5
- data/lib/delayed/worker.rb +42 -42
- data/lib/generators/delayed_job/delayed_job_generator.rb +2 -3
- data/spec/delayed/backend/test.rb +22 -17
- data/spec/delayed/command_spec.rb +57 -0
- data/spec/helper.rb +25 -12
- data/spec/lifecycle_spec.rb +23 -15
- data/spec/message_sending_spec.rb +34 -34
- data/spec/performable_mailer_spec.rb +11 -11
- data/spec/performable_method_spec.rb +24 -26
- data/spec/psych_ext_spec.rb +12 -0
- data/spec/sample_jobs.rb +46 -18
- data/spec/test_backend_spec.rb +3 -3
- data/spec/worker_spec.rb +27 -27
- data/spec/yaml_ext_spec.rb +16 -16
- metadata +12 -8
data/lib/delayed/command.rb
CHANGED
@@ -1,15 +1,17 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
unless ENV['RAILS_ENV'] == 'test'
|
2
|
+
begin
|
3
|
+
require 'daemons'
|
4
|
+
rescue LoadError
|
5
|
+
raise "You need to add gem 'daemons' to your Gemfile if you wish to use it."
|
6
|
+
end
|
5
7
|
end
|
6
8
|
require 'optparse'
|
7
9
|
|
8
10
|
module Delayed
|
9
|
-
class Command
|
10
|
-
attr_accessor :worker_count
|
11
|
+
class Command # rubocop:disable ClassLength
|
12
|
+
attr_accessor :worker_count, :worker_pools
|
11
13
|
|
12
|
-
def initialize(args)
|
14
|
+
def initialize(args) # rubocop:disable MethodLength
|
13
15
|
@options = {
|
14
16
|
:quiet => true,
|
15
17
|
:pid_dir => "#{Rails.root}/tmp/pids"
|
@@ -18,88 +20,106 @@ module Delayed
|
|
18
20
|
@worker_count = 1
|
19
21
|
@monitor = false
|
20
22
|
|
21
|
-
opts = OptionParser.new do |
|
22
|
-
|
23
|
+
opts = OptionParser.new do |opt|
|
24
|
+
opt.banner = "Usage: #{File.basename($PROGRAM_NAME)} [options] start|stop|restart|run"
|
23
25
|
|
24
|
-
|
25
|
-
puts
|
26
|
+
opt.on('-h', '--help', 'Show this message') do
|
27
|
+
puts opt
|
26
28
|
exit 1
|
27
29
|
end
|
28
|
-
|
29
|
-
STDERR.puts
|
30
|
+
opt.on('-e', '--environment=NAME', 'Specifies the environment to run this delayed jobs under (test/development/production).') do |_e|
|
31
|
+
STDERR.puts 'The -e/--environment option has been deprecated and has no effect. Use RAILS_ENV and see http://github.com/collectiveidea/delayed_job/issues/#issue/7'
|
30
32
|
end
|
31
|
-
|
33
|
+
opt.on('--min-priority N', 'Minimum priority of jobs to run.') do |n|
|
32
34
|
@options[:min_priority] = n
|
33
35
|
end
|
34
|
-
|
36
|
+
opt.on('--max-priority N', 'Maximum priority of jobs to run.') do |n|
|
35
37
|
@options[:max_priority] = n
|
36
38
|
end
|
37
|
-
|
38
|
-
@worker_count = worker_count.to_i rescue 1
|
39
|
+
opt.on('-n', '--number_of_workers=workers', 'Number of unique workers to spawn') do |worker_count|
|
40
|
+
@worker_count = worker_count.to_i rescue 1 # rubocop:disable RescueModifier
|
39
41
|
end
|
40
|
-
|
42
|
+
opt.on('--pid-dir=DIR', 'Specifies an alternate directory in which to store the process ids.') do |dir|
|
41
43
|
@options[:pid_dir] = dir
|
42
44
|
end
|
43
|
-
|
45
|
+
opt.on('-i', '--identifier=n', 'A numeric identifier for the worker.') do |n|
|
44
46
|
@options[:identifier] = n
|
45
47
|
end
|
46
|
-
|
48
|
+
opt.on('-m', '--monitor', 'Start monitor process.') do
|
47
49
|
@monitor = true
|
48
50
|
end
|
49
|
-
|
51
|
+
opt.on('--sleep-delay N', 'Amount of time to sleep when no jobs are found') do |n|
|
50
52
|
@options[:sleep_delay] = n.to_i
|
51
53
|
end
|
52
|
-
|
54
|
+
opt.on('--read-ahead N', 'Number of jobs from the queue to consider') do |n|
|
53
55
|
@options[:read_ahead] = n
|
54
56
|
end
|
55
|
-
|
57
|
+
opt.on('-p', '--prefix NAME', 'String to be prefixed to worker process names') do |prefix|
|
56
58
|
@options[:prefix] = prefix
|
57
59
|
end
|
58
|
-
|
60
|
+
opt.on('--queues=queues', 'Specify which queue DJ must look up for jobs') do |queues|
|
59
61
|
@options[:queues] = queues.split(',')
|
60
62
|
end
|
61
|
-
|
63
|
+
opt.on('--queue=queue', 'Specify which queue DJ must look up for jobs') do |queue|
|
62
64
|
@options[:queues] = queue.split(',')
|
63
65
|
end
|
64
|
-
|
66
|
+
opt.on('--pool=queue1[,queue2][:worker_count]', 'Specify queues and number of workers for a worker pool') do |pool|
|
67
|
+
parse_worker_pool(pool)
|
68
|
+
end
|
69
|
+
opt.on('--exit-on-complete', 'Exit when no more jobs are available to run. This will exit if all jobs are scheduled to run in the future.') do
|
65
70
|
@options[:exit_on_complete] = true
|
66
71
|
end
|
67
72
|
end
|
68
73
|
@args = opts.parse!(args)
|
69
74
|
end
|
70
75
|
|
71
|
-
def daemonize
|
76
|
+
def daemonize # rubocop:disable PerceivedComplexity
|
72
77
|
dir = @options[:pid_dir]
|
73
|
-
Dir.mkdir(dir) unless File.
|
78
|
+
Dir.mkdir(dir) unless File.exist?(dir)
|
74
79
|
|
75
|
-
if
|
76
|
-
|
77
|
-
elsif @
|
78
|
-
|
79
|
-
|
80
|
+
if worker_pools
|
81
|
+
setup_pools
|
82
|
+
elsif @options[:identifier]
|
83
|
+
if worker_count > 1
|
84
|
+
raise ArgumentError, 'Cannot specify both --number-of-workers and --identifier'
|
85
|
+
else
|
86
|
+
run_process("delayed_job.#{@options[:identifier]}", @options)
|
87
|
+
end
|
80
88
|
else
|
81
89
|
worker_count.times do |worker_index|
|
82
|
-
process_name = worker_count == 1 ?
|
83
|
-
run_process(process_name,
|
90
|
+
process_name = worker_count == 1 ? 'delayed_job' : "delayed_job.#{worker_index}"
|
91
|
+
run_process(process_name, @options)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def setup_pools
|
97
|
+
worker_index = 0
|
98
|
+
@worker_pools.each do |queues, worker_count|
|
99
|
+
options = @options.merge(:queues => queues)
|
100
|
+
worker_count.times do
|
101
|
+
process_name = "delayed_job.#{worker_index}"
|
102
|
+
run_process(process_name, options)
|
103
|
+
worker_index += 1
|
84
104
|
end
|
85
105
|
end
|
86
106
|
end
|
87
107
|
|
88
|
-
def run_process(process_name,
|
108
|
+
def run_process(process_name, options = {})
|
89
109
|
Delayed::Worker.before_fork
|
90
|
-
Daemons.run_proc(process_name, :dir =>
|
91
|
-
$0 = File.join(
|
110
|
+
Daemons.run_proc(process_name, :dir => options[:pid_dir], :dir_mode => :normal, :monitor => @monitor, :ARGV => @args) do |*_args|
|
111
|
+
$0 = File.join(options[:prefix], process_name) if @options[:prefix]
|
92
112
|
run process_name
|
93
113
|
end
|
94
114
|
end
|
95
115
|
|
96
|
-
def run(worker_name = nil)
|
116
|
+
def run(worker_name = nil, options = {})
|
97
117
|
Dir.chdir(Rails.root)
|
98
118
|
|
99
119
|
Delayed::Worker.after_fork
|
100
120
|
Delayed::Worker.logger ||= Logger.new(File.join(Rails.root, 'log', 'delayed_job.log'))
|
101
121
|
|
102
|
-
worker = Delayed::Worker.new(
|
122
|
+
worker = Delayed::Worker.new(options)
|
103
123
|
worker.name_prefix = "#{worker_name} "
|
104
124
|
worker.start
|
105
125
|
rescue => e
|
@@ -107,5 +127,20 @@ module Delayed
|
|
107
127
|
STDERR.puts e.message
|
108
128
|
exit 1
|
109
129
|
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def parse_worker_pool(pool)
|
134
|
+
@worker_pools ||= []
|
135
|
+
|
136
|
+
queues, worker_count = pool.split(':')
|
137
|
+
if ['*', '', nil].include?(queues)
|
138
|
+
queues = []
|
139
|
+
else
|
140
|
+
queues = queues.split(',')
|
141
|
+
end
|
142
|
+
worker_count = (worker_count || 1).to_i rescue 1
|
143
|
+
@worker_pools << [queues, worker_count]
|
144
|
+
end
|
110
145
|
end
|
111
146
|
end
|
data/lib/delayed/exceptions.rb
CHANGED
@@ -3,7 +3,8 @@ require 'timeout'
|
|
3
3
|
module Delayed
|
4
4
|
class WorkerTimeout < Timeout::Error
|
5
5
|
def message
|
6
|
-
|
6
|
+
seconds = Delayed::Worker.max_run_time.to_i
|
7
|
+
"#{super} (Delayed::Worker.max_run_time is only #{seconds} second#{seconds == 1 ? '' : 's'})"
|
7
8
|
end
|
8
9
|
end
|
9
10
|
|
data/lib/delayed/lifecycle.rb
CHANGED
@@ -13,7 +13,9 @@ module Delayed
|
|
13
13
|
}
|
14
14
|
|
15
15
|
def initialize
|
16
|
-
@callbacks = EVENTS.keys.
|
16
|
+
@callbacks = EVENTS.keys.each_with_object({}) do |e, hash|
|
17
|
+
hash[e] = Callback.new
|
18
|
+
end
|
17
19
|
end
|
18
20
|
|
19
21
|
def before(event, &block)
|
@@ -29,7 +31,7 @@ module Delayed
|
|
29
31
|
end
|
30
32
|
|
31
33
|
def run_callbacks(event, *args, &block)
|
32
|
-
missing_callback(event) unless @callbacks.
|
34
|
+
missing_callback(event) unless @callbacks.key?(event)
|
33
35
|
|
34
36
|
unless EVENTS[event].size == args.size
|
35
37
|
raise ArgumentError, "Callback #{event} expects #{EVENTS[event].size} parameter(s): #{EVENTS[event].join(', ')}"
|
@@ -38,17 +40,16 @@ module Delayed
|
|
38
40
|
@callbacks[event].execute(*args, &block)
|
39
41
|
end
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
-
def add(type, event, &block)
|
44
|
-
missing_callback(event) unless @callbacks.has_key?(event)
|
43
|
+
private
|
45
44
|
|
46
|
-
|
47
|
-
|
45
|
+
def add(type, event, &block)
|
46
|
+
missing_callback(event) unless @callbacks.key?(event)
|
47
|
+
@callbacks[event].add(type, &block)
|
48
|
+
end
|
48
49
|
|
49
|
-
|
50
|
-
|
51
|
-
|
50
|
+
def missing_callback(event)
|
51
|
+
raise InvalidCallback, "Unknown callback event: #{event}"
|
52
|
+
end
|
52
53
|
end
|
53
54
|
|
54
55
|
class Callback
|
@@ -17,31 +17,30 @@ module Delayed
|
|
17
17
|
def delay(options = {})
|
18
18
|
DelayProxy.new(PerformableMethod, self, options)
|
19
19
|
end
|
20
|
-
|
20
|
+
alias_method :__delay__, :delay
|
21
21
|
|
22
22
|
def send_later(method, *args)
|
23
|
-
warn
|
23
|
+
warn '[DEPRECATION] `object.send_later(:method)` is deprecated. Use `object.delay.method'
|
24
24
|
__delay__.__send__(method, *args)
|
25
25
|
end
|
26
26
|
|
27
27
|
def send_at(time, method, *args)
|
28
|
-
warn
|
28
|
+
warn '[DEPRECATION] `object.send_at(time, :method)` is deprecated. Use `object.delay(:run_at => time).method'
|
29
29
|
__delay__(:run_at => time).__send__(method, *args)
|
30
30
|
end
|
31
31
|
|
32
32
|
module ClassMethods
|
33
33
|
def handle_asynchronously(method, opts = {})
|
34
|
-
aliased_method, punctuation = method.to_s.sub(/([?!=])$/, ''), $1
|
34
|
+
aliased_method, punctuation = method.to_s.sub(/([?!=])$/, ''), $1 # rubocop:disable PerlBackrefs
|
35
35
|
with_method, without_method = "#{aliased_method}_with_delay#{punctuation}", "#{aliased_method}_without_delay#{punctuation}"
|
36
36
|
define_method(with_method) do |*args|
|
37
37
|
curr_opts = opts.clone
|
38
38
|
curr_opts.each_key do |key|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
end
|
39
|
+
next unless (val = curr_opts[key]).is_a?(Proc)
|
40
|
+
curr_opts[key] = if val.arity == 1
|
41
|
+
val.call(self)
|
42
|
+
else
|
43
|
+
val.call
|
45
44
|
end
|
46
45
|
end
|
47
46
|
delay(curr_opts).__send__(without_method, *args)
|
@@ -15,7 +15,7 @@ module Delayed
|
|
15
15
|
end
|
16
16
|
|
17
17
|
Mail::Message.class_eval do
|
18
|
-
def delay(*
|
19
|
-
raise
|
18
|
+
def delay(*_args)
|
19
|
+
raise 'Use MyMailer.delay.mailer_action(args) to delay sending of emails.'
|
20
20
|
end
|
21
21
|
end
|
@@ -10,7 +10,7 @@ module Delayed
|
|
10
10
|
raise NoMethodError, "undefined method `#{method_name}' for #{object.inspect}" unless object.respond_to?(method_name, true)
|
11
11
|
|
12
12
|
if object.respond_to?(:persisted?) && !object.persisted?
|
13
|
-
raise
|
13
|
+
raise ArgumentError, 'Jobs cannot be created for non-persisted records'
|
14
14
|
end
|
15
15
|
|
16
16
|
self.object = object
|
@@ -30,7 +30,7 @@ module Delayed
|
|
30
30
|
object.send(symbol, *args)
|
31
31
|
end
|
32
32
|
|
33
|
-
def respond_to?(symbol, include_private=false)
|
33
|
+
def respond_to?(symbol, include_private = false)
|
34
34
|
super || object.respond_to?(symbol, include_private)
|
35
35
|
end
|
36
36
|
end
|
data/lib/delayed/psych_ext.rb
CHANGED
@@ -1,133 +1,69 @@
|
|
1
1
|
if defined?(ActiveRecord)
|
2
2
|
ActiveRecord::Base.class_eval do
|
3
|
+
# rubocop:disable BlockNesting
|
3
4
|
if instance_methods.include?(:encode_with)
|
4
5
|
def encode_with_override(coder)
|
5
6
|
encode_with_without_override(coder)
|
6
|
-
coder.tag = "!ruby/ActiveRecord:#{self.class.name}"
|
7
|
+
coder.tag = "!ruby/ActiveRecord:#{self.class.name}" if coder.respond_to?(:tag=)
|
7
8
|
end
|
8
9
|
alias_method :encode_with_without_override, :encode_with
|
9
10
|
alias_method :encode_with, :encode_with_override
|
10
11
|
else
|
11
12
|
def encode_with(coder)
|
12
|
-
coder[
|
13
|
-
coder.tag = "!ruby/ActiveRecord:#{self.class.name}"
|
13
|
+
coder['attributes'] = attributes
|
14
|
+
coder.tag = "!ruby/ActiveRecord:#{self.class.name}" if coder.respond_to?(:tag=)
|
14
15
|
end
|
15
16
|
end
|
16
17
|
end
|
17
18
|
end
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
coder
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
20
|
+
module Delayed
|
21
|
+
class PerformableMethod
|
22
|
+
# serialize to YAML
|
23
|
+
def encode_with(coder)
|
24
|
+
coder.map = {
|
25
|
+
'object' => object,
|
26
|
+
'method_name' => method_name,
|
27
|
+
'args' => args
|
28
|
+
}
|
29
|
+
end
|
27
30
|
end
|
28
31
|
end
|
29
32
|
|
30
33
|
module Psych
|
31
34
|
module Visitors
|
32
|
-
class YAMLTree
|
33
|
-
def visit_Class(klass)
|
34
|
-
@emitter.scalar klass.name, nil, '!ruby/class', false, false, Nodes::Scalar::SINGLE_QUOTED
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
35
|
class ToRuby
|
39
|
-
def
|
40
|
-
@st[o.anchor] = o.value if o.anchor
|
41
|
-
|
42
|
-
if klass = Psych.load_tags[o.tag]
|
43
|
-
instance = klass.allocate
|
44
|
-
|
45
|
-
if instance.respond_to?(:init_with)
|
46
|
-
coder = Psych::Coder.new(o.tag)
|
47
|
-
coder.scalar = o.value
|
48
|
-
instance.init_with coder
|
49
|
-
end
|
50
|
-
|
51
|
-
return instance
|
52
|
-
end
|
53
|
-
|
54
|
-
return o.value if o.quoted
|
55
|
-
return @ss.tokenize(o.value) unless o.tag
|
56
|
-
|
57
|
-
case o.tag
|
58
|
-
when '!binary', 'tag:yaml.org,2002:binary'
|
59
|
-
o.value.unpack('m').first
|
60
|
-
when '!str', 'tag:yaml.org,2002:str'
|
61
|
-
o.value
|
62
|
-
when "!ruby/object:DateTime"
|
63
|
-
require 'date'
|
64
|
-
@ss.parse_time(o.value).to_datetime
|
65
|
-
when "!ruby/object:Complex"
|
66
|
-
Complex(o.value)
|
67
|
-
when "!ruby/object:Rational"
|
68
|
-
Rational(o.value)
|
69
|
-
when "!ruby/class", "!ruby/module"
|
70
|
-
resolve_class o.value
|
71
|
-
when "tag:yaml.org,2002:float", "!float"
|
72
|
-
Float(@ss.tokenize(o.value))
|
73
|
-
when "!ruby/regexp"
|
74
|
-
o.value =~ /^\/(.*)\/([mixn]*)$/
|
75
|
-
source = $1
|
76
|
-
options = 0
|
77
|
-
lang = nil
|
78
|
-
($2 || '').split('').each do |option|
|
79
|
-
case option
|
80
|
-
when 'x' then options |= Regexp::EXTENDED
|
81
|
-
when 'i' then options |= Regexp::IGNORECASE
|
82
|
-
when 'm' then options |= Regexp::MULTILINE
|
83
|
-
when 'n' then options |= Regexp::NOENCODING
|
84
|
-
else lang = option
|
85
|
-
end
|
86
|
-
end
|
87
|
-
Regexp.new(*[source, options, lang].compact)
|
88
|
-
when "!ruby/range"
|
89
|
-
args = o.value.split(/([.]{2,3})/, 2).map { |s|
|
90
|
-
accept Nodes::Scalar.new(s)
|
91
|
-
}
|
92
|
-
args.push(args.delete_at(1) == '...')
|
93
|
-
Range.new(*args)
|
94
|
-
when /^!ruby\/sym(bol)?:?(.*)?$/
|
95
|
-
o.value.to_sym
|
96
|
-
else
|
97
|
-
@ss.tokenize o.value
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
def visit_Psych_Nodes_Mapping_with_class(object)
|
36
|
+
def visit_Psych_Nodes_Mapping_with_class(object) # rubocop:disable CyclomaticComplexity, MethodName
|
102
37
|
return revive(Psych.load_tags[object.tag], object) if Psych.load_tags[object.tag]
|
103
38
|
|
104
39
|
case object.tag
|
105
40
|
when /^!ruby\/ActiveRecord:(.+)$/
|
106
|
-
klass = resolve_class(
|
41
|
+
klass = resolve_class(Regexp.last_match[1])
|
107
42
|
payload = Hash[*object.children.map { |c| accept c }]
|
108
|
-
id = payload[
|
43
|
+
id = payload['attributes'][klass.primary_key]
|
109
44
|
begin
|
110
45
|
klass.unscoped.find(id)
|
111
|
-
rescue ActiveRecord::RecordNotFound
|
112
|
-
raise Delayed::DeserializationError
|
46
|
+
rescue ActiveRecord::RecordNotFound => error
|
47
|
+
raise Delayed::DeserializationError, "ActiveRecord::RecordNotFound, class: #{klass}, primary key: #{id} (#{error.message})"
|
113
48
|
end
|
114
49
|
when /^!ruby\/Mongoid:(.+)$/
|
115
|
-
klass = resolve_class(
|
50
|
+
klass = resolve_class(Regexp.last_match[1])
|
116
51
|
payload = Hash[*object.children.map { |c| accept c }]
|
52
|
+
id = payload['attributes']['_id']
|
117
53
|
begin
|
118
|
-
klass.find(
|
119
|
-
rescue Mongoid::Errors::DocumentNotFound
|
120
|
-
raise Delayed::DeserializationError
|
54
|
+
klass.find(id)
|
55
|
+
rescue Mongoid::Errors::DocumentNotFound => error
|
56
|
+
raise Delayed::DeserializationError, "Mongoid::Errors::DocumentNotFound, class: #{klass}, primary key: #{id} (#{error.message})"
|
121
57
|
end
|
122
58
|
when /^!ruby\/DataMapper:(.+)$/
|
123
|
-
klass = resolve_class(
|
59
|
+
klass = resolve_class(Regexp.last_match[1])
|
124
60
|
payload = Hash[*object.children.map { |c| accept c }]
|
125
61
|
begin
|
126
|
-
primary_keys = klass.properties.select
|
62
|
+
primary_keys = klass.properties.select(&:key?)
|
127
63
|
key_names = primary_keys.map { |p| p.name.to_s }
|
128
|
-
klass.get!(*key_names.map { |k| payload[
|
129
|
-
rescue DataMapper::ObjectNotFoundError
|
130
|
-
raise Delayed::DeserializationError
|
64
|
+
klass.get!(*key_names.map { |k| payload['attributes'][k] })
|
65
|
+
rescue DataMapper::ObjectNotFoundError => error
|
66
|
+
raise Delayed::DeserializationError, "DataMapper::ObjectNotFoundError, class: #{klass} (#{error.message})"
|
131
67
|
end
|
132
68
|
else
|
133
69
|
visit_Psych_Nodes_Mapping_without_class(object)
|