grockit-resque 1.5.0
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.
- data/.gitignore +2 -0
- data/.kick +26 -0
- data/CONTRIBUTORS +14 -0
- data/HISTORY.md +78 -0
- data/LICENSE +20 -0
- data/README.markdown +762 -0
- data/Rakefile +67 -0
- data/bin/resque +57 -0
- data/bin/resque-web +18 -0
- data/config.ru +14 -0
- data/deps.rip +6 -0
- data/examples/async_helper.rb +31 -0
- data/examples/demo/README.markdown +71 -0
- data/examples/demo/Rakefile +3 -0
- data/examples/demo/app.rb +38 -0
- data/examples/demo/config.ru +19 -0
- data/examples/demo/job.rb +22 -0
- data/examples/god/resque.god +53 -0
- data/examples/god/stale.god +26 -0
- data/examples/instance.rb +11 -0
- data/examples/simple.rb +30 -0
- data/init.rb +1 -0
- data/lib/resque.rb +247 -0
- data/lib/resque/errors.rb +7 -0
- data/lib/resque/failure.rb +63 -0
- data/lib/resque/failure/base.rb +58 -0
- data/lib/resque/failure/hoptoad.rb +122 -0
- data/lib/resque/failure/multiple.rb +44 -0
- data/lib/resque/failure/redis.rb +33 -0
- data/lib/resque/helpers.rb +57 -0
- data/lib/resque/job.rb +158 -0
- data/lib/resque/server.rb +182 -0
- data/lib/resque/server/public/idle.png +0 -0
- data/lib/resque/server/public/jquery-1.3.2.min.js +19 -0
- data/lib/resque/server/public/jquery.relatize_date.js +95 -0
- data/lib/resque/server/public/poll.png +0 -0
- data/lib/resque/server/public/ranger.js +24 -0
- data/lib/resque/server/public/reset.css +48 -0
- data/lib/resque/server/public/style.css +75 -0
- data/lib/resque/server/public/working.png +0 -0
- data/lib/resque/server/views/error.erb +1 -0
- data/lib/resque/server/views/failed.erb +35 -0
- data/lib/resque/server/views/key.erb +17 -0
- data/lib/resque/server/views/layout.erb +38 -0
- data/lib/resque/server/views/next_more.erb +10 -0
- data/lib/resque/server/views/overview.erb +4 -0
- data/lib/resque/server/views/queues.erb +46 -0
- data/lib/resque/server/views/stats.erb +62 -0
- data/lib/resque/server/views/workers.erb +78 -0
- data/lib/resque/server/views/working.erb +69 -0
- data/lib/resque/stat.rb +53 -0
- data/lib/resque/tasks.rb +39 -0
- data/lib/resque/version.rb +3 -0
- data/lib/resque/worker.rb +442 -0
- data/tasks/redis.rake +135 -0
- data/tasks/resque.rake +2 -0
- data/test/redis-test.conf +132 -0
- data/test/resque_test.rb +227 -0
- data/test/test_helper.rb +96 -0
- data/test/worker_test.rb +243 -0
- metadata +172 -0
data/examples/simple.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# This is a simple Resque job.
|
2
|
+
class Archive
|
3
|
+
@queue = :file_serve
|
4
|
+
|
5
|
+
def self.perform(repo_id, branch = 'master')
|
6
|
+
repo = Repository.find(repo_id)
|
7
|
+
repo.create_archive(branch)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# This is in our app code
|
12
|
+
class Repository < Model
|
13
|
+
# ... stuff ...
|
14
|
+
|
15
|
+
def async_create_archive(branch)
|
16
|
+
Resque.enqueue(Archive, self.id, branch)
|
17
|
+
end
|
18
|
+
|
19
|
+
# ... more stuff ...
|
20
|
+
end
|
21
|
+
|
22
|
+
# Calling this code:
|
23
|
+
repo = Repository.find(22)
|
24
|
+
repo.async_create_archive('homebrew')
|
25
|
+
|
26
|
+
# Will return immediately and create a Resque job which is later
|
27
|
+
# processed.
|
28
|
+
|
29
|
+
# Essentially, this code is run by the worker when processing:
|
30
|
+
Archive.perform(22, 'homebrew')
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'resque'
|
data/lib/resque.rb
ADDED
@@ -0,0 +1,247 @@
|
|
1
|
+
require 'redis/namespace'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'yajl'
|
5
|
+
rescue LoadError
|
6
|
+
require 'json'
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'resque/version'
|
10
|
+
|
11
|
+
require 'resque/errors'
|
12
|
+
|
13
|
+
require 'resque/failure'
|
14
|
+
require 'resque/failure/base'
|
15
|
+
|
16
|
+
require 'resque/helpers'
|
17
|
+
require 'resque/stat'
|
18
|
+
require 'resque/job'
|
19
|
+
require 'resque/worker'
|
20
|
+
|
21
|
+
module Resque
|
22
|
+
include Helpers
|
23
|
+
extend self
|
24
|
+
|
25
|
+
# Accepts:
|
26
|
+
# 1. A 'hostname:port' string
|
27
|
+
# 2. A 'hostname:port:db' string (to select the Redis db)
|
28
|
+
# 3. An instance of `Redis`
|
29
|
+
def redis=(server)
|
30
|
+
case server
|
31
|
+
when String
|
32
|
+
host, port, db = server.split(':')
|
33
|
+
redis = Redis.new(:host => host, :port => port,
|
34
|
+
:thread_safe => true, :db => db)
|
35
|
+
@redis = Redis::Namespace.new(:resque, :redis => redis)
|
36
|
+
when Redis
|
37
|
+
@redis = Redis::Namespace.new(:resque, :redis => server)
|
38
|
+
else
|
39
|
+
raise "I don't know what to do with #{server.inspect}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns the current Redis connection. If none has been created, will
|
44
|
+
# create a new one.
|
45
|
+
def redis
|
46
|
+
return @redis if @redis
|
47
|
+
self.redis = 'localhost:6379'
|
48
|
+
self.redis
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_s
|
52
|
+
"Resque Client connected to #{redis.server}"
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
#
|
57
|
+
# queue manipulation
|
58
|
+
#
|
59
|
+
|
60
|
+
# Pushes a job onto a queue. Queue name should be a string and the
|
61
|
+
# item should be any JSON-able Ruby object.
|
62
|
+
def push(queue, item)
|
63
|
+
watch_queue(queue)
|
64
|
+
queue_key = "queue:#{queue}"
|
65
|
+
if item[:unique]
|
66
|
+
unless redis.lrange(queue_key, 0, -1).include?(encode(item))
|
67
|
+
redis.rpush queue_key, encode(item)
|
68
|
+
end
|
69
|
+
else
|
70
|
+
redis.rpush queue_key, encode(item)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Pops a job off a queue. Queue name should be a string.
|
75
|
+
#
|
76
|
+
# Returns a Ruby object.
|
77
|
+
def pop(queue)
|
78
|
+
decode redis.lpop("queue:#{queue}")
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns an int representing the size of a queue.
|
82
|
+
# Queue name should be a string.
|
83
|
+
def size(queue)
|
84
|
+
redis.llen("queue:#{queue}").to_i
|
85
|
+
end
|
86
|
+
|
87
|
+
# Returns an array of items currently queued. Queue name should be
|
88
|
+
# a string.
|
89
|
+
#
|
90
|
+
# start and count should be integer and can be used for pagination.
|
91
|
+
# start is the item to begin, count is how many items to return.
|
92
|
+
#
|
93
|
+
# To get the 3rd page of a 30 item, paginatied list one would use:
|
94
|
+
# Resque.peek('my_list', 59, 30)
|
95
|
+
def peek(queue, start = 0, count = 1)
|
96
|
+
list_range("queue:#{queue}", start, count)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Does the dirty work of fetching a range of items from a Redis list
|
100
|
+
# and converting them into Ruby objects.
|
101
|
+
def list_range(key, start = 0, count = 1)
|
102
|
+
if count == 1
|
103
|
+
decode redis.lindex(key, start)
|
104
|
+
else
|
105
|
+
Array(redis.lrange(key, start, start+count-1)).map do |item|
|
106
|
+
decode item
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns an array of all known Resque queues as strings.
|
112
|
+
def queues
|
113
|
+
redis.smembers(:queues)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Given a queue name, completely deletes the queue.
|
117
|
+
def remove_queue(queue)
|
118
|
+
redis.srem(:queues, queue.to_s)
|
119
|
+
redis.del("queue:#{queue}")
|
120
|
+
end
|
121
|
+
|
122
|
+
# Used internally to keep track of which queues we've created.
|
123
|
+
# Don't call this directly.
|
124
|
+
def watch_queue(queue)
|
125
|
+
redis.sadd(:queues, queue.to_s)
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
#
|
130
|
+
# job shortcuts
|
131
|
+
#
|
132
|
+
|
133
|
+
# This method can be used to conveniently add a job to a queue.
|
134
|
+
# It assumes the class you're passing it is a real Ruby class (not
|
135
|
+
# a string or reference) which either:
|
136
|
+
#
|
137
|
+
# a) has a @queue ivar set
|
138
|
+
# b) responds to `queue`
|
139
|
+
#
|
140
|
+
# If either of those conditions are met, it will use the value obtained
|
141
|
+
# from performing one of the above operations to determine the queue.
|
142
|
+
#
|
143
|
+
# If no queue can be inferred this method will raise a `Resque::NoQueueError`
|
144
|
+
#
|
145
|
+
# This method is considered part of the `stable` API.
|
146
|
+
def enqueue(klass, *args)
|
147
|
+
Job.create(queue_from_class(klass), klass, *args)
|
148
|
+
end
|
149
|
+
|
150
|
+
def enqueue_unique(klass, *args)
|
151
|
+
Job.create_unique(queue_from_class(klass), klass, *args)
|
152
|
+
end
|
153
|
+
|
154
|
+
# This method can be used to conveniently remove a job from a queue.
|
155
|
+
# It assumes the class you're passing it is a real Ruby class (not
|
156
|
+
# a string or reference) which either:
|
157
|
+
#
|
158
|
+
# a) has a @queue ivar set
|
159
|
+
# b) responds to `queue`
|
160
|
+
#
|
161
|
+
# If either of those conditions are met, it will use the value obtained
|
162
|
+
# from performing one of the above operations to determine the queue.
|
163
|
+
#
|
164
|
+
# If no queue can be inferred this method will raise a `Resque::NoQueueError`
|
165
|
+
#
|
166
|
+
# If no args are given, this method will dequeue *all* jobs matching
|
167
|
+
# the provided class. See `Resque::Job.destroy` for more
|
168
|
+
# information.
|
169
|
+
#
|
170
|
+
# Returns the number of jobs destroyed.
|
171
|
+
#
|
172
|
+
# Example:
|
173
|
+
#
|
174
|
+
# # Removes all jobs of class `UpdateNetworkGraph`
|
175
|
+
# Resque.dequeue(GitHub::Jobs::UpdateNetworkGraph)
|
176
|
+
#
|
177
|
+
# # Removes all jobs of class `UpdateNetworkGraph` with matching args.
|
178
|
+
# Resque.dequeue(GitHub::Jobs::UpdateNetworkGraph, 'repo:135325')
|
179
|
+
#
|
180
|
+
# This method is considered part of the `stable` API.
|
181
|
+
def dequeue(klass, *args)
|
182
|
+
Job.destroy(queue_from_class(klass), klass, *args)
|
183
|
+
end
|
184
|
+
|
185
|
+
# Given a class, try to extrapolate an appropriate queue based on a
|
186
|
+
# class instance variable or `queue` method.
|
187
|
+
def queue_from_class(klass)
|
188
|
+
klass.instance_variable_get(:@queue) ||
|
189
|
+
(klass.respond_to?(:queue) and klass.queue)
|
190
|
+
end
|
191
|
+
|
192
|
+
# This method will return a `Resque::Job` object or a non-true value
|
193
|
+
# depending on whether a job can be obtained. You should pass it the
|
194
|
+
# precise name of a queue: case matters.
|
195
|
+
#
|
196
|
+
# This method is considered part of the `stable` API.
|
197
|
+
def reserve(queue)
|
198
|
+
Job.reserve(queue)
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
#
|
203
|
+
# worker shortcuts
|
204
|
+
#
|
205
|
+
|
206
|
+
# A shortcut to Worker.all
|
207
|
+
def workers
|
208
|
+
Worker.all
|
209
|
+
end
|
210
|
+
|
211
|
+
# A shortcut to Worker.working
|
212
|
+
def working
|
213
|
+
Worker.working
|
214
|
+
end
|
215
|
+
|
216
|
+
# A shortcut to unregister_worker
|
217
|
+
# useful for command line tool
|
218
|
+
def remove_worker(worker_id)
|
219
|
+
worker = Resque::Worker.find(worker_id)
|
220
|
+
worker.unregister_worker
|
221
|
+
end
|
222
|
+
|
223
|
+
#
|
224
|
+
# stats
|
225
|
+
#
|
226
|
+
|
227
|
+
# Returns a hash, similar to redis-rb's #info, of interesting stats.
|
228
|
+
def info
|
229
|
+
return {
|
230
|
+
:pending => queues.inject(0) { |m,k| m + size(k) },
|
231
|
+
:processed => Stat[:processed],
|
232
|
+
:queues => queues.size,
|
233
|
+
:workers => workers.size.to_i,
|
234
|
+
:working => working.size,
|
235
|
+
:failed => Stat[:failed],
|
236
|
+
:servers => [redis.server]
|
237
|
+
}
|
238
|
+
end
|
239
|
+
|
240
|
+
# Returns an array of all known Resque keys in Redis. Redis' KEYS operation
|
241
|
+
# is O(N) for the keyspace, so be careful - this can be slow for big databases.
|
242
|
+
def keys
|
243
|
+
redis.keys("*").map do |key|
|
244
|
+
key.sub('resque:', '')
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Resque
|
2
|
+
# The Failure module provides an interface for working with different
|
3
|
+
# failure backends.
|
4
|
+
#
|
5
|
+
# You can use it to query the failure backend without knowing which specific
|
6
|
+
# backend is being used. For instance, the Resque web app uses it to display
|
7
|
+
# stats and other information.
|
8
|
+
module Failure
|
9
|
+
# Creates a new failure, which is delegated to the appropriate backend.
|
10
|
+
#
|
11
|
+
# Expects a hash with the following keys:
|
12
|
+
# :exception - The Exception object
|
13
|
+
# :worker - The Worker object who is reporting the failure
|
14
|
+
# :queue - The string name of the queue from which the job was pulled
|
15
|
+
# :payload - The job's payload
|
16
|
+
def self.create(options = {})
|
17
|
+
backend.new(*options.values_at(:exception, :worker, :queue, :payload)).save
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# Sets the current backend. Expects a class descendent of
|
22
|
+
# `Resque::Failure::Base`.
|
23
|
+
#
|
24
|
+
# Example use:
|
25
|
+
# require 'resque/failure/hoptoad'
|
26
|
+
# Resque::Failure.backend = Resque::Failure::Hoptoad
|
27
|
+
def self.backend=(backend)
|
28
|
+
@backend = backend
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns the current backend class. If none has been set, falls
|
32
|
+
# back to `Resque::Failure::Redis`
|
33
|
+
def self.backend
|
34
|
+
return @backend if @backend
|
35
|
+
require 'resque/failure/redis'
|
36
|
+
@backend = Failure::Redis
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns the int count of how many failures we have seen.
|
40
|
+
def self.count
|
41
|
+
backend.count
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns an array of all the failures, paginated.
|
45
|
+
#
|
46
|
+
# `start` is the int of the first item in the page, `count` is the
|
47
|
+
# number of items to return.
|
48
|
+
def self.all(start = 0, count = 1)
|
49
|
+
backend.all(start, count)
|
50
|
+
end
|
51
|
+
|
52
|
+
# The string url of the backend's web interface, if any.
|
53
|
+
def self.url
|
54
|
+
backend.url
|
55
|
+
end
|
56
|
+
|
57
|
+
# Clear all failure jobs
|
58
|
+
def self.clear
|
59
|
+
backend.clear
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Resque
|
2
|
+
module Failure
|
3
|
+
# All Failure classes are expected to subclass Base.
|
4
|
+
#
|
5
|
+
# When a job fails, a new instance of your Failure backend is created
|
6
|
+
# and #save is called.
|
7
|
+
class Base
|
8
|
+
# The exception object raised by the failed job
|
9
|
+
attr_accessor :exception
|
10
|
+
|
11
|
+
# The worker object who detected the failure
|
12
|
+
attr_accessor :worker
|
13
|
+
|
14
|
+
# The string name of the queue from which the failed job was pulled
|
15
|
+
attr_accessor :queue
|
16
|
+
|
17
|
+
# The payload object associated with the failed job
|
18
|
+
attr_accessor :payload
|
19
|
+
|
20
|
+
def initialize(exception, worker, queue, payload)
|
21
|
+
@exception = exception
|
22
|
+
@worker = worker
|
23
|
+
@queue = queue
|
24
|
+
@payload = payload
|
25
|
+
end
|
26
|
+
|
27
|
+
# When a job fails, a new instance of your Failure backend is created
|
28
|
+
# and #save is called.
|
29
|
+
#
|
30
|
+
# This is where you POST or PUT or whatever to your Failure service.
|
31
|
+
def save
|
32
|
+
end
|
33
|
+
|
34
|
+
# The number of failures.
|
35
|
+
def self.count
|
36
|
+
0
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns a paginated array of failure objects.
|
40
|
+
def self.all(start = 0, count = 1)
|
41
|
+
[]
|
42
|
+
end
|
43
|
+
|
44
|
+
# A URL where someone can go to view failures.
|
45
|
+
def self.url
|
46
|
+
end
|
47
|
+
|
48
|
+
# Clear all failure objects
|
49
|
+
def self.clear
|
50
|
+
end
|
51
|
+
|
52
|
+
# Logging!
|
53
|
+
def log(message)
|
54
|
+
@worker.log(message)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'builder'
|
3
|
+
|
4
|
+
module Resque
|
5
|
+
module Failure
|
6
|
+
# A Failure backend that sends exceptions raised by jobs to Hoptoad.
|
7
|
+
#
|
8
|
+
# To use it, put this code in an initializer, Rake task, or wherever:
|
9
|
+
#
|
10
|
+
# Resque::Failure::Hoptoad.configure do |config|
|
11
|
+
# config.api_key = 'blah'
|
12
|
+
# config.secure = true
|
13
|
+
# config.subdomain = 'your_hoptoad_subdomain'
|
14
|
+
# end
|
15
|
+
class Hoptoad < Base
|
16
|
+
#from the hoptoad plugin
|
17
|
+
INPUT_FORMAT = %r{^([^:]+):(\d+)(?::in `([^']+)')?$}.freeze
|
18
|
+
|
19
|
+
class << self
|
20
|
+
attr_accessor :secure, :api_key, :subdomain
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.url
|
24
|
+
"http://#{subdomain}.hoptoadapp.com/" if subdomain
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.count
|
28
|
+
# We can't get the total # of errors from Hoptoad so we fake it
|
29
|
+
# by asking Resque how many errors it has seen.
|
30
|
+
Stat[:failed]
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.configure
|
34
|
+
yield self
|
35
|
+
Resque::Failure.backend = self
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
def save
|
41
|
+
http = use_ssl? ? :https : :http
|
42
|
+
url = URI.parse("#{http}://hoptoadapp.com/notifier_api/v2/notices")
|
43
|
+
|
44
|
+
http = Net::HTTP.new(url.host, url.port)
|
45
|
+
headers = {
|
46
|
+
'Content-type' => 'text/xml',
|
47
|
+
'Accept' => 'text/xml, application/xml'
|
48
|
+
}
|
49
|
+
|
50
|
+
http.read_timeout = 5 # seconds
|
51
|
+
http.open_timeout = 2 # seconds
|
52
|
+
|
53
|
+
http.use_ssl = use_ssl?
|
54
|
+
|
55
|
+
begin
|
56
|
+
response = http.post(url.path, xml, headers)
|
57
|
+
rescue TimeoutError => e
|
58
|
+
log "Timeout while contacting the Hoptoad server."
|
59
|
+
end
|
60
|
+
|
61
|
+
case response
|
62
|
+
when Net::HTTPSuccess then
|
63
|
+
log "Hoptoad Success: #{response.class}"
|
64
|
+
else
|
65
|
+
body = response.body if response.respond_to? :body
|
66
|
+
log "Hoptoad Failure: #{response.class}\n#{body}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def xml
|
71
|
+
x = Builder::XmlMarkup.new
|
72
|
+
x.instruct!
|
73
|
+
x.notice :version=>"2.0" do
|
74
|
+
x.tag! "api-key", api_key
|
75
|
+
x.notifier do
|
76
|
+
x.name "Resqueue"
|
77
|
+
x.version "0.1"
|
78
|
+
x.url "http://github.com/defunkt/resque"
|
79
|
+
end
|
80
|
+
x.error do
|
81
|
+
x.class exception.class.name
|
82
|
+
x.message "#{exception.class.name}: #{exception.message}"
|
83
|
+
x.backtrace do
|
84
|
+
fill_in_backtrace_lines(x)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
x.request do
|
88
|
+
x.url queue.to_s
|
89
|
+
x.component worker.to_s
|
90
|
+
x.params do
|
91
|
+
x.var :key=>"payload_class" do
|
92
|
+
x.text! payload["class"].to_s
|
93
|
+
end
|
94
|
+
x.var :key=>"payload_args" do
|
95
|
+
x.text! payload["args"].to_s
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
x.tag!("server-environment") do
|
100
|
+
x.tag!("environment-name",RAILS_ENV)
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def fill_in_backtrace_lines(x)
|
107
|
+
exception.backtrace.each do |unparsed_line|
|
108
|
+
_, file, number, method = unparsed_line.match(INPUT_FORMAT).to_a
|
109
|
+
x.line :file=>file,:number=>number
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def use_ssl?
|
114
|
+
self.class.secure
|
115
|
+
end
|
116
|
+
|
117
|
+
def api_key
|
118
|
+
self.class.api_key
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|