activejob 0 → 4.2.0.beta1
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/README.md +141 -0
- data/lib/active_job.rb +1 -1
- data/lib/active_job/arguments.rb +62 -0
- data/lib/active_job/base.rb +12 -3
- data/lib/active_job/callbacks.rb +144 -0
- data/lib/active_job/enqueuing.rb +65 -12
- data/lib/active_job/execution.rb +33 -0
- data/lib/active_job/gem_version.rb +1 -1
- data/lib/active_job/identifier.rb +15 -0
- data/lib/active_job/logging.rb +85 -2
- data/lib/active_job/queue_adapters/backburner_adapter.rb +6 -2
- data/lib/active_job/queue_adapters/delayed_job_adapter.rb +7 -3
- data/lib/active_job/queue_adapters/inline_adapter.rb +7 -3
- data/lib/active_job/queue_adapters/qu_adapter.rb +30 -0
- data/lib/active_job/queue_adapters/que_adapter.rb +8 -4
- data/lib/active_job/queue_adapters/queue_classic_adapter.rb +10 -4
- data/lib/active_job/queue_adapters/resque_adapter.rb +21 -11
- data/lib/active_job/queue_adapters/sidekiq_adapter.rb +12 -2
- data/lib/active_job/queue_adapters/sneakers_adapter.rb +14 -7
- data/lib/active_job/queue_adapters/sucker_punch_adapter.rb +7 -3
- data/lib/active_job/queue_name.rb +16 -5
- data/lib/active_job/railtie.rb +23 -0
- data/lib/rails/generators/job/job_generator.rb +22 -0
- data/lib/rails/generators/job/templates/job.rb +9 -0
- metadata +17 -26
- data/lib/active_job/log_subscriber.rb +0 -19
- data/lib/active_job/parameters.rb +0 -19
- data/lib/active_job/railitie.rb +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f5b4155049f893704d35f9712dacadac5c6a63a3
|
4
|
+
data.tar.gz: c072445cde94c55babc2a55489e877ff7930529e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 563242f9567f12e8cb4ecb8204fc9a0956c93507ddea5eca3d3fe824157aca34afacfd47eff94e73aadadedbfb8972e058af16cb226f9ca53959cab3b8cf2de3
|
7
|
+
data.tar.gz: fbc34e79c27f8ce715d350c740b149b8fe02decf1d3175a6c1ab3a859b93caf963a094c1c5c5d01c6b22f8f5bb9cd093504f2263d10f4ca50b0319ef334f66c6
|
data/README.md
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
# Active Job -- Make work happen later
|
2
|
+
|
3
|
+
Active Job is a framework for declaring jobs and making them run on a variety
|
4
|
+
of queueing backends. These jobs can be everything from regularly scheduled
|
5
|
+
clean-ups, billing charges, or mailings. Anything that can be chopped up into
|
6
|
+
small units of work and run in parallel, really.
|
7
|
+
|
8
|
+
It also serves as the backend for ActionMailer's #deliver_later functionality
|
9
|
+
that makes it easy to turn any mailing into a job for running later. That's
|
10
|
+
one of the most common jobs in a modern web application: Sending emails outside
|
11
|
+
of the request-response cycle, so the user doesn't have to wait on it.
|
12
|
+
|
13
|
+
The main point is to ensure that all Rails apps will have a job infrastructure
|
14
|
+
in place, even if it's in the form of an "immediate runner". We can then have
|
15
|
+
framework features and other gems build on top of that, without having to worry
|
16
|
+
about API differences between Delayed Job and Resque. Picking your queuing
|
17
|
+
backend becomes more of an operational concern, then. And you'll be able to
|
18
|
+
switch between them without having to rewrite your jobs.
|
19
|
+
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
Set the queue adapter for Active Job:
|
24
|
+
|
25
|
+
``` ruby
|
26
|
+
ActiveJob::Base.queue_adapter = :inline # default queue adapter
|
27
|
+
# Adapters currently supported: :backburner, :delayed_job, :qu, :que, :queue_classic,
|
28
|
+
# :resque, :sidekiq, :sneakers, :sucker_punch
|
29
|
+
```
|
30
|
+
|
31
|
+
Declare a job like so:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
class MyJob < ActiveJob::Base
|
35
|
+
queue_as :my_jobs
|
36
|
+
|
37
|
+
def perform(record)
|
38
|
+
record.do_work
|
39
|
+
end
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
Enqueue a job like so:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
MyJob.enqueue record # Enqueue a job to be performed as soon the queueing system is free.
|
47
|
+
```
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
MyJob.enqueue_at Date.tomorrow.noon, record # Enqueue a job to be performed tomorrow at noon.
|
51
|
+
```
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
MyJob.enqueue_in 1.week, record # Enqueue a job to be performed 1 week from now.
|
55
|
+
```
|
56
|
+
|
57
|
+
That's it!
|
58
|
+
|
59
|
+
|
60
|
+
## GlobalID support
|
61
|
+
|
62
|
+
Active Job supports [GlobalID serialization](https://github.com/rails/globalid/) for parameters. This makes it possible
|
63
|
+
to pass live Active Record objects to your job instead of class/id pairs, which
|
64
|
+
you then have to manually deserialize. Before, jobs would look like this:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
class TrashableCleanupJob
|
68
|
+
def perform(trashable_class, trashable_id, depth)
|
69
|
+
trashable = trashable_class.constantize.find(trashable_id)
|
70
|
+
trashable.cleanup(depth)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
```
|
74
|
+
|
75
|
+
Now you can simply do:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
class TrashableCleanupJob
|
79
|
+
def perform(trashable, depth)
|
80
|
+
trashable.cleanup(depth)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
```
|
84
|
+
|
85
|
+
This works with any class that mixes in GlobalID::Identification, which
|
86
|
+
by default has been mixed into Active Record classes.
|
87
|
+
|
88
|
+
|
89
|
+
## Supported queueing systems
|
90
|
+
|
91
|
+
We currently have adapters for:
|
92
|
+
|
93
|
+
* [Backburner](https://github.com/nesquena/backburner)
|
94
|
+
* [Delayed Job](https://github.com/collectiveidea/delayed_job)
|
95
|
+
* [Qu](https://github.com/bkeepers/qu)
|
96
|
+
* [Que](https://github.com/chanks/que)
|
97
|
+
* [QueueClassic](https://github.com/ryandotsmith/queue_classic)
|
98
|
+
* [Resque 1.x](https://github.com/resque/resque)
|
99
|
+
* [Sidekiq](https://github.com/mperham/sidekiq)
|
100
|
+
* [Sneakers](https://github.com/jondot/sneakers)
|
101
|
+
* [Sucker Punch](https://github.com/brandonhilkert/sucker_punch)
|
102
|
+
|
103
|
+
|
104
|
+
## Auxiliary gems
|
105
|
+
|
106
|
+
* [activejob-stats](https://github.com/seuros/activejob-stats)
|
107
|
+
|
108
|
+
## Download and installation
|
109
|
+
|
110
|
+
The latest version of Active Job can be installed with RubyGems:
|
111
|
+
|
112
|
+
```
|
113
|
+
% [sudo] gem install activejob
|
114
|
+
```
|
115
|
+
|
116
|
+
Source code can be downloaded as part of the Rails project on GitHub
|
117
|
+
|
118
|
+
* https://github.com/rails/rails/tree/master/activejob
|
119
|
+
|
120
|
+
## License
|
121
|
+
|
122
|
+
ActiveJob is released under the MIT license:
|
123
|
+
|
124
|
+
* http://www.opensource.org/licenses/MIT
|
125
|
+
|
126
|
+
|
127
|
+
## Support
|
128
|
+
|
129
|
+
API documentation is at
|
130
|
+
|
131
|
+
* http://api.rubyonrails.org
|
132
|
+
|
133
|
+
Bug reports can be filed for the Ruby on Rails project here:
|
134
|
+
|
135
|
+
* https://github.com/rails/rails/issues
|
136
|
+
|
137
|
+
Feature requests should be discussed on the rails-core mailing list here:
|
138
|
+
|
139
|
+
* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
|
140
|
+
|
141
|
+
|
data/lib/active_job.rb
CHANGED
@@ -0,0 +1,62 @@
|
|
1
|
+
module ActiveJob
|
2
|
+
class DeserializationError < StandardError
|
3
|
+
attr_reader :original_exception
|
4
|
+
|
5
|
+
def initialize(e)
|
6
|
+
super ("Error while trying to deserialize arguments: #{e.message}")
|
7
|
+
@original_exception = e
|
8
|
+
set_backtrace e.backtrace
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module Arguments
|
13
|
+
extend self
|
14
|
+
TYPE_WHITELIST = [ NilClass, Fixnum, Float, String, TrueClass, FalseClass, Bignum ]
|
15
|
+
|
16
|
+
def serialize(arguments)
|
17
|
+
arguments.map { |argument| serialize_argument(argument) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def deserialize(arguments)
|
21
|
+
arguments.map { |argument| deserialize_argument(argument) }
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def serialize_argument(argument)
|
26
|
+
case argument
|
27
|
+
when GlobalID::Identification
|
28
|
+
argument.global_id.to_s
|
29
|
+
when *TYPE_WHITELIST
|
30
|
+
argument
|
31
|
+
when Array
|
32
|
+
serialize(argument)
|
33
|
+
when Hash
|
34
|
+
Hash[ argument.map { |key, value| [ serialize_hash_key(key), serialize_argument(value) ] } ]
|
35
|
+
else
|
36
|
+
raise "Unsupported argument type: #{argument.class.name}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def deserialize_argument(argument)
|
41
|
+
case argument
|
42
|
+
when Array
|
43
|
+
deserialize(argument)
|
44
|
+
when Hash
|
45
|
+
Hash[ argument.map { |key, value| [ key, deserialize_argument(value) ] } ].with_indifferent_access
|
46
|
+
else
|
47
|
+
GlobalID::Locator.locate(argument) || argument
|
48
|
+
end
|
49
|
+
rescue => e
|
50
|
+
raise DeserializationError.new(e)
|
51
|
+
end
|
52
|
+
|
53
|
+
def serialize_hash_key(key)
|
54
|
+
case key
|
55
|
+
when String, Symbol
|
56
|
+
key.to_s
|
57
|
+
else
|
58
|
+
raise "Unsupported hash key type: #{key.class.name}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/active_job/base.rb
CHANGED
@@ -1,13 +1,22 @@
|
|
1
1
|
require 'active_job/queue_adapter'
|
2
2
|
require 'active_job/queue_name'
|
3
3
|
require 'active_job/enqueuing'
|
4
|
+
require 'active_job/execution'
|
5
|
+
require 'active_job/callbacks'
|
6
|
+
require 'active_job/identifier'
|
4
7
|
require 'active_job/logging'
|
5
8
|
|
6
9
|
module ActiveJob
|
7
10
|
class Base
|
8
11
|
extend QueueAdapter
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
+
|
13
|
+
include QueueName
|
14
|
+
include Enqueuing
|
15
|
+
include Execution
|
16
|
+
include Callbacks
|
17
|
+
include Identifier
|
18
|
+
include Logging
|
19
|
+
|
20
|
+
ActiveSupport.run_load_hooks(:active_job, self)
|
12
21
|
end
|
13
22
|
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'active_support/callbacks'
|
2
|
+
|
3
|
+
module ActiveJob
|
4
|
+
# = Active Job Callbacks
|
5
|
+
#
|
6
|
+
# Active Job provides hooks during the lifecycle of a job. Callbacks allows you to trigger
|
7
|
+
# logic during the lifecycle of a job. Available callbacks:
|
8
|
+
#
|
9
|
+
# * <tt>before_enqueue</tt>
|
10
|
+
# * <tt>around_enqueue</tt>
|
11
|
+
# * <tt>after_enqueue</tt>
|
12
|
+
# * <tt>before_perform</tt>
|
13
|
+
# * <tt>around_perform</tt>
|
14
|
+
# * <tt>after_perform</tt>
|
15
|
+
#
|
16
|
+
module Callbacks
|
17
|
+
extend ActiveSupport::Concern
|
18
|
+
include ActiveSupport::Callbacks
|
19
|
+
|
20
|
+
included do
|
21
|
+
define_callbacks :perform
|
22
|
+
define_callbacks :enqueue
|
23
|
+
end
|
24
|
+
|
25
|
+
module ClassMethods
|
26
|
+
# Defines a callback that will get called right before the
|
27
|
+
# job's perform method is executed.
|
28
|
+
#
|
29
|
+
# class VideoProcessJob < ActiveJob::Base
|
30
|
+
# queue_as :default
|
31
|
+
#
|
32
|
+
# before_perform do |job|
|
33
|
+
# UserMailer.notify_video_started_processing(job.arguments.first)
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# def perform(video_id)
|
37
|
+
# Video.find(video_id).process
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
def before_perform(*filters, &blk)
|
42
|
+
set_callback(:perform, :before, *filters, &blk)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Defines a callback that will get called right after the
|
46
|
+
# job's perform method has finished.
|
47
|
+
#
|
48
|
+
# class VideoProcessJob < ActiveJob::Base
|
49
|
+
# queue_as :default
|
50
|
+
#
|
51
|
+
# after_perform do |job|
|
52
|
+
# UserMailer.notify_video_processed(job.arguments.first)
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# def perform(video_id)
|
56
|
+
# Video.find(video_id).process
|
57
|
+
# end
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
def after_perform(*filters, &blk)
|
61
|
+
set_callback(:perform, :after, *filters, &blk)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Defines a callback that will get called around the job's perform method.
|
65
|
+
#
|
66
|
+
# class VideoProcessJob < ActiveJob::Base
|
67
|
+
# queue_as :default
|
68
|
+
#
|
69
|
+
# around_perform do |job, block|
|
70
|
+
# UserMailer.notify_video_started_processing(job.arguments.first)
|
71
|
+
# block.call
|
72
|
+
# UserMailer.notify_video_processed(job.arguments.first)
|
73
|
+
# end
|
74
|
+
#
|
75
|
+
# def perform(video_id)
|
76
|
+
# Video.find(video_id).process
|
77
|
+
# end
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
def around_perform(*filters, &blk)
|
81
|
+
set_callback(:perform, :around, *filters, &blk)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Defines a callback that will get called right before the
|
85
|
+
# job is enqueued.
|
86
|
+
#
|
87
|
+
# class VideoProcessJob < ActiveJob::Base
|
88
|
+
# queue_as :default
|
89
|
+
#
|
90
|
+
# before_enqueue do |job|
|
91
|
+
# $statsd.increment "enqueue-video-job.try"
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# def perform(video_id)
|
95
|
+
# Video.find(video_id).process
|
96
|
+
# end
|
97
|
+
# end
|
98
|
+
#
|
99
|
+
def before_enqueue(*filters, &blk)
|
100
|
+
set_callback(:enqueue, :before, *filters, &blk)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Defines a callback that will get called right after the
|
104
|
+
# job is enqueued.
|
105
|
+
#
|
106
|
+
# class VideoProcessJob < ActiveJob::Base
|
107
|
+
# queue_as :default
|
108
|
+
#
|
109
|
+
# after_enqueue do |job|
|
110
|
+
# $statsd.increment "enqueue-video-job.success"
|
111
|
+
# end
|
112
|
+
#
|
113
|
+
# def perform(video_id)
|
114
|
+
# Video.find(video_id).process
|
115
|
+
# end
|
116
|
+
# end
|
117
|
+
#
|
118
|
+
def after_enqueue(*filters, &blk)
|
119
|
+
set_callback(:enqueue, :after, *filters, &blk)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Defines a callback that will get called before and after the
|
123
|
+
# job is enqueued.
|
124
|
+
#
|
125
|
+
# class VideoProcessJob < ActiveJob::Base
|
126
|
+
# queue_as :default
|
127
|
+
#
|
128
|
+
# around_enqueue do |job, block|
|
129
|
+
# $statsd.time "video-job.process" do
|
130
|
+
# block.call
|
131
|
+
# end
|
132
|
+
# end
|
133
|
+
#
|
134
|
+
# def perform(video_id)
|
135
|
+
# Video.find(video_id).process
|
136
|
+
# end
|
137
|
+
# end
|
138
|
+
#
|
139
|
+
def around_enqueue(*filters, &blk)
|
140
|
+
set_callback(:enqueue, :around, *filters, &blk)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
data/lib/active_job/enqueuing.rb
CHANGED
@@ -1,18 +1,71 @@
|
|
1
|
-
require 'active_job/
|
1
|
+
require 'active_job/arguments'
|
2
2
|
|
3
3
|
module ActiveJob
|
4
4
|
module Enqueuing
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
# Push a job onto the queue. The arguments must be legal JSON types
|
9
|
+
# (string, int, float, nil, true, false, hash or array) or
|
10
|
+
# GlobalID::Identification instances. Arbitrary Ruby objects
|
11
|
+
# are not supported.
|
12
|
+
#
|
13
|
+
# Returns an instance of the job class queued with args available in
|
14
|
+
# Job#arguments.
|
15
|
+
def enqueue(*args)
|
16
|
+
new(args).tap do |job|
|
17
|
+
job.run_callbacks :enqueue do
|
18
|
+
queue_adapter.enqueue self, job.job_id, *Arguments.serialize(args)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Enqueue a job to be performed at +interval+ from now.
|
24
|
+
#
|
25
|
+
# enqueue_in(1.week, "mike")
|
26
|
+
#
|
27
|
+
# Returns an instance of the job class queued with args available in
|
28
|
+
# Job#arguments and the timestamp in Job#enqueue_at.
|
29
|
+
def enqueue_in(interval, *args)
|
30
|
+
enqueue_at interval.seconds.from_now, *args
|
31
|
+
end
|
32
|
+
|
33
|
+
# Enqueue a job to be performed at an explicit point in time.
|
34
|
+
#
|
35
|
+
# enqueue_at(Date.tomorrow.midnight, "mike")
|
36
|
+
#
|
37
|
+
# Returns an instance of the job class queued with args available in
|
38
|
+
# Job#arguments and the timestamp in Job#enqueue_at.
|
39
|
+
def enqueue_at(timestamp, *args)
|
40
|
+
new(args).tap do |job|
|
41
|
+
job.enqueued_at = timestamp
|
42
|
+
|
43
|
+
job.run_callbacks :enqueue do
|
44
|
+
queue_adapter.enqueue_at self, timestamp.to_f, job.job_id, *Arguments.serialize(args)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
included do
|
51
|
+
attr_accessor :arguments
|
52
|
+
attr_accessor :enqueued_at
|
53
|
+
end
|
54
|
+
|
55
|
+
def initialize(arguments = nil)
|
56
|
+
@arguments = arguments
|
57
|
+
end
|
58
|
+
|
59
|
+
def retry_now
|
60
|
+
self.class.enqueue(*arguments)
|
61
|
+
end
|
62
|
+
|
63
|
+
def retry_in(interval)
|
64
|
+
self.class.enqueue_in interval, *arguments
|
65
|
+
end
|
66
|
+
|
67
|
+
def retry_at(timestamp)
|
68
|
+
self.class.enqueue_at timestamp, *arguments
|
16
69
|
end
|
17
70
|
end
|
18
71
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'active_support/rescuable'
|
2
|
+
require 'active_job/arguments'
|
3
|
+
|
4
|
+
module ActiveJob
|
5
|
+
module Execution
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
include ActiveSupport::Rescuable
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute(job_id, *serialized_args)
|
13
|
+
self.job_id = job_id
|
14
|
+
self.arguments = deserialize_arguments(serialized_args)
|
15
|
+
|
16
|
+
run_callbacks :perform do
|
17
|
+
perform(*arguments)
|
18
|
+
end
|
19
|
+
rescue => exception
|
20
|
+
rescue_with_handler(exception) || raise(exception)
|
21
|
+
end
|
22
|
+
|
23
|
+
def perform(*)
|
24
|
+
fail NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def deserialize_arguments(serialized_args)
|
29
|
+
Arguments.deserialize(serialized_args)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
data/lib/active_job/logging.rb
CHANGED
@@ -1,7 +1,90 @@
|
|
1
|
-
require '
|
1
|
+
require 'active_support/core_ext/string/filters'
|
2
|
+
require 'active_support/tagged_logging'
|
3
|
+
require 'active_support/logger'
|
2
4
|
|
3
5
|
module ActiveJob
|
4
6
|
module Logging
|
5
|
-
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
cattr_accessor(:logger) { ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT)) }
|
11
|
+
|
12
|
+
around_enqueue do |_, block, _|
|
13
|
+
tag_logger do
|
14
|
+
block.call
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
around_perform do |job, block, _|
|
19
|
+
tag_logger(job.class.name, job.job_id) do
|
20
|
+
payload = {adapter: job.class.queue_adapter, job: job.class, args: job.arguments}
|
21
|
+
ActiveSupport::Notifications.instrument("perform_start.active_job", payload.dup)
|
22
|
+
ActiveSupport::Notifications.instrument("perform.active_job", payload) do
|
23
|
+
block.call
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
before_enqueue do |job|
|
29
|
+
if job.enqueued_at
|
30
|
+
ActiveSupport::Notifications.instrument "enqueue_at.active_job",
|
31
|
+
adapter: job.class.queue_adapter, job: job.class, job_id: job.job_id, args: job.arguments, timestamp: job.enqueued_at
|
32
|
+
else
|
33
|
+
ActiveSupport::Notifications.instrument "enqueue.active_job",
|
34
|
+
adapter: job.class.queue_adapter, job: job.class, job_id: job.job_id, args: job.arguments
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def tag_logger(*tags)
|
41
|
+
if logger.respond_to?(:tagged)
|
42
|
+
tags.unshift "ActiveJob" unless logger_tagged_by_active_job?
|
43
|
+
ActiveJob::Base.logger.tagged(*tags){ yield }
|
44
|
+
else
|
45
|
+
yield
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def logger_tagged_by_active_job?
|
50
|
+
logger.formatter.current_tags.include?("ActiveJob")
|
51
|
+
end
|
52
|
+
|
53
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
54
|
+
def enqueue(event)
|
55
|
+
info "Enqueued #{event.payload[:job].name} (Job ID: #{event.payload[:job_id]}) to #{queue_name(event)}" + args_info(event)
|
56
|
+
end
|
57
|
+
|
58
|
+
def enqueue_at(event)
|
59
|
+
info "Enqueued #{event.payload[:job].name} (Job ID: #{event.payload[:job_id]}) to #{queue_name(event)} at #{enqueued_at(event)}" + args_info(event)
|
60
|
+
end
|
61
|
+
|
62
|
+
def perform_start(event)
|
63
|
+
info "Performing #{event.payload[:job].name} from #{queue_name(event)}" + args_info(event)
|
64
|
+
end
|
65
|
+
|
66
|
+
def perform(event)
|
67
|
+
info "Performed #{event.payload[:job].name} from #{queue_name(event)} in #{event.duration.round(2).to_s}ms"
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
def queue_name(event)
|
72
|
+
event.payload[:adapter].name.demodulize.remove('Adapter') + "(#{event.payload[:job].queue_name})"
|
73
|
+
end
|
74
|
+
|
75
|
+
def args_info(event)
|
76
|
+
event.payload[:args].any? ? " with arguments: #{event.payload[:args].map(&:inspect).join(", ")}" : ""
|
77
|
+
end
|
78
|
+
|
79
|
+
def enqueued_at(event)
|
80
|
+
Time.at(event.payload[:timestamp]).utc
|
81
|
+
end
|
82
|
+
|
83
|
+
def logger
|
84
|
+
ActiveJob::Base.logger
|
85
|
+
end
|
86
|
+
end
|
6
87
|
end
|
7
88
|
end
|
89
|
+
|
90
|
+
ActiveJob::Logging::LogSubscriber.attach_to :active_job
|
@@ -4,15 +4,19 @@ module ActiveJob
|
|
4
4
|
module QueueAdapters
|
5
5
|
class BackburnerAdapter
|
6
6
|
class << self
|
7
|
-
def
|
7
|
+
def enqueue(job, *args)
|
8
8
|
Backburner::Worker.enqueue JobWrapper, [ job.name, *args ], queue: job.queue_name
|
9
9
|
end
|
10
|
+
|
11
|
+
def enqueue_at(job, timestamp, *args)
|
12
|
+
raise NotImplementedError
|
13
|
+
end
|
10
14
|
end
|
11
15
|
|
12
16
|
class JobWrapper
|
13
17
|
class << self
|
14
18
|
def perform(job_name, *args)
|
15
|
-
job_name.constantize.new.
|
19
|
+
job_name.constantize.new.execute(*args)
|
16
20
|
end
|
17
21
|
end
|
18
22
|
end
|
@@ -4,14 +4,18 @@ module ActiveJob
|
|
4
4
|
module QueueAdapters
|
5
5
|
class DelayedJobAdapter
|
6
6
|
class << self
|
7
|
-
def
|
7
|
+
def enqueue(job, *args)
|
8
8
|
JobWrapper.new.delay(queue: job.queue_name).perform(job, *args)
|
9
9
|
end
|
10
|
+
|
11
|
+
def enqueue_at(job, timestamp, *args)
|
12
|
+
JobWrapper.new.delay(queue: job.queue_name, run_at: Time.at(timestamp)).perform(job, *args)
|
13
|
+
end
|
10
14
|
end
|
11
|
-
|
15
|
+
|
12
16
|
class JobWrapper
|
13
17
|
def perform(job, *args)
|
14
|
-
job.new.
|
18
|
+
job.new.execute(*args)
|
15
19
|
end
|
16
20
|
end
|
17
21
|
end
|
@@ -2,10 +2,14 @@ module ActiveJob
|
|
2
2
|
module QueueAdapters
|
3
3
|
class InlineAdapter
|
4
4
|
class << self
|
5
|
-
def
|
6
|
-
job.new.
|
5
|
+
def enqueue(job, *args)
|
6
|
+
job.new.execute(*args)
|
7
|
+
end
|
8
|
+
|
9
|
+
def enqueue_at(*)
|
10
|
+
raise NotImplementedError.new("Use a queueing backend to enqueue jobs in the future. Read more at https://github.com/rails/activejob")
|
7
11
|
end
|
8
12
|
end
|
9
13
|
end
|
10
14
|
end
|
11
|
-
end
|
15
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'qu'
|
2
|
+
|
3
|
+
module ActiveJob
|
4
|
+
module QueueAdapters
|
5
|
+
class QuAdapter
|
6
|
+
class << self
|
7
|
+
def enqueue(job, *args)
|
8
|
+
Qu::Payload.new(klass: JobWrapper, args: [job.name, *args]).tap do |payload|
|
9
|
+
payload.instance_variable_set(:@queue, job.queue_name)
|
10
|
+
end.push
|
11
|
+
end
|
12
|
+
|
13
|
+
def enqueue_at(job, timestamp, *args)
|
14
|
+
raise NotImplementedError
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class JobWrapper < Qu::Job
|
19
|
+
def initialize(job_name, *args)
|
20
|
+
@job = job_name.constantize
|
21
|
+
@args = args
|
22
|
+
end
|
23
|
+
|
24
|
+
def perform
|
25
|
+
@job.new.execute(*@args)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -4,14 +4,18 @@ module ActiveJob
|
|
4
4
|
module QueueAdapters
|
5
5
|
class QueAdapter
|
6
6
|
class << self
|
7
|
-
def
|
8
|
-
JobWrapper.enqueue job, *args, queue: job.queue_name
|
7
|
+
def enqueue(job, *args)
|
8
|
+
JobWrapper.enqueue job.name, *args, queue: job.queue_name
|
9
|
+
end
|
10
|
+
|
11
|
+
def enqueue_at(job, timestamp, *args)
|
12
|
+
raise NotImplementedError
|
9
13
|
end
|
10
14
|
end
|
11
15
|
|
12
16
|
class JobWrapper < Que::Job
|
13
|
-
def run(
|
14
|
-
|
17
|
+
def run(job_name, *args)
|
18
|
+
job_name.constantize.new.execute(*args)
|
15
19
|
end
|
16
20
|
end
|
17
21
|
end
|
@@ -4,14 +4,20 @@ module ActiveJob
|
|
4
4
|
module QueueAdapters
|
5
5
|
class QueueClassicAdapter
|
6
6
|
class << self
|
7
|
-
def
|
8
|
-
QC::Queue.new(job.queue_name).enqueue("#{JobWrapper.name}.perform", job, *args)
|
7
|
+
def enqueue(job, *args)
|
8
|
+
QC::Queue.new(job.queue_name).enqueue("#{JobWrapper.name}.perform", job.name, *args)
|
9
|
+
end
|
10
|
+
|
11
|
+
def enqueue_at(job, timestamp, *args)
|
12
|
+
raise NotImplementedError
|
9
13
|
end
|
10
14
|
end
|
11
15
|
|
12
16
|
class JobWrapper
|
13
|
-
|
14
|
-
|
17
|
+
class << self
|
18
|
+
def perform(job_name, *args)
|
19
|
+
job_name.constantize.new.execute(*args)
|
20
|
+
end
|
15
21
|
end
|
16
22
|
end
|
17
23
|
end
|
@@ -2,29 +2,39 @@ require 'resque'
|
|
2
2
|
require 'active_support/core_ext/enumerable'
|
3
3
|
require 'active_support/core_ext/array/access'
|
4
4
|
|
5
|
+
begin
|
6
|
+
require 'resque-scheduler'
|
7
|
+
rescue LoadError
|
8
|
+
begin
|
9
|
+
require 'resque_scheduler'
|
10
|
+
rescue LoadError
|
11
|
+
false
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
5
15
|
module ActiveJob
|
6
16
|
module QueueAdapters
|
7
17
|
class ResqueAdapter
|
8
18
|
class << self
|
9
|
-
def
|
10
|
-
Resque.
|
19
|
+
def enqueue(job, *args)
|
20
|
+
Resque.enqueue_to job.queue_name, JobWrapper, job.name, *args
|
21
|
+
end
|
22
|
+
|
23
|
+
def enqueue_at(job, timestamp, *args)
|
24
|
+
unless Resque.respond_to?(:enqueue_at_with_queue)
|
25
|
+
raise NotImplementedError, "To be able to schedule jobs with Resque you need the " \
|
26
|
+
"resque-scheduler gem. Please add it to your Gemfile and run bundle install"
|
27
|
+
end
|
28
|
+
Resque.enqueue_at_with_queue job.queue_name, timestamp, JobWrapper, job.name, *args
|
11
29
|
end
|
12
30
|
end
|
13
31
|
|
14
32
|
class JobWrapper
|
15
33
|
class << self
|
16
34
|
def perform(job_name, *args)
|
17
|
-
job_name.constantize.new.
|
35
|
+
job_name.constantize.new.execute(*args)
|
18
36
|
end
|
19
37
|
end
|
20
|
-
|
21
|
-
def initialize(job)
|
22
|
-
@queue = job.queue_name
|
23
|
-
end
|
24
|
-
|
25
|
-
def to_s
|
26
|
-
self.class.name
|
27
|
-
end
|
28
38
|
end
|
29
39
|
end
|
30
40
|
end
|
@@ -4,20 +4,30 @@ module ActiveJob
|
|
4
4
|
module QueueAdapters
|
5
5
|
class SidekiqAdapter
|
6
6
|
class << self
|
7
|
-
def
|
7
|
+
def enqueue(job, *args)
|
8
|
+
#Sidekiq::Client does not support symbols as keys
|
8
9
|
Sidekiq::Client.push \
|
9
10
|
'class' => JobWrapper,
|
10
11
|
'queue' => job.queue_name,
|
11
12
|
'args' => [ job, *args ],
|
12
13
|
'retry' => true
|
13
14
|
end
|
15
|
+
|
16
|
+
def enqueue_at(job, timestamp, *args)
|
17
|
+
Sidekiq::Client.push \
|
18
|
+
'class' => JobWrapper,
|
19
|
+
'queue' => job.queue_name,
|
20
|
+
'args' => [ job, *args ],
|
21
|
+
'retry' => true,
|
22
|
+
'at' => timestamp
|
23
|
+
end
|
14
24
|
end
|
15
25
|
|
16
26
|
class JobWrapper
|
17
27
|
include Sidekiq::Worker
|
18
28
|
|
19
29
|
def perform(job_name, *args)
|
20
|
-
job_name.constantize.new.
|
30
|
+
job_name.constantize.new.execute(*args)
|
21
31
|
end
|
22
32
|
end
|
23
33
|
end
|
@@ -4,22 +4,29 @@ require 'thread'
|
|
4
4
|
module ActiveJob
|
5
5
|
module QueueAdapters
|
6
6
|
class SneakersAdapter
|
7
|
-
@
|
8
|
-
|
7
|
+
@monitor = Monitor.new
|
8
|
+
|
9
9
|
class << self
|
10
|
-
def
|
11
|
-
@
|
10
|
+
def enqueue(job, *args)
|
11
|
+
@monitor.synchronize do
|
12
12
|
JobWrapper.from_queue job.queue_name
|
13
|
-
JobWrapper.enqueue [ job, *args ]
|
13
|
+
JobWrapper.enqueue ActiveSupport::JSON.encode([ job.name, *args ])
|
14
14
|
end
|
15
15
|
end
|
16
|
+
|
17
|
+
def enqueue_at(job, timestamp, *args)
|
18
|
+
raise NotImplementedError
|
19
|
+
end
|
16
20
|
end
|
17
21
|
|
18
22
|
class JobWrapper
|
19
23
|
include Sneakers::Worker
|
24
|
+
from_queue 'active_jobs_default'
|
20
25
|
|
21
|
-
def work(
|
22
|
-
|
26
|
+
def work(msg)
|
27
|
+
job_name, *args = ActiveSupport::JSON.decode(msg)
|
28
|
+
job_name.constantize.new.execute(*args)
|
29
|
+
ack!
|
23
30
|
end
|
24
31
|
end
|
25
32
|
end
|
@@ -4,8 +4,12 @@ module ActiveJob
|
|
4
4
|
module QueueAdapters
|
5
5
|
class SuckerPunchAdapter
|
6
6
|
class << self
|
7
|
-
def
|
8
|
-
JobWrapper.new.async.perform
|
7
|
+
def enqueue(job, *args)
|
8
|
+
JobWrapper.new.async.perform job, *args
|
9
|
+
end
|
10
|
+
|
11
|
+
def enqueue_at(job, timestamp, *args)
|
12
|
+
raise NotImplementedError
|
9
13
|
end
|
10
14
|
end
|
11
15
|
|
@@ -13,7 +17,7 @@ module ActiveJob
|
|
13
17
|
include SuckerPunch::Job
|
14
18
|
|
15
19
|
def perform(job, *args)
|
16
|
-
job.new.
|
20
|
+
job.new.execute(*args)
|
17
21
|
end
|
18
22
|
end
|
19
23
|
end
|
@@ -1,10 +1,21 @@
|
|
1
1
|
module ActiveJob
|
2
2
|
module QueueName
|
3
|
-
|
4
|
-
mattr_accessor(:queue_name) { queue_base_name }
|
3
|
+
extend ActiveSupport::Concern
|
5
4
|
|
6
|
-
|
7
|
-
|
5
|
+
module ClassMethods
|
6
|
+
mattr_accessor(:queue_name_prefix)
|
7
|
+
mattr_accessor(:default_queue_name) { "default" }
|
8
|
+
|
9
|
+
def queue_as(part_name)
|
10
|
+
queue_name = part_name.to_s.presence || default_queue_name
|
11
|
+
name_parts = [queue_name_prefix.presence, queue_name]
|
12
|
+
self.queue_name = name_parts.compact.join('_')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
included do
|
17
|
+
class_attribute :queue_name
|
18
|
+
self.queue_name = default_queue_name
|
8
19
|
end
|
9
20
|
end
|
10
|
-
end
|
21
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'global_id/railtie'
|
2
|
+
require 'active_job'
|
3
|
+
|
4
|
+
module ActiveJob
|
5
|
+
# = Active Job Railtie
|
6
|
+
class Railtie < Rails::Railtie # :nodoc:
|
7
|
+
config.active_job = ActiveSupport::OrderedOptions.new
|
8
|
+
|
9
|
+
initializer 'active_job.logger' do
|
10
|
+
ActiveSupport.on_load(:active_job) { self.logger = ::Rails.logger }
|
11
|
+
end
|
12
|
+
|
13
|
+
initializer "active_job.set_configs" do |app|
|
14
|
+
options = app.config.active_job
|
15
|
+
options.queue_adapter ||= :inline
|
16
|
+
|
17
|
+
ActiveSupport.on_load(:active_job) do
|
18
|
+
options.each { |k,v| send("#{k}=", v) }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rails/generators/named_base'
|
2
|
+
|
3
|
+
module Rails
|
4
|
+
module Generators # :nodoc:
|
5
|
+
class JobGenerator < Rails::Generators::NamedBase # :nodoc:
|
6
|
+
desc 'This generator creates an active job file at app/jobs'
|
7
|
+
|
8
|
+
class_option :queue, type: :string, default: 'default', desc: 'The queue name for the generated job'
|
9
|
+
|
10
|
+
def self.default_generator_root
|
11
|
+
File.dirname(__FILE__)
|
12
|
+
end
|
13
|
+
|
14
|
+
check_class_collision suffix: 'Job'
|
15
|
+
|
16
|
+
def create_job_file
|
17
|
+
template 'job.rb', File.join('app/jobs', class_path, "#{file_name}_job.rb")
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
metadata
CHANGED
@@ -1,43 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activejob
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.2.0.beta1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Heinemeier Hansson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-08-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: globalid
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 0.2.3
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: activemodel-globalid
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - ">="
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0'
|
34
|
-
type: :runtime
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - ">="
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0'
|
26
|
+
version: 0.2.3
|
41
27
|
description: Declare job classes that can be run by a variety of queueing backends.
|
42
28
|
email: david@loudthinking.com
|
43
29
|
executables: []
|
@@ -46,17 +32,21 @@ extra_rdoc_files: []
|
|
46
32
|
files:
|
47
33
|
- CHANGELOG.md
|
48
34
|
- MIT-LICENSE
|
35
|
+
- README.md
|
49
36
|
- lib/active_job.rb
|
37
|
+
- lib/active_job/arguments.rb
|
50
38
|
- lib/active_job/base.rb
|
39
|
+
- lib/active_job/callbacks.rb
|
51
40
|
- lib/active_job/enqueuing.rb
|
41
|
+
- lib/active_job/execution.rb
|
52
42
|
- lib/active_job/gem_version.rb
|
53
|
-
- lib/active_job/
|
43
|
+
- lib/active_job/identifier.rb
|
54
44
|
- lib/active_job/logging.rb
|
55
|
-
- lib/active_job/parameters.rb
|
56
45
|
- lib/active_job/queue_adapter.rb
|
57
46
|
- lib/active_job/queue_adapters/backburner_adapter.rb
|
58
47
|
- lib/active_job/queue_adapters/delayed_job_adapter.rb
|
59
48
|
- lib/active_job/queue_adapters/inline_adapter.rb
|
49
|
+
- lib/active_job/queue_adapters/qu_adapter.rb
|
60
50
|
- lib/active_job/queue_adapters/que_adapter.rb
|
61
51
|
- lib/active_job/queue_adapters/queue_classic_adapter.rb
|
62
52
|
- lib/active_job/queue_adapters/resque_adapter.rb
|
@@ -64,8 +54,10 @@ files:
|
|
64
54
|
- lib/active_job/queue_adapters/sneakers_adapter.rb
|
65
55
|
- lib/active_job/queue_adapters/sucker_punch_adapter.rb
|
66
56
|
- lib/active_job/queue_name.rb
|
67
|
-
- lib/active_job/
|
57
|
+
- lib/active_job/railtie.rb
|
68
58
|
- lib/active_job/version.rb
|
59
|
+
- lib/rails/generators/job/job_generator.rb
|
60
|
+
- lib/rails/generators/job/templates/job.rb
|
69
61
|
homepage: http://www.rubyonrails.org
|
70
62
|
licenses:
|
71
63
|
- MIT
|
@@ -81,14 +73,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
81
73
|
version: 1.9.3
|
82
74
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
83
75
|
requirements:
|
84
|
-
- - "
|
76
|
+
- - ">"
|
85
77
|
- !ruby/object:Gem::Version
|
86
|
-
version:
|
78
|
+
version: 1.3.1
|
87
79
|
requirements: []
|
88
80
|
rubyforge_project:
|
89
81
|
rubygems_version: 2.2.2
|
90
82
|
signing_key:
|
91
83
|
specification_version: 4
|
92
|
-
summary: Job framework with pluggable queues
|
84
|
+
summary: Job framework with pluggable queues.
|
93
85
|
test_files: []
|
94
|
-
has_rdoc:
|
@@ -1,19 +0,0 @@
|
|
1
|
-
require 'active_support/core_ext/string/filters'
|
2
|
-
|
3
|
-
module ActiveJob
|
4
|
-
class LogSubscriber < ActiveSupport::LogSubscriber
|
5
|
-
def enqueue(event)
|
6
|
-
queue_name = event.payload[:adapter].name.demodulize.remove('Adapter')
|
7
|
-
job_name = event.payload[:job].name
|
8
|
-
args = event.payload[:args].any? ? ": #{event.payload[:args].inspect}" : ""
|
9
|
-
|
10
|
-
info "Enqueued #{job_name} to #{queue_name}" + args
|
11
|
-
end
|
12
|
-
|
13
|
-
def logger
|
14
|
-
ActiveJob::Base.logger
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
ActiveJob::LogSubscriber.attach_to :active_job
|
@@ -1,19 +0,0 @@
|
|
1
|
-
require 'active_model/global_locator'
|
2
|
-
require 'active_support/core_ext/object/try'
|
3
|
-
|
4
|
-
module ActiveJob
|
5
|
-
class Parameters
|
6
|
-
TYPE_WHITELIST = [NilClass, Fixnum, Float, String, TrueClass, FalseClass, Hash, Array, Bignum]
|
7
|
-
|
8
|
-
def self.serialize(params)
|
9
|
-
params.collect do |param|
|
10
|
-
raise "Unsupported parameter type: #{param.class.name}" unless param.respond_to?(:global_id) || TYPE_WHITELIST.include?(param.class)
|
11
|
-
param.try(:global_id) || param
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
|
-
def self.deserialize(params)
|
16
|
-
params.collect { |param| ActiveModel::GlobalLocator.locate(param) || param }
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|