sidekiq 7.2.4 → 7.3.9
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 +4 -4
- data/Changes.md +116 -0
- data/README.md +1 -1
- data/bin/sidekiqload +21 -12
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +75 -0
- data/lib/generators/sidekiq/job_generator.rb +2 -0
- data/lib/sidekiq/api.rb +63 -34
- data/lib/sidekiq/capsule.rb +8 -3
- data/lib/sidekiq/cli.rb +2 -1
- data/lib/sidekiq/client.rb +21 -1
- data/lib/sidekiq/component.rb +22 -0
- data/lib/sidekiq/config.rb +27 -3
- data/lib/sidekiq/deploy.rb +2 -0
- data/lib/sidekiq/embedded.rb +2 -0
- data/lib/sidekiq/fetch.rb +1 -1
- data/lib/sidekiq/iterable_job.rb +55 -0
- data/lib/sidekiq/job/interrupt_handler.rb +24 -0
- data/lib/sidekiq/job/iterable/active_record_enumerator.rb +53 -0
- data/lib/sidekiq/job/iterable/csv_enumerator.rb +47 -0
- data/lib/sidekiq/job/iterable/enumerators.rb +135 -0
- data/lib/sidekiq/job/iterable.rb +294 -0
- data/lib/sidekiq/job.rb +13 -2
- data/lib/sidekiq/job_logger.rb +7 -6
- data/lib/sidekiq/job_retry.rb +6 -1
- data/lib/sidekiq/job_util.rb +2 -0
- data/lib/sidekiq/launcher.rb +1 -1
- data/lib/sidekiq/metrics/query.rb +2 -0
- data/lib/sidekiq/metrics/shared.rb +15 -4
- data/lib/sidekiq/metrics/tracking.rb +13 -5
- data/lib/sidekiq/middleware/current_attributes.rb +46 -13
- data/lib/sidekiq/middleware/modules.rb +2 -0
- data/lib/sidekiq/monitor.rb +2 -1
- data/lib/sidekiq/paginator.rb +6 -0
- data/lib/sidekiq/processor.rb +20 -10
- data/lib/sidekiq/rails.rb +12 -0
- data/lib/sidekiq/redis_client_adapter.rb +8 -5
- data/lib/sidekiq/redis_connection.rb +33 -2
- data/lib/sidekiq/ring_buffer.rb +2 -0
- data/lib/sidekiq/systemd.rb +2 -0
- data/lib/sidekiq/testing.rb +5 -5
- data/lib/sidekiq/version.rb +5 -1
- data/lib/sidekiq/web/action.rb +21 -4
- data/lib/sidekiq/web/application.rb +43 -82
- data/lib/sidekiq/web/helpers.rb +62 -15
- data/lib/sidekiq/web/router.rb +5 -2
- data/lib/sidekiq/web.rb +54 -2
- data/lib/sidekiq.rb +5 -3
- data/sidekiq.gemspec +3 -2
- data/web/assets/javascripts/application.js +6 -1
- data/web/assets/javascripts/dashboard-charts.js +24 -12
- data/web/assets/javascripts/dashboard.js +7 -1
- data/web/assets/stylesheets/application.css +16 -3
- data/web/locales/en.yml +3 -1
- data/web/locales/fr.yml +0 -1
- data/web/locales/gd.yml +0 -1
- data/web/locales/it.yml +32 -1
- data/web/locales/ja.yml +0 -1
- data/web/locales/pt-br.yml +1 -2
- data/web/locales/tr.yml +100 -0
- data/web/locales/uk.yml +24 -1
- data/web/locales/zh-cn.yml +0 -1
- data/web/locales/zh-tw.yml +0 -1
- data/web/views/_footer.erb +1 -2
- data/web/views/dashboard.erb +10 -7
- data/web/views/filtering.erb +1 -2
- data/web/views/layout.erb +6 -6
- data/web/views/metrics.erb +7 -8
- data/web/views/metrics_for_job.erb +4 -4
- data/web/views/morgue.erb +2 -2
- data/web/views/queue.erb +1 -1
- metadata +32 -13
data/lib/sidekiq/job_util.rb
CHANGED
data/lib/sidekiq/launcher.rb
CHANGED
@@ -36,8 +36,8 @@ module Sidekiq
|
|
36
36
|
# has a heartbeat thread, caller can use `async_beat: false`
|
37
37
|
# and instead have thread call Launcher#heartbeat every N seconds.
|
38
38
|
def run(async_beat: true)
|
39
|
-
Sidekiq.freeze!
|
40
39
|
logger.debug { @config.merge!({}) }
|
40
|
+
Sidekiq.freeze!
|
41
41
|
@thread = safe_thread("heartbeat", &method(:start_heartbeat)) if async_beat
|
42
42
|
@poller.start
|
43
43
|
@managers.each(&:start)
|
@@ -1,10 +1,21 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Sidekiq
|
4
4
|
module Metrics
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
class Counter
|
6
|
+
def initialize
|
7
|
+
@value = 0
|
8
|
+
@lock = Mutex.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def increment
|
12
|
+
@lock.synchronize { @value += 1 }
|
13
|
+
end
|
14
|
+
|
15
|
+
def value
|
16
|
+
@lock.synchronize { @value }
|
17
|
+
end
|
18
|
+
end
|
8
19
|
|
9
20
|
# Implements space-efficient but statistically useful histogram storage.
|
10
21
|
# A precise time histogram stores every time. Instead we break times into a set of
|
@@ -31,11 +31,11 @@ module Sidekiq
|
|
31
31
|
# We don't track time for failed jobs as they can have very unpredictable
|
32
32
|
# execution times. more important to know average time for successful jobs so we
|
33
33
|
# can better recognize when a perf regression is introduced.
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
34
|
+
track_time(klass, time_ms)
|
35
|
+
rescue JobRetry::Skip
|
36
|
+
# This is raised when iterable job is interrupted.
|
37
|
+
track_time(klass, time_ms)
|
38
|
+
raise
|
39
39
|
rescue Exception
|
40
40
|
@lock.synchronize {
|
41
41
|
@jobs["#{klass}|f"] += 1
|
@@ -100,6 +100,14 @@ module Sidekiq
|
|
100
100
|
|
101
101
|
private
|
102
102
|
|
103
|
+
def track_time(klass, time_ms)
|
104
|
+
@lock.synchronize {
|
105
|
+
@grams[klass].record_time(time_ms)
|
106
|
+
@jobs["#{klass}|ms"] += time_ms
|
107
|
+
@totals["ms"] += time_ms
|
108
|
+
}
|
109
|
+
end
|
110
|
+
|
103
111
|
def reset
|
104
112
|
@lock.synchronize {
|
105
113
|
array = [@totals, @jobs, @grams]
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "active_support/current_attributes"
|
2
4
|
|
3
5
|
module Sidekiq
|
@@ -31,11 +33,26 @@ module Sidekiq
|
|
31
33
|
attrs = strklass.constantize.attributes
|
32
34
|
# Retries can push the job N times, we don't
|
33
35
|
# want retries to reset cattr. #5692, #5090
|
34
|
-
|
36
|
+
if attrs.any?
|
37
|
+
# Older rails has a bug that `CurrentAttributes#attributes` always returns
|
38
|
+
# the same hash instance. We need to dup it to avoid being accidentally mutated.
|
39
|
+
job[key] = if returns_same_object?
|
40
|
+
attrs.dup
|
41
|
+
else
|
42
|
+
attrs
|
43
|
+
end
|
44
|
+
end
|
35
45
|
end
|
36
46
|
end
|
37
47
|
yield
|
38
48
|
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def returns_same_object?
|
53
|
+
ActiveSupport::VERSION::MAJOR < 8 ||
|
54
|
+
(ActiveSupport::VERSION::MAJOR == 8 && ActiveSupport::VERSION::MINOR == 0)
|
55
|
+
end
|
39
56
|
end
|
40
57
|
|
41
58
|
class Load
|
@@ -46,22 +63,38 @@ module Sidekiq
|
|
46
63
|
end
|
47
64
|
|
48
65
|
def call(_, job, _, &block)
|
49
|
-
|
66
|
+
klass_attrs = {}
|
50
67
|
|
51
68
|
@cattrs.each do |(key, strklass)|
|
52
|
-
|
53
|
-
constklass = strklass.constantize
|
54
|
-
cattrs_to_reset << constklass
|
69
|
+
next unless job.has_key?(key)
|
55
70
|
|
56
|
-
|
57
|
-
constklass.public_send(:"#{attribute}=", value)
|
58
|
-
end
|
59
|
-
end
|
71
|
+
klass_attrs[strklass.constantize] = job[key]
|
60
72
|
end
|
61
73
|
|
62
|
-
|
63
|
-
|
64
|
-
|
74
|
+
wrap(klass_attrs.to_a, &block)
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def wrap(klass_attrs, &block)
|
80
|
+
klass, attrs = klass_attrs.shift
|
81
|
+
return block.call unless klass
|
82
|
+
|
83
|
+
retried = false
|
84
|
+
|
85
|
+
begin
|
86
|
+
klass.set(attrs) do
|
87
|
+
wrap(klass_attrs, &block)
|
88
|
+
end
|
89
|
+
rescue NoMethodError
|
90
|
+
raise if retried
|
91
|
+
|
92
|
+
# It is possible that the `CurrentAttributes` definition
|
93
|
+
# was changed before the job started processing.
|
94
|
+
attrs = attrs.select { |attr| klass.respond_to?(attr) }
|
95
|
+
retried = true
|
96
|
+
retry
|
97
|
+
end
|
65
98
|
end
|
66
99
|
end
|
67
100
|
|
@@ -70,7 +103,7 @@ module Sidekiq
|
|
70
103
|
cattrs = build_cattrs_hash(klass_or_array)
|
71
104
|
|
72
105
|
config.client_middleware.add Save, cattrs
|
73
|
-
config.server_middleware.
|
106
|
+
config.server_middleware.prepend Load, cattrs
|
74
107
|
end
|
75
108
|
|
76
109
|
private
|
data/lib/sidekiq/monitor.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require "fileutils"
|
4
5
|
require "sidekiq/api"
|
@@ -98,7 +99,7 @@ class Sidekiq::Monitor
|
|
98
99
|
pad = opts[:pad] || 0
|
99
100
|
max_length = opts[:max_length] || (80 - pad)
|
100
101
|
out = []
|
101
|
-
line = ""
|
102
|
+
line = +""
|
102
103
|
values.each do |value|
|
103
104
|
if (line.length + value.length) > max_length
|
104
105
|
out << line
|
data/lib/sidekiq/paginator.rb
CHANGED
@@ -2,6 +2,12 @@
|
|
2
2
|
|
3
3
|
module Sidekiq
|
4
4
|
module Paginator
|
5
|
+
TYPE_CACHE = {
|
6
|
+
"dead" => "zset",
|
7
|
+
"retry" => "zset",
|
8
|
+
"schedule" => "zset"
|
9
|
+
}
|
10
|
+
|
5
11
|
def page(key, pageidx = 1, page_size = 25, opts = nil)
|
6
12
|
current_page = (pageidx.to_i < 1) ? 1 : pageidx.to_i
|
7
13
|
pageidx = current_page - 1
|
data/lib/sidekiq/processor.rb
CHANGED
@@ -36,7 +36,7 @@ module Sidekiq
|
|
36
36
|
@job = nil
|
37
37
|
@thread = nil
|
38
38
|
@reloader = Sidekiq.default_configuration[:reloader]
|
39
|
-
@job_logger = (capsule.config[:job_logger] || Sidekiq::JobLogger).new(
|
39
|
+
@job_logger = (capsule.config[:job_logger] || Sidekiq::JobLogger).new(capsule.config)
|
40
40
|
@retrier = Sidekiq::JobRetry.new(capsule)
|
41
41
|
end
|
42
42
|
|
@@ -58,6 +58,10 @@ module Sidekiq
|
|
58
58
|
@thread.value if wait
|
59
59
|
end
|
60
60
|
|
61
|
+
def stopping?
|
62
|
+
@done
|
63
|
+
end
|
64
|
+
|
61
65
|
def start
|
62
66
|
@thread ||= safe_thread("#{config.name}/processor", &method(:run))
|
63
67
|
end
|
@@ -134,10 +138,11 @@ module Sidekiq
|
|
134
138
|
# Effectively this block denotes a "unit of work" to Rails.
|
135
139
|
@reloader.call do
|
136
140
|
klass = Object.const_get(job_hash["class"])
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
+
instance = klass.new
|
142
|
+
instance.jid = job_hash["jid"]
|
143
|
+
instance._context = self
|
144
|
+
@retrier.local(instance, jobstr, queue) do
|
145
|
+
yield instance
|
141
146
|
end
|
142
147
|
end
|
143
148
|
end
|
@@ -175,9 +180,9 @@ module Sidekiq
|
|
175
180
|
ack = false
|
176
181
|
Thread.handle_interrupt(IGNORE_SHUTDOWN_INTERRUPTS) do
|
177
182
|
Thread.handle_interrupt(ALLOW_SHUTDOWN_INTERRUPTS) do
|
178
|
-
dispatch(job_hash, queue, jobstr) do |
|
179
|
-
config.server_middleware.invoke(
|
180
|
-
execute_job(
|
183
|
+
dispatch(job_hash, queue, jobstr) do |instance|
|
184
|
+
config.server_middleware.invoke(instance, job_hash, queue) do
|
185
|
+
execute_job(instance, job_hash["args"])
|
181
186
|
end
|
182
187
|
end
|
183
188
|
ack = true
|
@@ -185,6 +190,11 @@ module Sidekiq
|
|
185
190
|
# Had to force kill this job because it didn't finish
|
186
191
|
# within the timeout. Don't acknowledge the work since
|
187
192
|
# we didn't properly finish it.
|
193
|
+
rescue Sidekiq::JobRetry::Skip => s
|
194
|
+
# Skip means we handled this error elsewhere. We don't
|
195
|
+
# need to log or report the error.
|
196
|
+
ack = true
|
197
|
+
raise s
|
188
198
|
rescue Sidekiq::JobRetry::Handled => h
|
189
199
|
# this is the common case: job raised error and Sidekiq::JobRetry::Handled
|
190
200
|
# signals that we created a retry successfully. We can acknowledge the job.
|
@@ -206,8 +216,8 @@ module Sidekiq
|
|
206
216
|
end
|
207
217
|
end
|
208
218
|
|
209
|
-
def execute_job(
|
210
|
-
|
219
|
+
def execute_job(instance, cloned_args)
|
220
|
+
instance.perform(*cloned_args)
|
211
221
|
end
|
212
222
|
|
213
223
|
# Ruby doesn't provide atomic counters out of the box so we'll
|
data/lib/sidekiq/rails.rb
CHANGED
@@ -4,6 +4,17 @@ require "sidekiq/job"
|
|
4
4
|
require "rails"
|
5
5
|
|
6
6
|
module Sidekiq
|
7
|
+
module ActiveJob
|
8
|
+
# @api private
|
9
|
+
class Wrapper
|
10
|
+
include Sidekiq::Job
|
11
|
+
|
12
|
+
def perform(job_data)
|
13
|
+
::ActiveJob::Base.execute(job_data.merge("provider_job_id" => jid))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
7
18
|
class Rails < ::Rails::Engine
|
8
19
|
class Reloader
|
9
20
|
def initialize(app = ::Rails.application)
|
@@ -39,6 +50,7 @@ module Sidekiq
|
|
39
50
|
# end
|
40
51
|
initializer "sidekiq.active_job_integration" do
|
41
52
|
ActiveSupport.on_load(:active_job) do
|
53
|
+
require_relative "../active_job/queue_adapters/sidekiq_adapter"
|
42
54
|
include ::Sidekiq::Job::Options unless respond_to?(:sidekiq_options)
|
43
55
|
end
|
44
56
|
end
|
@@ -64,6 +64,13 @@ module Sidekiq
|
|
64
64
|
opts = client_opts(options)
|
65
65
|
@config = if opts.key?(:sentinels)
|
66
66
|
RedisClient.sentinel(**opts)
|
67
|
+
elsif opts.key?(:nodes)
|
68
|
+
# Sidekiq does not support Redis clustering but Sidekiq Enterprise's
|
69
|
+
# rate limiters are cluster-safe so we can scale to millions
|
70
|
+
# of rate limiters using a Redis cluster. This requires the
|
71
|
+
# `redis-cluster-client` gem.
|
72
|
+
# Sidekiq::Limiter.redis = { nodes: [...] }
|
73
|
+
RedisClient.cluster(**opts)
|
67
74
|
else
|
68
75
|
RedisClient.config(**opts)
|
69
76
|
end
|
@@ -90,13 +97,9 @@ module Sidekiq
|
|
90
97
|
opts.delete(:network_timeout)
|
91
98
|
end
|
92
99
|
|
93
|
-
if opts[:driver]
|
94
|
-
opts[:driver] = opts[:driver].to_sym
|
95
|
-
end
|
96
|
-
|
97
100
|
opts[:name] = opts.delete(:master_name) if opts.key?(:master_name)
|
98
101
|
opts[:role] = opts[:role].to_sym if opts.key?(:role)
|
99
|
-
opts
|
102
|
+
opts[:driver] = opts[:driver].to_sym if opts.key?(:driver)
|
100
103
|
|
101
104
|
# Issue #3303, redis-rb will silently retry an operation.
|
102
105
|
# This can lead to duplicate jobs if Sidekiq::Client's LPUSH
|
@@ -8,17 +8,28 @@ module Sidekiq
|
|
8
8
|
module RedisConnection
|
9
9
|
class << self
|
10
10
|
def create(options = {})
|
11
|
-
symbolized_options = options
|
11
|
+
symbolized_options = deep_symbolize_keys(options)
|
12
12
|
symbolized_options[:url] ||= determine_redis_provider
|
13
13
|
|
14
14
|
logger = symbolized_options.delete(:logger)
|
15
15
|
logger&.info { "Sidekiq #{Sidekiq::VERSION} connecting to Redis with options #{scrub(symbolized_options)}" }
|
16
16
|
|
17
17
|
raise "Sidekiq 7+ does not support Redis protocol 2" if symbolized_options[:protocol] == 2
|
18
|
+
|
19
|
+
safe = !!symbolized_options.delete(:cluster_safe)
|
20
|
+
raise ":nodes not allowed, Sidekiq is not safe to run on Redis Cluster" if !safe && symbolized_options.key?(:nodes)
|
21
|
+
|
18
22
|
size = symbolized_options.delete(:size) || 5
|
19
23
|
pool_timeout = symbolized_options.delete(:pool_timeout) || 1
|
20
24
|
pool_name = symbolized_options.delete(:pool_name)
|
21
25
|
|
26
|
+
# Default timeout in redis-client is 1 second, which can be too aggressive
|
27
|
+
# if the Sidekiq process is CPU-bound. With 10-15 threads and a thread quantum of 100ms,
|
28
|
+
# it can be easy to get the occasional ReadTimeoutError. You can still provide
|
29
|
+
# a smaller timeout explicitly:
|
30
|
+
# config.redis = { url: "...", timeout: 1 }
|
31
|
+
symbolized_options[:timeout] ||= 3
|
32
|
+
|
22
33
|
redis_config = Sidekiq::RedisClientAdapter.new(symbolized_options)
|
23
34
|
ConnectionPool.new(timeout: pool_timeout, size: size, name: pool_name) do
|
24
35
|
redis_config.new_client
|
@@ -27,6 +38,19 @@ module Sidekiq
|
|
27
38
|
|
28
39
|
private
|
29
40
|
|
41
|
+
def deep_symbolize_keys(object)
|
42
|
+
case object
|
43
|
+
when Hash
|
44
|
+
object.each_with_object({}) do |(key, value), result|
|
45
|
+
result[key.to_sym] = deep_symbolize_keys(value)
|
46
|
+
end
|
47
|
+
when Array
|
48
|
+
object.map { |e| deep_symbolize_keys(e) }
|
49
|
+
else
|
50
|
+
object
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
30
54
|
def scrub(options)
|
31
55
|
redacted = "REDACTED"
|
32
56
|
|
@@ -42,7 +66,14 @@ module Sidekiq
|
|
42
66
|
scrubbed_options[:password] = redacted if scrubbed_options[:password]
|
43
67
|
scrubbed_options[:sentinel_password] = redacted if scrubbed_options[:sentinel_password]
|
44
68
|
scrubbed_options[:sentinels]&.each do |sentinel|
|
45
|
-
|
69
|
+
if sentinel.is_a?(String)
|
70
|
+
if (uri = URI(sentinel)) && uri.password
|
71
|
+
uri.password = redacted
|
72
|
+
sentinel.replace(uri.to_s)
|
73
|
+
end
|
74
|
+
elsif sentinel[:password]
|
75
|
+
sentinel[:password] = redacted
|
76
|
+
end
|
46
77
|
end
|
47
78
|
scrubbed_options
|
48
79
|
end
|
data/lib/sidekiq/ring_buffer.rb
CHANGED
data/lib/sidekiq/systemd.rb
CHANGED
data/lib/sidekiq/testing.rb
CHANGED
@@ -283,11 +283,11 @@ module Sidekiq
|
|
283
283
|
end
|
284
284
|
|
285
285
|
def process_job(job)
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
Sidekiq::Testing.server_middleware.invoke(
|
290
|
-
execute_job(
|
286
|
+
instance = new
|
287
|
+
instance.jid = job["jid"]
|
288
|
+
instance.bid = job["bid"] if instance.respond_to?(:bid=)
|
289
|
+
Sidekiq::Testing.server_middleware.invoke(instance, job, job["queue"]) do
|
290
|
+
execute_job(instance, job["args"])
|
291
291
|
end
|
292
292
|
end
|
293
293
|
|
data/lib/sidekiq/version.rb
CHANGED
data/lib/sidekiq/web/action.rb
CHANGED
@@ -27,6 +27,7 @@ module Sidekiq
|
|
27
27
|
redirect current_location
|
28
28
|
end
|
29
29
|
|
30
|
+
# deprecated, will warn in 8.0
|
30
31
|
def params
|
31
32
|
indifferent_hash = Hash.new { |hash, key| hash[key.to_s] if Symbol === key }
|
32
33
|
|
@@ -36,8 +37,19 @@ module Sidekiq
|
|
36
37
|
indifferent_hash
|
37
38
|
end
|
38
39
|
|
39
|
-
|
40
|
-
|
40
|
+
# Use like `url_params("page")` within your action blocks
|
41
|
+
def url_params(key)
|
42
|
+
request.params[key]
|
43
|
+
end
|
44
|
+
|
45
|
+
# Use like `route_params(:name)` within your action blocks
|
46
|
+
# key is required in 8.0, nil is only used for backwards compatibility
|
47
|
+
def route_params(key = nil)
|
48
|
+
if key
|
49
|
+
env[WebRouter::ROUTE_PARAMS][key]
|
50
|
+
else
|
51
|
+
env[WebRouter::ROUTE_PARAMS]
|
52
|
+
end
|
41
53
|
end
|
42
54
|
|
43
55
|
def session
|
@@ -47,8 +59,13 @@ module Sidekiq
|
|
47
59
|
def erb(content, options = {})
|
48
60
|
if content.is_a? Symbol
|
49
61
|
unless respond_to?(:"_erb_#{content}")
|
50
|
-
|
51
|
-
|
62
|
+
views = options[:views] || Web.settings.views
|
63
|
+
filename = "#{views}/#{content}.erb"
|
64
|
+
src = ERB.new(File.read(filename)).src
|
65
|
+
|
66
|
+
# Need to use lineno less by 1 because erb generates a
|
67
|
+
# comment before the source code.
|
68
|
+
WebAction.class_eval <<-RUBY, filename, -1 # standard:disable Style/EvalWithLocation
|
52
69
|
def _erb_#{content}
|
53
70
|
#{src}
|
54
71
|
end
|