resque-admin-scheduler 1.0.2
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 +7 -0
- data/AUTHORS.md +81 -0
- data/CHANGELOG.md +456 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONTRIBUTING.md +6 -0
- data/Gemfile +4 -0
- data/LICENSE +23 -0
- data/README.md +691 -0
- data/Rakefile +21 -0
- data/exe/resque-scheduler +5 -0
- data/lib/resque/scheduler/cli.rb +147 -0
- data/lib/resque/scheduler/configuration.rb +73 -0
- data/lib/resque/scheduler/delaying_extensions.rb +324 -0
- data/lib/resque/scheduler/env.rb +89 -0
- data/lib/resque/scheduler/extension.rb +13 -0
- data/lib/resque/scheduler/failure_handler.rb +11 -0
- data/lib/resque/scheduler/lock/base.rb +61 -0
- data/lib/resque/scheduler/lock/basic.rb +27 -0
- data/lib/resque/scheduler/lock/resilient.rb +78 -0
- data/lib/resque/scheduler/lock.rb +4 -0
- data/lib/resque/scheduler/locking.rb +104 -0
- data/lib/resque/scheduler/logger_builder.rb +72 -0
- data/lib/resque/scheduler/plugin.rb +31 -0
- data/lib/resque/scheduler/scheduling_extensions.rb +141 -0
- data/lib/resque/scheduler/server/views/delayed.erb +63 -0
- data/lib/resque/scheduler/server/views/delayed_schedules.erb +20 -0
- data/lib/resque/scheduler/server/views/delayed_timestamp.erb +26 -0
- data/lib/resque/scheduler/server/views/requeue-params.erb +23 -0
- data/lib/resque/scheduler/server/views/scheduler.erb +58 -0
- data/lib/resque/scheduler/server/views/search.erb +72 -0
- data/lib/resque/scheduler/server/views/search_form.erb +8 -0
- data/lib/resque/scheduler/server.rb +268 -0
- data/lib/resque/scheduler/signal_handling.rb +40 -0
- data/lib/resque/scheduler/tasks.rb +25 -0
- data/lib/resque/scheduler/util.rb +39 -0
- data/lib/resque/scheduler/version.rb +7 -0
- data/lib/resque/scheduler.rb +447 -0
- data/lib/resque-scheduler.rb +4 -0
- data/resque-scheduler.gemspec +60 -0
- metadata +330 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
<h1>Schedule</h1>
|
2
|
+
|
3
|
+
<p class='intro'>
|
4
|
+
The list below contains all scheduled jobs. Click "Queue now" to queue
|
5
|
+
a job immediately.
|
6
|
+
<br/> Server local time: <%= Time.now %>
|
7
|
+
<br/> Server Environment: <%= Resque::Scheduler.env %>
|
8
|
+
<br/> Current master: <%= Resque.redis.get(Resque::Scheduler.master_lock.key) %>
|
9
|
+
</p>
|
10
|
+
<p class='intro'>
|
11
|
+
The highlighted jobs are skipped for current environment.
|
12
|
+
</p>
|
13
|
+
<div style="overflow-y: auto; width:100%; padding: 0px 5px;">
|
14
|
+
<table>
|
15
|
+
<tr>
|
16
|
+
<th>Index</th>
|
17
|
+
<% if Resque::Scheduler.dynamic %>
|
18
|
+
<th></th>
|
19
|
+
<% end %>
|
20
|
+
<th></th>
|
21
|
+
<th>Name</th>
|
22
|
+
<th>Description</th>
|
23
|
+
<th>Interval</th>
|
24
|
+
<th>Class</th>
|
25
|
+
<th>Queue</th>
|
26
|
+
<th>Arguments</th>
|
27
|
+
<th>Last Enqueued</th>
|
28
|
+
</tr>
|
29
|
+
<% Resque.schedule.keys.sort.each_with_index do |name, index| %>
|
30
|
+
<% config = Resque.schedule[name] %>
|
31
|
+
<tr style="<%= scheduled_in_this_env?(name) ? '' : 'color: #9F6000;background: #FEEFB3;' %>">
|
32
|
+
<td style="padding-left: 15px;"><%= index+ 1 %>.</td>
|
33
|
+
<% if Resque::Scheduler.dynamic %>
|
34
|
+
<td style="padding-top: 12px; padding-bottom: 2px; width: 10px">
|
35
|
+
<form action="<%= u "/schedule" %>" method="post" style="margin-left: 0">
|
36
|
+
<input type="hidden" name="job_name" value="<%= h name %>">
|
37
|
+
<input type="hidden" name="_method" value="delete">
|
38
|
+
<input type="submit" value="Delete">
|
39
|
+
</form>
|
40
|
+
</td>
|
41
|
+
<% end %>
|
42
|
+
<td style="padding-top: 12px; padding-bottom: 2px; width: 10px">
|
43
|
+
<form action="<%= u "/schedule/requeue" %>" method="post" style="margin-left: 0">
|
44
|
+
<input type="hidden" name="job_name" value="<%= h name %>">
|
45
|
+
<input type="submit" value="Queue now">
|
46
|
+
</form>
|
47
|
+
</td>
|
48
|
+
<td><%= h name %></td>
|
49
|
+
<td><%= h config['description'] %></td>
|
50
|
+
<td style="white-space:nowrap"><%= h schedule_interval(config) %></td>
|
51
|
+
<td><%= h schedule_class(config) %></td>
|
52
|
+
<td><%= h config['queue'] || queue_from_class_name(config['class']) %></td>
|
53
|
+
<td><%= h show_job_arguments(config['args']) %></td>
|
54
|
+
<td><%= h Resque.get_last_enqueued_at(name) || 'Never' %></td>
|
55
|
+
</tr>
|
56
|
+
<% end %>
|
57
|
+
</table>
|
58
|
+
</div>
|
@@ -0,0 +1,72 @@
|
|
1
|
+
<h1>Search Results</h1>
|
2
|
+
<%= scheduler_view :search_form, layout: false %>
|
3
|
+
<hr>
|
4
|
+
<% delayed = @jobs.select { |j| j['where_at'] == 'delayed' } %>
|
5
|
+
<h1>Delayed jobs</h1>
|
6
|
+
<table class='jobs'>
|
7
|
+
<tr>
|
8
|
+
<th></th>
|
9
|
+
<th></th>
|
10
|
+
<th>Timestamp</th>
|
11
|
+
<th>Class</th>
|
12
|
+
<th>Args</th>
|
13
|
+
</tr>
|
14
|
+
<% delayed.each do |job| %>
|
15
|
+
<tr>
|
16
|
+
<td>
|
17
|
+
<form action="<%= u "/delayed/queue_now" %>" method="post">
|
18
|
+
<input type="hidden" name="timestamp" value="<%= job['timestamp'].to_i %>">
|
19
|
+
<input type="submit" value="Queue now">
|
20
|
+
</form>
|
21
|
+
</td>
|
22
|
+
<td>
|
23
|
+
<form action="<%= u "/delayed/cancel_now" %>" method="post">
|
24
|
+
<input type="hidden" name="timestamp" value="<%= job['timestamp'].to_i %>">
|
25
|
+
<input type="hidden" name="klass" value="<%= job['class'] %>">
|
26
|
+
<input type="hidden" name="args" value="<%= h(Resque.encode job['args']) %>">
|
27
|
+
<input type="submit" value="Cancel Job">
|
28
|
+
</form>
|
29
|
+
</td>
|
30
|
+
<td class='args'><%= format_time(Time.at(job['timestamp'])) %></td>
|
31
|
+
<td class='class'><%= job['class'] %></td>
|
32
|
+
<td class='args'><%= h job['args'].inspect %></td>
|
33
|
+
</tr>
|
34
|
+
<% end %>
|
35
|
+
</table>
|
36
|
+
</h1>
|
37
|
+
|
38
|
+
<% queued = @jobs.select { |j| j['where_at'] == 'queued' } %>
|
39
|
+
<h1>Queued jobs</h1>
|
40
|
+
<table class='jobs'>
|
41
|
+
<tr>
|
42
|
+
<th>Queue</th>
|
43
|
+
<th>Class</th>
|
44
|
+
<th>Args</th>
|
45
|
+
</tr>
|
46
|
+
<% queued.each do |job| %>
|
47
|
+
<tr>
|
48
|
+
<td class='class'><%= job['queue'] %></td>
|
49
|
+
<td class='class'><%= job['class'] %></td>
|
50
|
+
<td class='args'><%= h job['args'].inspect %></td>
|
51
|
+
</tr>
|
52
|
+
<% end %>
|
53
|
+
</table>
|
54
|
+
|
55
|
+
<% working = @jobs.select { |j| j['where_at'] == 'working' } %>
|
56
|
+
<h1>Working jobs</h1>
|
57
|
+
<table class='jobs'>
|
58
|
+
<tr>
|
59
|
+
<th>Queue</th>
|
60
|
+
<th>Class</th>
|
61
|
+
<th>Args</th>
|
62
|
+
</tr>
|
63
|
+
<% working.each do |job| %>
|
64
|
+
<tr>
|
65
|
+
<td class='class'><%= job['queue'] %></td>
|
66
|
+
<td class='class'><%= job['class'] %></td>
|
67
|
+
<td class='args'><%= h job['args'].inspect %></td>
|
68
|
+
</tr>
|
69
|
+
<% end %>
|
70
|
+
</table>
|
71
|
+
|
72
|
+
|
@@ -0,0 +1,268 @@
|
|
1
|
+
# vim:fileencoding=utf-8
|
2
|
+
require 'resque-scheduler'
|
3
|
+
require 'resque/server'
|
4
|
+
require 'tilt/erb'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
# Extend Resque::Server to add tabs
|
8
|
+
module Resque
|
9
|
+
module Scheduler
|
10
|
+
module Server
|
11
|
+
TIMESTAMP_FORMAT = '%Y-%m-%d %H:%M:%S %z'.freeze
|
12
|
+
|
13
|
+
unless defined?(::Resque::Scheduler::Server::VIEW_PATH)
|
14
|
+
VIEW_PATH = File.join(File.dirname(__FILE__), 'server', 'views')
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.included(base)
|
18
|
+
base.class_eval do
|
19
|
+
helpers { include HelperMethods }
|
20
|
+
include ServerMethods
|
21
|
+
|
22
|
+
get('/schedule') { schedule }
|
23
|
+
post('/schedule/requeue') { schedule_requeue }
|
24
|
+
post('/schedule/requeue_with_params') do
|
25
|
+
schedule_requeue_with_params
|
26
|
+
end
|
27
|
+
delete('/schedule') { delete_schedule }
|
28
|
+
get('/delayed') { delayed }
|
29
|
+
get('/delayed/jobs/:klass') { delayed_jobs_klass }
|
30
|
+
post('/delayed/search') { delayed_search }
|
31
|
+
get('/delayed/:timestamp') { delayed_timestamp }
|
32
|
+
post('/delayed/queue_now') { delayed_queue_now }
|
33
|
+
post('/delayed/cancel_now') { delayed_cancel_now }
|
34
|
+
post('/delayed/clear') { delayed_clear }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module ServerMethods
|
39
|
+
def schedule
|
40
|
+
Resque.reload_schedule! if Resque::Scheduler.dynamic
|
41
|
+
erb scheduler_template('scheduler')
|
42
|
+
end
|
43
|
+
|
44
|
+
def schedule_requeue
|
45
|
+
@job_name = params['job_name'] || params[:job_name]
|
46
|
+
config = Resque.schedule[@job_name]
|
47
|
+
@parameters = config['parameters'] || config[:parameters]
|
48
|
+
if @parameters
|
49
|
+
erb scheduler_template('requeue-params')
|
50
|
+
else
|
51
|
+
Resque::Scheduler.enqueue_from_config(config)
|
52
|
+
redirect u('/overview')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def schedule_requeue_with_params
|
57
|
+
job_name = params['job_name'] || params[:job_name]
|
58
|
+
config = Resque.schedule[job_name]
|
59
|
+
# Build args hash from post data (removing the job name)
|
60
|
+
submitted_args = params.reject do |key, _value|
|
61
|
+
key == 'job_name' || key == :job_name
|
62
|
+
end
|
63
|
+
|
64
|
+
# Merge constructed args hash with existing args hash for
|
65
|
+
# the job, if it exists
|
66
|
+
config_args = config['args'] || config[:args] || {}
|
67
|
+
config_args = config_args.merge(submitted_args)
|
68
|
+
|
69
|
+
# Insert the args hash into config and queue the resque job
|
70
|
+
config = config.merge('args' => config_args)
|
71
|
+
Resque::Scheduler.enqueue_from_config(config)
|
72
|
+
redirect u('/overview')
|
73
|
+
end
|
74
|
+
|
75
|
+
def delete_schedule
|
76
|
+
if Resque::Scheduler.dynamic
|
77
|
+
job_name = params['job_name'] || params[:job_name]
|
78
|
+
Resque.remove_schedule(job_name)
|
79
|
+
end
|
80
|
+
redirect u('/schedule')
|
81
|
+
end
|
82
|
+
|
83
|
+
def delayed
|
84
|
+
erb scheduler_template('delayed')
|
85
|
+
end
|
86
|
+
|
87
|
+
def delayed_jobs_klass
|
88
|
+
begin
|
89
|
+
klass = Resque::Scheduler::Util.constantize(params[:klass])
|
90
|
+
@args = JSON.load(URI.decode(params[:args]))
|
91
|
+
@timestamps = Resque.scheduled_at(klass, *@args)
|
92
|
+
rescue
|
93
|
+
@timestamps = []
|
94
|
+
end
|
95
|
+
|
96
|
+
erb scheduler_template('delayed_schedules')
|
97
|
+
end
|
98
|
+
|
99
|
+
def delayed_search
|
100
|
+
@jobs = find_job(params[:search])
|
101
|
+
erb scheduler_template('search')
|
102
|
+
end
|
103
|
+
|
104
|
+
def delayed_timestamp
|
105
|
+
erb scheduler_template('delayed_timestamp')
|
106
|
+
end
|
107
|
+
|
108
|
+
def delayed_queue_now
|
109
|
+
timestamp = params['timestamp'].to_i
|
110
|
+
formatted_time = Time.at(timestamp).strftime(
|
111
|
+
::Resque::Scheduler::Server::TIMESTAMP_FORMAT
|
112
|
+
)
|
113
|
+
|
114
|
+
if timestamp > 0
|
115
|
+
unless Resque::Scheduler.enqueue_next_item(timestamp)
|
116
|
+
@error_message = "Unable to remove item at #{formatted_time}"
|
117
|
+
end
|
118
|
+
else
|
119
|
+
@error_message = "Incorrect timestamp #{formatted_time}"
|
120
|
+
end
|
121
|
+
|
122
|
+
erb scheduler_template('delayed')
|
123
|
+
end
|
124
|
+
|
125
|
+
def delayed_cancel_now
|
126
|
+
klass = Resque::Scheduler::Util.constantize(params['klass'])
|
127
|
+
timestamp = params['timestamp']
|
128
|
+
args = Resque.decode params['args']
|
129
|
+
Resque.remove_delayed_job_from_timestamp(timestamp, klass, *args)
|
130
|
+
redirect u('/delayed')
|
131
|
+
end
|
132
|
+
|
133
|
+
def delayed_clear
|
134
|
+
Resque.reset_delayed_queue
|
135
|
+
redirect u('delayed')
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
module HelperMethods
|
140
|
+
def format_time(t)
|
141
|
+
t.strftime(::Resque::Scheduler::Server::TIMESTAMP_FORMAT)
|
142
|
+
end
|
143
|
+
|
144
|
+
def show_job_arguments(args)
|
145
|
+
Array(args).map(&:inspect).join("\n")
|
146
|
+
end
|
147
|
+
|
148
|
+
def queue_from_class_name(class_name)
|
149
|
+
Resque.queue_from_class(
|
150
|
+
Resque::Scheduler::Util.constantize(class_name)
|
151
|
+
)
|
152
|
+
end
|
153
|
+
|
154
|
+
def find_job(worker)
|
155
|
+
worker = worker.downcase
|
156
|
+
results = working_jobs_for_worker(worker)
|
157
|
+
|
158
|
+
dels = delayed_jobs_for_worker(worker)
|
159
|
+
results += dels.select do |j|
|
160
|
+
j['class'].downcase.include?(worker) &&
|
161
|
+
j.merge!('where_at' => 'delayed')
|
162
|
+
end
|
163
|
+
|
164
|
+
Resque.queues.each do |queue|
|
165
|
+
queued = Resque.peek(queue, 0, Resque.size(queue))
|
166
|
+
queued = [queued] unless queued.is_a?(Array)
|
167
|
+
results += queued.select do |j|
|
168
|
+
j['class'].downcase.include?(worker) &&
|
169
|
+
j.merge!('queue' => queue, 'where_at' => 'queued')
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
results
|
174
|
+
end
|
175
|
+
|
176
|
+
def schedule_interval(config)
|
177
|
+
if config['every']
|
178
|
+
schedule_interval_every(config['every'])
|
179
|
+
elsif config['cron']
|
180
|
+
'cron: ' + config['cron'].to_s
|
181
|
+
else
|
182
|
+
'Not currently scheduled'
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def schedule_interval_every(every)
|
187
|
+
every = [*every]
|
188
|
+
s = 'every: ' << every.first
|
189
|
+
|
190
|
+
return s unless every.length > 1
|
191
|
+
|
192
|
+
s << ' ('
|
193
|
+
meta = every.last.map do |key, value|
|
194
|
+
"#{key.to_s.tr('_', ' ')} #{value}"
|
195
|
+
end
|
196
|
+
s << meta.join(', ') << ')'
|
197
|
+
end
|
198
|
+
|
199
|
+
def schedule_class(config)
|
200
|
+
if config['class'].nil? && !config['custom_job_class'].nil?
|
201
|
+
config['custom_job_class']
|
202
|
+
else
|
203
|
+
config['class']
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
def scheduler_template(name)
|
208
|
+
File.read(
|
209
|
+
File.expand_path("../server/views/#{name}.erb", __FILE__)
|
210
|
+
)
|
211
|
+
end
|
212
|
+
|
213
|
+
def scheduled_in_this_env?(name)
|
214
|
+
return true if rails_env(name).nil?
|
215
|
+
rails_env(name).split(/[\s,]+/).include?(Resque::Scheduler.env)
|
216
|
+
end
|
217
|
+
|
218
|
+
def rails_env(name)
|
219
|
+
Resque.schedule[name]['rails_env'] || Resque.schedule[name]['env']
|
220
|
+
end
|
221
|
+
|
222
|
+
def scheduler_view(filename, options = {}, locals = {})
|
223
|
+
source = File.read(File.join(VIEW_PATH, "#{filename}.erb"))
|
224
|
+
erb source, options, locals
|
225
|
+
end
|
226
|
+
|
227
|
+
private
|
228
|
+
|
229
|
+
def working_jobs_for_worker(worker)
|
230
|
+
[].tap do |results|
|
231
|
+
working = [*Resque.working]
|
232
|
+
work = working.select do |w|
|
233
|
+
w.job && w.job['payload'] &&
|
234
|
+
w.job['payload']['class'].downcase.include?(worker)
|
235
|
+
end
|
236
|
+
work.each do |w|
|
237
|
+
results += [
|
238
|
+
w.job['payload'].merge(
|
239
|
+
'queue' => w.job['queue'], 'where_at' => 'working'
|
240
|
+
)
|
241
|
+
]
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
def delayed_jobs_for_worker(_worker)
|
247
|
+
[].tap do |dels|
|
248
|
+
schedule_size = Resque.delayed_queue_schedule_size
|
249
|
+
Resque.delayed_queue_peek(0, schedule_size).each do |d|
|
250
|
+
Resque.delayed_timestamp_peek(
|
251
|
+
d, 0, Resque.delayed_timestamp_size(d)
|
252
|
+
).each do |j|
|
253
|
+
dels << j.merge!('timestamp' => d)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
Resque::Server.tabs << 'Schedule'
|
264
|
+
Resque::Server.tabs << 'Delayed'
|
265
|
+
|
266
|
+
Resque::Server.class_eval do
|
267
|
+
include Resque::Scheduler::Server
|
268
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# vim:fileencoding=utf-8
|
2
|
+
|
3
|
+
module Resque
|
4
|
+
module Scheduler
|
5
|
+
module SignalHandling
|
6
|
+
attr_writer :signal_queue
|
7
|
+
|
8
|
+
def signal_queue
|
9
|
+
@signal_queue ||= []
|
10
|
+
end
|
11
|
+
|
12
|
+
# For all signals, set the shutdown flag and wait for current
|
13
|
+
# poll/enqueing to finish (should be almost instant). In the
|
14
|
+
# case of sleeping, exit immediately.
|
15
|
+
def register_signal_handlers
|
16
|
+
(Signal.list.keys & %w(INT TERM USR1 USR2 QUIT)).each do |sig|
|
17
|
+
trap(sig) do
|
18
|
+
signal_queue << sig
|
19
|
+
# break sleep in the primary scheduler thread, alowing
|
20
|
+
# the signal queue to get processed as soon as possible.
|
21
|
+
@th.wakeup if @th && @th.alive?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def handle_signals
|
27
|
+
loop do
|
28
|
+
sig = signal_queue.shift
|
29
|
+
break unless sig
|
30
|
+
log! "Got #{sig} signal"
|
31
|
+
case sig
|
32
|
+
when 'INT', 'TERM', 'QUIT' then shutdown
|
33
|
+
when 'USR1' then print_schedule
|
34
|
+
when 'USR2' then reload_schedule!
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# vim:fileencoding=utf-8
|
2
|
+
|
3
|
+
require 'resque/tasks'
|
4
|
+
require 'resque-scheduler'
|
5
|
+
|
6
|
+
namespace :resque do
|
7
|
+
task :setup
|
8
|
+
|
9
|
+
def scheduler_cli
|
10
|
+
@scheduler_cli ||= Resque::Scheduler::Cli.new(
|
11
|
+
%W(#{ENV['RESQUE_SCHEDULER_OPTIONS']})
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Start Resque Scheduler'
|
16
|
+
task scheduler: :scheduler_setup do
|
17
|
+
scheduler_cli.setup_env
|
18
|
+
scheduler_cli.run_forever
|
19
|
+
end
|
20
|
+
|
21
|
+
task :scheduler_setup do
|
22
|
+
scheduler_cli.parse_options
|
23
|
+
Rake::Task['resque:setup'].invoke unless scheduler_cli.pre_setup
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# vim:fileencoding=utf-8
|
2
|
+
|
3
|
+
module Resque
|
4
|
+
module Scheduler
|
5
|
+
class Util
|
6
|
+
# In order to upgrade to resque(1.25) which has deprecated following
|
7
|
+
# methods, we just added these usefull helpers back to use in Resque
|
8
|
+
# Scheduler. refer to:
|
9
|
+
# https://github.com/resque/resque-scheduler/pull/273
|
10
|
+
|
11
|
+
def self.constantize(camel_cased_word)
|
12
|
+
camel_cased_word = camel_cased_word.to_s
|
13
|
+
|
14
|
+
if camel_cased_word.include?('-')
|
15
|
+
camel_cased_word = classify(camel_cased_word)
|
16
|
+
end
|
17
|
+
|
18
|
+
names = camel_cased_word.split('::')
|
19
|
+
names.shift if names.empty? || names.first.empty?
|
20
|
+
|
21
|
+
constant = Object
|
22
|
+
names.each do |name|
|
23
|
+
args = Module.method(:const_get).arity != 1 ? [false] : []
|
24
|
+
|
25
|
+
constant = if constant.const_defined?(name, *args)
|
26
|
+
constant.const_get(name)
|
27
|
+
else
|
28
|
+
constant.const_missing(name)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
constant
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.classify(dashed_word)
|
35
|
+
dashed_word.split('-').map(&:capitalize).join
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|