sidekiq 6.0.4 → 7.0.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sidekiq might be problematic. Click here for more details.

Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/Changes.md +336 -6
  3. data/LICENSE.txt +9 -0
  4. data/README.md +22 -20
  5. data/bin/sidekiq +22 -3
  6. data/bin/sidekiqload +71 -76
  7. data/bin/sidekiqmon +1 -1
  8. data/lib/generators/sidekiq/job_generator.rb +57 -0
  9. data/lib/generators/sidekiq/templates/{worker.rb.erb → job.rb.erb} +2 -2
  10. data/lib/generators/sidekiq/templates/{worker_spec.rb.erb → job_spec.rb.erb} +1 -1
  11. data/lib/generators/sidekiq/templates/{worker_test.rb.erb → job_test.rb.erb} +1 -1
  12. data/lib/sidekiq/api.rb +365 -230
  13. data/lib/sidekiq/capsule.rb +110 -0
  14. data/lib/sidekiq/cli.rb +120 -86
  15. data/lib/sidekiq/client.rb +81 -85
  16. data/lib/sidekiq/{util.rb → component.rb} +13 -14
  17. data/lib/sidekiq/config.rb +270 -0
  18. data/lib/sidekiq/deploy.rb +62 -0
  19. data/lib/sidekiq/embedded.rb +61 -0
  20. data/lib/sidekiq/fetch.rb +42 -32
  21. data/lib/sidekiq/{worker.rb → job.rb} +165 -34
  22. data/lib/sidekiq/job_logger.rb +18 -30
  23. data/lib/sidekiq/job_retry.rb +80 -60
  24. data/lib/sidekiq/job_util.rb +71 -0
  25. data/lib/sidekiq/launcher.rb +173 -85
  26. data/lib/sidekiq/logger.rb +13 -47
  27. data/lib/sidekiq/manager.rb +40 -41
  28. data/lib/sidekiq/metrics/query.rb +153 -0
  29. data/lib/sidekiq/metrics/shared.rb +95 -0
  30. data/lib/sidekiq/metrics/tracking.rb +134 -0
  31. data/lib/sidekiq/middleware/chain.rb +90 -46
  32. data/lib/sidekiq/middleware/current_attributes.rb +58 -0
  33. data/lib/sidekiq/middleware/i18n.rb +6 -4
  34. data/lib/sidekiq/middleware/modules.rb +21 -0
  35. data/lib/sidekiq/monitor.rb +19 -4
  36. data/lib/sidekiq/paginator.rb +17 -9
  37. data/lib/sidekiq/processor.rb +57 -60
  38. data/lib/sidekiq/rails.rb +33 -23
  39. data/lib/sidekiq/redis_client_adapter.rb +115 -0
  40. data/lib/sidekiq/redis_connection.rb +21 -87
  41. data/lib/sidekiq/ring_buffer.rb +29 -0
  42. data/lib/sidekiq/scheduled.rb +102 -39
  43. data/lib/sidekiq/sd_notify.rb +149 -0
  44. data/lib/sidekiq/systemd.rb +24 -0
  45. data/lib/sidekiq/testing/inline.rb +4 -4
  46. data/lib/sidekiq/testing.rb +43 -72
  47. data/lib/sidekiq/transaction_aware_client.rb +44 -0
  48. data/lib/sidekiq/version.rb +2 -1
  49. data/lib/sidekiq/web/action.rb +3 -3
  50. data/lib/sidekiq/web/application.rb +47 -24
  51. data/lib/sidekiq/web/csrf_protection.rb +180 -0
  52. data/lib/sidekiq/web/helpers.rb +59 -47
  53. data/lib/sidekiq/web/router.rb +6 -5
  54. data/lib/sidekiq/web.rb +31 -74
  55. data/lib/sidekiq/worker_compatibility_alias.rb +13 -0
  56. data/lib/sidekiq.rb +86 -199
  57. data/sidekiq.gemspec +36 -7
  58. data/web/assets/images/apple-touch-icon.png +0 -0
  59. data/web/assets/javascripts/application.js +130 -61
  60. data/web/assets/javascripts/base-charts.js +106 -0
  61. data/web/assets/javascripts/chart.min.js +13 -0
  62. data/web/assets/javascripts/chartjs-plugin-annotation.min.js +7 -0
  63. data/web/assets/javascripts/dashboard-charts.js +166 -0
  64. data/web/assets/javascripts/dashboard.js +36 -273
  65. data/web/assets/javascripts/metrics.js +236 -0
  66. data/web/assets/stylesheets/application-dark.css +146 -124
  67. data/web/assets/stylesheets/application-rtl.css +2 -95
  68. data/web/assets/stylesheets/application.css +100 -529
  69. data/web/locales/ar.yml +71 -65
  70. data/web/locales/cs.yml +62 -62
  71. data/web/locales/da.yml +52 -52
  72. data/web/locales/de.yml +65 -65
  73. data/web/locales/el.yml +43 -24
  74. data/web/locales/en.yml +83 -67
  75. data/web/locales/es.yml +70 -54
  76. data/web/locales/fa.yml +65 -65
  77. data/web/locales/fr.yml +69 -62
  78. data/web/locales/he.yml +65 -64
  79. data/web/locales/hi.yml +59 -59
  80. data/web/locales/it.yml +53 -53
  81. data/web/locales/ja.yml +73 -65
  82. data/web/locales/ko.yml +52 -52
  83. data/web/locales/lt.yml +83 -0
  84. data/web/locales/nb.yml +61 -61
  85. data/web/locales/nl.yml +52 -52
  86. data/web/locales/pl.yml +45 -45
  87. data/web/locales/pt-br.yml +63 -55
  88. data/web/locales/pt.yml +51 -51
  89. data/web/locales/ru.yml +68 -63
  90. data/web/locales/sv.yml +53 -53
  91. data/web/locales/ta.yml +60 -60
  92. data/web/locales/uk.yml +62 -61
  93. data/web/locales/ur.yml +64 -64
  94. data/web/locales/vi.yml +83 -0
  95. data/web/locales/zh-cn.yml +43 -16
  96. data/web/locales/zh-tw.yml +42 -8
  97. data/web/views/_footer.erb +6 -3
  98. data/web/views/_job_info.erb +17 -1
  99. data/web/views/_nav.erb +1 -1
  100. data/web/views/_poll_link.erb +3 -6
  101. data/web/views/_summary.erb +7 -7
  102. data/web/views/busy.erb +70 -21
  103. data/web/views/dashboard.erb +58 -18
  104. data/web/views/dead.erb +1 -1
  105. data/web/views/layout.erb +3 -2
  106. data/web/views/metrics.erb +80 -0
  107. data/web/views/metrics_for_job.erb +69 -0
  108. data/web/views/morgue.erb +7 -7
  109. data/web/views/queue.erb +15 -11
  110. data/web/views/queues.erb +4 -4
  111. data/web/views/retries.erb +8 -8
  112. data/web/views/retry.erb +1 -1
  113. data/web/views/scheduled.erb +2 -2
  114. metadata +79 -55
  115. data/.circleci/config.yml +0 -82
  116. data/.github/contributing.md +0 -32
  117. data/.github/issue_template.md +0 -11
  118. data/.gitignore +0 -13
  119. data/.standard.yml +0 -20
  120. data/3.0-Upgrade.md +0 -70
  121. data/4.0-Upgrade.md +0 -53
  122. data/5.0-Upgrade.md +0 -56
  123. data/6.0-Upgrade.md +0 -72
  124. data/COMM-LICENSE +0 -97
  125. data/Ent-2.0-Upgrade.md +0 -37
  126. data/Ent-Changes.md +0 -256
  127. data/Gemfile +0 -24
  128. data/Gemfile.lock +0 -199
  129. data/LICENSE +0 -9
  130. data/Pro-2.0-Upgrade.md +0 -138
  131. data/Pro-3.0-Upgrade.md +0 -44
  132. data/Pro-4.0-Upgrade.md +0 -35
  133. data/Pro-5.0-Upgrade.md +0 -25
  134. data/Pro-Changes.md +0 -776
  135. data/Rakefile +0 -10
  136. data/code_of_conduct.md +0 -50
  137. data/lib/generators/sidekiq/worker_generator.rb +0 -57
  138. data/lib/sidekiq/delay.rb +0 -41
  139. data/lib/sidekiq/exception_handler.rb +0 -27
  140. data/lib/sidekiq/extensions/action_mailer.rb +0 -47
  141. data/lib/sidekiq/extensions/active_record.rb +0 -42
  142. data/lib/sidekiq/extensions/class_methods.rb +0 -42
  143. data/lib/sidekiq/extensions/generic_proxy.rb +0 -31
@@ -1,82 +1,93 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "sidekiq/middleware/modules"
4
+
3
5
  module Sidekiq
4
6
  # Middleware is code configured to run before/after
5
- # a message is processed. It is patterned after Rack
7
+ # a job is processed. It is patterned after Rack
6
8
  # middleware. Middleware exists for the client side
7
9
  # (pushing jobs onto the queue) as well as the server
8
10
  # side (when jobs are actually processed).
9
11
  #
12
+ # Callers will register middleware Classes and Sidekiq will
13
+ # create new instances of the middleware for every job. This
14
+ # is important so that instance state is not shared accidentally
15
+ # between job executions.
16
+ #
10
17
  # To add middleware for the client:
11
18
  #
12
- # Sidekiq.configure_client do |config|
13
- # config.client_middleware do |chain|
14
- # chain.add MyClientHook
19
+ # Sidekiq.configure_client do |config|
20
+ # config.client_middleware do |chain|
21
+ # chain.add MyClientHook
22
+ # end
15
23
  # end
16
- # end
17
24
  #
18
25
  # To modify middleware for the server, just call
19
26
  # with another block:
20
27
  #
21
- # Sidekiq.configure_server do |config|
22
- # config.server_middleware do |chain|
23
- # chain.add MyServerHook
24
- # chain.remove ActiveRecord
28
+ # Sidekiq.configure_server do |config|
29
+ # config.server_middleware do |chain|
30
+ # chain.add MyServerHook
31
+ # chain.remove ActiveRecord
32
+ # end
25
33
  # end
26
- # end
27
34
  #
28
35
  # To insert immediately preceding another entry:
29
36
  #
30
- # Sidekiq.configure_client do |config|
31
- # config.client_middleware do |chain|
32
- # chain.insert_before ActiveRecord, MyClientHook
37
+ # Sidekiq.configure_client do |config|
38
+ # config.client_middleware do |chain|
39
+ # chain.insert_before ActiveRecord, MyClientHook
40
+ # end
33
41
  # end
34
- # end
35
42
  #
36
43
  # To insert immediately after another entry:
37
44
  #
38
- # Sidekiq.configure_client do |config|
39
- # config.client_middleware do |chain|
40
- # chain.insert_after ActiveRecord, MyClientHook
45
+ # Sidekiq.configure_client do |config|
46
+ # config.client_middleware do |chain|
47
+ # chain.insert_after ActiveRecord, MyClientHook
48
+ # end
41
49
  # end
42
- # end
43
50
  #
44
51
  # This is an example of a minimal server middleware:
45
52
  #
46
- # class MyServerHook
47
- # def call(worker_instance, msg, queue)
48
- # puts "Before work"
49
- # yield
50
- # puts "After work"
53
+ # class MyServerHook
54
+ # include Sidekiq::ServerMiddleware
55
+ #
56
+ # def call(job_instance, msg, queue)
57
+ # logger.info "Before job"
58
+ # redis {|conn| conn.get("foo") } # do something in Redis
59
+ # yield
60
+ # logger.info "After job"
61
+ # end
51
62
  # end
52
- # end
53
63
  #
54
64
  # This is an example of a minimal client middleware, note
55
65
  # the method must return the result or the job will not push
56
66
  # to Redis:
57
67
  #
58
- # class MyClientHook
59
- # def call(worker_class, msg, queue, redis_pool)
60
- # puts "Before push"
61
- # result = yield
62
- # puts "After push"
63
- # result
68
+ # class MyClientHook
69
+ # include Sidekiq::ClientMiddleware
70
+ #
71
+ # def call(job_class, msg, queue, redis_pool)
72
+ # logger.info "Before push"
73
+ # result = yield
74
+ # logger.info "After push"
75
+ # result
76
+ # end
64
77
  # end
65
- # end
66
78
  #
67
79
  module Middleware
68
80
  class Chain
69
81
  include Enumerable
70
82
 
71
- def initialize_copy(copy)
72
- copy.instance_variable_set(:@entries, entries.dup)
73
- end
74
-
83
+ # Iterate through each middleware in the chain
75
84
  def each(&block)
76
85
  entries.each(&block)
77
86
  end
78
87
 
79
- def initialize
88
+ # @api private
89
+ def initialize(config = nil) # :nodoc:
90
+ @config = config
80
91
  @entries = nil
81
92
  yield self if block_given?
82
93
  end
@@ -85,38 +96,62 @@ module Sidekiq
85
96
  @entries ||= []
86
97
  end
87
98
 
99
+ def copy_for(capsule)
100
+ chain = Sidekiq::Middleware::Chain.new(capsule)
101
+ chain.instance_variable_set(:@entries, entries.dup)
102
+ chain
103
+ end
104
+
105
+ # Remove all middleware matching the given Class
106
+ # @param klass [Class]
88
107
  def remove(klass)
89
108
  entries.delete_if { |entry| entry.klass == klass }
90
109
  end
91
110
 
111
+ # Add the given middleware to the end of the chain.
112
+ # Sidekiq will call `klass.new(*args)` to create a clean
113
+ # copy of your middleware for every job executed.
114
+ #
115
+ # chain.add(Statsd::Metrics, { collector: "localhost:8125" })
116
+ #
117
+ # @param klass [Class] Your middleware class
118
+ # @param *args [Array<Object>] Set of arguments to pass to every instance of your middleware
92
119
  def add(klass, *args)
93
- remove(klass) if exists?(klass)
94
- entries << Entry.new(klass, *args)
120
+ remove(klass)
121
+ entries << Entry.new(@config, klass, *args)
95
122
  end
96
123
 
124
+ # Identical to {#add} except the middleware is added to the front of the chain.
97
125
  def prepend(klass, *args)
98
- remove(klass) if exists?(klass)
99
- entries.insert(0, Entry.new(klass, *args))
126
+ remove(klass)
127
+ entries.insert(0, Entry.new(@config, klass, *args))
100
128
  end
101
129
 
130
+ # Inserts +newklass+ before +oldklass+ in the chain.
131
+ # Useful if one middleware must run before another middleware.
102
132
  def insert_before(oldklass, newklass, *args)
103
133
  i = entries.index { |entry| entry.klass == newklass }
104
- new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i)
134
+ new_entry = i.nil? ? Entry.new(@config, newklass, *args) : entries.delete_at(i)
105
135
  i = entries.index { |entry| entry.klass == oldklass } || 0
106
136
  entries.insert(i, new_entry)
107
137
  end
108
138
 
139
+ # Inserts +newklass+ after +oldklass+ in the chain.
140
+ # Useful if one middleware must run after another middleware.
109
141
  def insert_after(oldklass, newklass, *args)
110
142
  i = entries.index { |entry| entry.klass == newklass }
111
- new_entry = i.nil? ? Entry.new(newklass, *args) : entries.delete_at(i)
143
+ new_entry = i.nil? ? Entry.new(@config, newklass, *args) : entries.delete_at(i)
112
144
  i = entries.index { |entry| entry.klass == oldklass } || entries.count - 1
113
145
  entries.insert(i + 1, new_entry)
114
146
  end
115
147
 
148
+ # @return [Boolean] if the given class is already in the chain
116
149
  def exists?(klass)
117
150
  any? { |entry| entry.klass == klass }
118
151
  end
152
+ alias_method :include?, :exists?
119
153
 
154
+ # @return [Boolean] if the chain contains no middleware
120
155
  def empty?
121
156
  @entries.nil? || @entries.empty?
122
157
  end
@@ -129,11 +164,13 @@ module Sidekiq
129
164
  entries.clear
130
165
  end
131
166
 
167
+ # Used by Sidekiq to execute the middleware at runtime
168
+ # @api private
132
169
  def invoke(*args)
133
170
  return yield if empty?
134
171
 
135
- chain = retrieve.dup
136
- traverse_chain = lambda do
172
+ chain = retrieve
173
+ traverse_chain = proc do
137
174
  if chain.empty?
138
175
  yield
139
176
  else
@@ -144,16 +181,23 @@ module Sidekiq
144
181
  end
145
182
  end
146
183
 
184
+ private
185
+
186
+ # Represents each link in the middleware chain
187
+ # @api private
147
188
  class Entry
148
189
  attr_reader :klass
149
190
 
150
- def initialize(klass, *args)
191
+ def initialize(config, klass, *args)
192
+ @config = config
151
193
  @klass = klass
152
194
  @args = args
153
195
  end
154
196
 
155
197
  def make_new
156
- @klass.new(*@args)
198
+ x = @klass.new(*@args)
199
+ x.config = @config if @config && x.respond_to?(:config=)
200
+ x
157
201
  end
158
202
  end
159
203
  end
@@ -0,0 +1,58 @@
1
+ require "active_support/current_attributes"
2
+
3
+ module Sidekiq
4
+ ##
5
+ # Automatically save and load any current attributes in the execution context
6
+ # so context attributes "flow" from Rails actions into any associated jobs.
7
+ # This can be useful for multi-tenancy, i18n locale, timezone, any implicit
8
+ # per-request attribute. See +ActiveSupport::CurrentAttributes+.
9
+ #
10
+ # @example
11
+ #
12
+ # # in your initializer
13
+ # require "sidekiq/middleware/current_attributes"
14
+ # Sidekiq::CurrentAttributes.persist("Myapp::Current")
15
+ #
16
+ module CurrentAttributes
17
+ class Save
18
+ include Sidekiq::ClientMiddleware
19
+
20
+ def initialize(cattr)
21
+ @strklass = cattr
22
+ end
23
+
24
+ def call(_, job, _, _)
25
+ attrs = @strklass.constantize.attributes
26
+ if attrs.any?
27
+ if job.has_key?("cattr")
28
+ job["cattr"].merge!(attrs)
29
+ else
30
+ job["cattr"] = attrs
31
+ end
32
+ end
33
+ yield
34
+ end
35
+ end
36
+
37
+ class Load
38
+ include Sidekiq::ServerMiddleware
39
+
40
+ def initialize(cattr)
41
+ @strklass = cattr
42
+ end
43
+
44
+ def call(_, job, _, &block)
45
+ if job.has_key?("cattr")
46
+ @strklass.constantize.set(job["cattr"], &block)
47
+ else
48
+ yield
49
+ end
50
+ end
51
+ end
52
+
53
+ def self.persist(klass, config = Sidekiq.default_configuration)
54
+ config.client_middleware.add Save, klass.to_s
55
+ config.server_middleware.add Load, klass.to_s
56
+ end
57
+ end
58
+ end
@@ -10,16 +10,18 @@ module Sidekiq::Middleware::I18n
10
10
  # Get the current locale and store it in the message
11
11
  # to be sent to Sidekiq.
12
12
  class Client
13
- def call(_worker, msg, _queue, _redis)
14
- msg["locale"] ||= I18n.locale
13
+ include Sidekiq::ClientMiddleware
14
+ def call(_jobclass, job, _queue, _redis)
15
+ job["locale"] ||= I18n.locale
15
16
  yield
16
17
  end
17
18
  end
18
19
 
19
20
  # Pull the msg locale out and set the current thread to use it.
20
21
  class Server
21
- def call(_worker, msg, _queue, &block)
22
- I18n.with_locale(msg.fetch("locale", I18n.default_locale), &block)
22
+ include Sidekiq::ServerMiddleware
23
+ def call(_jobclass, job, _queue, &block)
24
+ I18n.with_locale(job.fetch("locale", I18n.default_locale), &block)
23
25
  end
24
26
  end
25
27
  end
@@ -0,0 +1,21 @@
1
+ module Sidekiq
2
+ # Server-side middleware must import this Module in order
3
+ # to get access to server resources during `call`.
4
+ module ServerMiddleware
5
+ attr_accessor :config
6
+ def redis_pool
7
+ config.redis_pool
8
+ end
9
+
10
+ def logger
11
+ config.logger
12
+ end
13
+
14
+ def redis(&block)
15
+ config.redis(&block)
16
+ end
17
+ end
18
+
19
+ # no difference for now
20
+ ClientMiddleware = ServerMiddleware
21
+ end
@@ -17,7 +17,7 @@ class Sidekiq::Monitor
17
17
  end
18
18
  send(section)
19
19
  rescue => e
20
- puts "Couldn't get status: #{e}"
20
+ abort "Couldn't get status: #{e}"
21
21
  end
22
22
 
23
23
  def all
@@ -49,10 +49,25 @@ class Sidekiq::Monitor
49
49
  def processes
50
50
  puts "---- Processes (#{process_set.size}) ----"
51
51
  process_set.each_with_index do |process, index|
52
+ # Keep compatibility with legacy versions since we don't want to break sidekiqmon during rolling upgrades or downgrades.
53
+ #
54
+ # Before:
55
+ # ["default", "critical"]
56
+ #
57
+ # After:
58
+ # {"default" => 1, "critical" => 10}
59
+ queues =
60
+ if process["weights"]
61
+ process["weights"].sort_by { |queue| queue[0] }.map { |queue| queue.join(": ") }
62
+ else
63
+ process["queues"].sort
64
+ end
65
+
52
66
  puts "#{process["identity"]} #{tags_for(process)}"
53
67
  puts " Started: #{Time.at(process["started_at"])} (#{time_ago(process["started_at"])})"
54
68
  puts " Threads: #{process["concurrency"]} (#{process["busy"]} busy)"
55
- puts " Queues: #{split_multiline(process["queues"].sort, pad: 11)}"
69
+ puts " Queues: #{split_multiline(queues, pad: 11)}"
70
+ puts " Version: #{process["version"] || "Unknown"}" if process["version"] != Sidekiq::VERSION
56
71
  puts "" unless (index + 1) == process_set.size
57
72
  end
58
73
  end
@@ -62,7 +77,7 @@ class Sidekiq::Monitor
62
77
  columns = {
63
78
  name: [:ljust, (["name"] + queue_data.map(&:name)).map(&:length).max + COL_PAD],
64
79
  size: [:rjust, (["size"] + queue_data.map(&:size)).map(&:length).max + COL_PAD],
65
- latency: [:rjust, (["latency"] + queue_data.map(&:latency)).map(&:length).max + COL_PAD],
80
+ latency: [:rjust, (["latency"] + queue_data.map(&:latency)).map(&:length).max + COL_PAD]
66
81
  }
67
82
  columns.each { |col, (dir, width)| print col.to_s.upcase.public_send(dir, width) }
68
83
  puts
@@ -101,7 +116,7 @@ class Sidekiq::Monitor
101
116
  tags = [
102
117
  process["tag"],
103
118
  process["labels"],
104
- (process["quiet"] == "true" ? "quiet" : nil),
119
+ ((process["quiet"] == "true") ? "quiet" : nil)
105
120
  ].flatten.compact
106
121
  tags.any? ? "[#{tags.join("] [")}]" : nil
107
122
  end
@@ -3,7 +3,7 @@
3
3
  module Sidekiq
4
4
  module Paginator
5
5
  def page(key, pageidx = 1, page_size = 25, opts = nil)
6
- current_page = pageidx.to_i < 1 ? 1 : pageidx.to_i
6
+ current_page = (pageidx.to_i < 1) ? 1 : pageidx.to_i
7
7
  pageidx = current_page - 1
8
8
  total_size = 0
9
9
  items = []
@@ -16,22 +16,22 @@ module Sidekiq
16
16
 
17
17
  case type
18
18
  when "zset"
19
- total_size, items = conn.multi {
20
- conn.zcard(key)
19
+ total_size, items = conn.multi { |transaction|
20
+ transaction.zcard(key)
21
21
  if rev
22
- conn.zrevrange(key, starting, ending, with_scores: true)
22
+ transaction.zrevrange(key, starting, ending, withscores: true)
23
23
  else
24
- conn.zrange(key, starting, ending, with_scores: true)
24
+ transaction.zrange(key, starting, ending, withscores: true)
25
25
  end
26
26
  }
27
27
  [current_page, total_size, items]
28
28
  when "list"
29
- total_size, items = conn.multi {
30
- conn.llen(key)
29
+ total_size, items = conn.multi { |transaction|
30
+ transaction.llen(key)
31
31
  if rev
32
- conn.lrange(key, -ending - 1, -starting - 1)
32
+ transaction.lrange(key, -ending - 1, -starting - 1)
33
33
  else
34
- conn.lrange(key, starting, ending)
34
+ transaction.lrange(key, starting, ending)
35
35
  end
36
36
  }
37
37
  items.reverse! if rev
@@ -43,5 +43,13 @@ module Sidekiq
43
43
  end
44
44
  end
45
45
  end
46
+
47
+ def page_items(items, pageidx = 1, page_size = 25)
48
+ current_page = (pageidx.to_i < 1) ? 1 : pageidx.to_i
49
+ pageidx = current_page - 1
50
+ starting = pageidx * page_size
51
+ items = items.to_a
52
+ [current_page, items.size, items[starting, page_size]]
53
+ end
46
54
  end
47
55
  end