falqon 0.0.1 → 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ba4e39b3f74379b319b0946fd639ecb58fe7306f2f062edbeca42ad7cb3224f
4
- data.tar.gz: 91aa2ebc4e44d890ad6343b85960d968aa33b10ef158a393f2119e8561db75d4
3
+ metadata.gz: 2a6de2c2359a6d2aebf3f7f0531b9dea885d46f579a64aa0b46036c204c7df52
4
+ data.tar.gz: 8896a1d994cd209f436c8c260c5eba7985c9b3055064f186e26d7ff8452f7407
5
5
  SHA512:
6
- metadata.gz: 6ebe08c3284edf73c79a37a0fb909dec8ce12cb52790426b0aeaad175000255204b09c1ead7834eac35872893825d43128b18567d18b7e9c3a919aef00e48c40
7
- data.tar.gz: a738b12bdcf0776ee1008cf833e87477e08d6a551c6881cbe92945ea9499e0830fd81908beaa6ca184809ca32a14350fc960fda728a47d365b01d43434c52e1a
6
+ metadata.gz: 465c3dc7415da3ee83118227916a0d21a653fd8c74b2876a73e2510e545e7b210d0a96425d3f014ad997516f9314fea9de819b81deb373ec949162a3b7d6ca15
7
+ data.tar.gz: 231afe294759e0cc1fe0b0a884a99044808021e0d6d1f7201a8406f7a5b0b0128c6821f25b6d743d9c2475e9397c94dce10fc12ec831fbd123c7875495e067a1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
- ## Falqon v0.0.1 (2022-06-22)
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+
12
+ ### Changed
13
+
14
+ ### Removed
15
+
16
+ ## Falqon v1.0.0 (2024-12-07)
17
+
18
+ ### Changed
19
+
20
+ - Use atomic execution (MULTI/EXEC) where possible.
21
+
22
+ ## Falqon v0.1.0 (2024-11-16)
4
23
 
5
24
  Initial release
25
+
26
+ ## Falqon v0.0.1 (2023-06-22)
27
+
28
+ [unreleased]: https://github.com/floriandejonckheere/falqon/compare/v1.0.0...HEAD
29
+ [1.0.0]: https://github.com/floriandejonckheere/falqon-pro/releases/tag/v1.0.0
30
+ [0.1.0]: https://github.com/floriandejonckheere/falqon-pro/releases/tag/v0.1.0
data/Gemfile CHANGED
@@ -5,13 +5,45 @@ source "https://rubygems.org"
5
5
  # Specify your gem's dependencies in falqon.gemspec
6
6
  gemspec
7
7
 
8
+ # Task runner
9
+ gem "rake", "13.2.1", require: false
10
+
11
+ group :development do
12
+ # Documentation
13
+ gem "yard", "0.9.37", require: false
14
+ gem "yard-sorbet", "0.9.0", require: false
15
+
16
+ # Simple server
17
+ gem "webrick", "1.9.1", require: false
18
+ end
19
+
8
20
  group :development, :test do
9
- gem "debug", require: false
10
- gem "ffaker", require: false
11
- gem "rake", require: false
12
- gem "rspec", require: false
13
- gem "rubocop", require: false
14
- gem "rubocop-performance", require: false
15
- gem "rubocop-rspec", require: false
16
- gem "timecop", require: false
21
+ # Debugger
22
+ gem "debug", "1.9.2", require: false
23
+
24
+ # Build objects for tests
25
+ gem "factory_bot", "6.5.0", require: false
26
+
27
+ # Generate fake data
28
+ gem "ffaker", "2.23.0", require: false
29
+
30
+ # Mock Redis server
31
+ gem "mock_redis", "0.48.1", require: false
32
+
33
+ # Behavior-driven test framework
34
+ gem "rspec", "3.13.0", require: false
35
+
36
+ # Linter
37
+ gem "rubocop", "1.69.0", require: false
38
+ gem "rubocop-factory_bot", "2.26.1", require: false
39
+ gem "rubocop-performance", "1.23.0", require: false
40
+ gem "rubocop-rake", "0.6.0", require: false
41
+ gem "rubocop-rspec", "3.2.0", require: false
42
+
43
+ # Type checker
44
+ gem "sorbet", "0.5.11672", require: false
45
+ gem "tapioca", "0.16.5", require: false
46
+
47
+ # Time control
48
+ gem "timecop", "0.9.10", require: false
17
49
  end
data/README.md CHANGED
@@ -1,13 +1,40 @@
1
1
  # Falqon
2
-
3
- ![Continuous Integration](https://github.com/floriandejonckheere/falqon/workflows/Continuous%20Integration/badge.svg)
2
+ [![Continuous Integration](https://github.com/floriandejonckheere/falqon/actions/workflows/ci.yml/badge.svg)](https://github.com/floriandejonckheere/falqon/actions/workflows/ci.yml)
4
3
  ![Release](https://img.shields.io/github/v/release/floriandejonckheere/falqon?label=Latest%20release)
5
4
 
6
- Simple, efficient messaging queue for Ruby.
5
+ Simple, efficient, and reliable messaging queue for Ruby.
6
+
7
+ Falqon is a simple messaging queue implementation, backed by the in-memory Redis key-value store.
8
+ It exposes a simple Ruby API to send and receive messages between different processes, between threads in the same process, or even fibers in the same thread.
9
+ It is perfect when you require a lightweight solution for processing messages, but don't want to deal with the complexity of a full-blown message queue like RabbitMQ or Kafka.
10
+
11
+ See the [documentation](https://docs.falqon.dev) for more information on how to use Falqon in your application.
12
+
13
+ ## Features
14
+
15
+ Falqon offers an elegant solution for messaging queues in Ruby.
16
+
17
+ - Elegant: only two methods to send and receive messages
18
+ - Reliable: no data is lost when a client crashes unexpectedly
19
+ - Fast: Falqon is built on top of Redis, a fast in-memory data store
20
+ - Flexible: tune the behaviour of the queue to your needs
21
+
22
+ ## Get started
23
+
24
+ - Install Falqon and get working with messaging queues in a heartbeat using the [quickstart guide](#quickstart)
25
+ - Check out the [API documentation](https://docs.falqon.dev/) for more information on how to use Falqon in your application
26
+ - Check out the [CLI documentation](https://docs.falqon.dev/Falqon/CLI.html) for more information on how to manage queues and messages from the command line
27
+ - Read the [architecture documentation](#architecture) to learn more about how Falqon works under the hood
28
+ - Read about [Falqon Pro](https://docs.falqon.dev/pro), the commercial addon for Falqon that offers additional features
29
+
30
+ ## Quickstart
31
+
32
+ ### Requirements
7
33
 
8
- Falqon offers a simple messaging queue implementation backed by Redis.
34
+ Falqon requires a Redis 6+ server to be available.
35
+ Use the [docker-compose.yml](https://github.com/floriandejonckheere/falqon/blob/master/docker-compose.yml) file to quickly spin up a Redis server.
9
36
 
10
- ## Installation
37
+ ### Installation
11
38
 
12
39
  Add this line to your application's Gemfile:
13
40
 
@@ -23,14 +50,72 @@ Or install it yourself as:
23
50
 
24
51
  $ gem install falqon
25
52
 
26
- ## Usage
53
+ ### Configuration
54
+
55
+ The default configuration works out of the box with the provided `docker-compose.yml` file.
56
+ See [configuration](https://docs.falqon.dev/Falqon/Configuration.html) if you want to adjust the configuration.
57
+
58
+ ### Usage
27
59
 
28
60
  ```ruby
29
61
  require "falqon"
30
62
 
31
- # TODO
63
+ queue = Falqon::Queue.new("my_queue")
64
+
65
+ # Push a message to the queue
66
+ queue.push("Hello, world!", "Hello, world again!")
67
+
68
+ # Pop a message from the queue (return style)
69
+ puts queue.pop # => "Hello, world!"
70
+
71
+ queue.empty? # => false
72
+
73
+ queue.peek # => "Hello, world again!"
74
+
75
+ # Pop a message from the queue (block style)
76
+ queue.pop do |message|
77
+ puts message # => "Hello, world again!"
78
+
79
+ # Raising a Falqon::Error exception will cause the message to be requeued
80
+ raise Falqon::Error, "Something went wrong"
81
+ end
82
+
83
+ queue.empty? # => false
84
+
85
+ puts queue.pop # => "Hello, world again!"
86
+
87
+ queue.empty? # => true
88
+
89
+ queue.peek # => nil
32
90
  ```
33
91
 
92
+ For more comprehensive examples, see the [examples directory](examples/) in the repository.
93
+
94
+ ## Architecture
95
+
96
+ A queue is identified with a name, which is used as a key prefix.
97
+ Queues are stored in Redis as a list of incrementing integers representing unique message identifiers.
98
+ The messages itself are stored in Redis as strings.
99
+
100
+ The following Redis keys are used to store data.
101
+
102
+ - `[{prefix}:]queues`: set of queue names
103
+
104
+ - `[{prefix}/]{name}`: list of message identifiers on the (pending) queue
105
+
106
+ - `[{prefix}/]{name}:id`: message identifier sequence
107
+
108
+ - `[{prefix}/]{name}:processing`: list of message identifiers being processed
109
+
110
+ - `[{prefix}/]{name}:scheduled`: list of message identifiers scheduled to retry
111
+
112
+ - `[{prefix}/]{name}:dead`: list of message identifiers that have been discarded
113
+
114
+ - `[{prefix}/]{name}:data:{id}`: message data for identifier `{id}`
115
+
116
+ - `[{prefix}/]{name}:metadata`: metadata for the queue
117
+
118
+ - `[{prefix}/]{name}:metadata:{id}`: metadata for identifier `{id}`
34
119
 
35
120
  ## Testing
36
121
 
@@ -44,10 +129,25 @@ bundle exec rspec
44
129
  To release a new version, update the version number in `lib/falqon/version.rb`, update the changelog, commit the files and create a git tag starting with `v`, and push it to the repository.
45
130
  Github Actions will automatically run the test suite, build the `.gem` file and push it to [rubygems.org](https://rubygems.org).
46
131
 
132
+ ## Documentation
133
+
134
+ The documentation in `docs/` is automatically built by [YARD](https://yardoc.org) and pushed to [docs.falqon.dev](https://docs.falqon.dev) on every push to the `main` branch.
135
+ Locally, you can build the documentation using the following commands:
136
+
137
+ ```sh
138
+ rake yard
139
+ ```
140
+
141
+ In development, you can start a local server to preview the documentation:
142
+
143
+ ```sh
144
+ yard server --reload
145
+ ```
146
+
47
147
  ## Contributing
48
148
 
49
149
  Bug reports and pull requests are welcome on GitHub at [https://github.com/floriandejonckheere/falqon](https://github.com/floriandejonckheere/falqon).
50
150
 
51
151
  ## License
52
152
 
53
- The software is available as open source under the terms of the [LGPLv3 License](https://www.gnu.org/licenses/lgpl-3.0.html).
153
+ The software is available as open source under the terms of the [LGPL-3.0 License](https://www.gnu.org/licenses/lgpl-3.0.html).
data/bin/falqon ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+
6
+ require "falqon"
7
+
8
+ Falqon::CLI.start(ARGV)
@@ -1,3 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Falqon.loader.inflector.inflect({})
3
+ Falqon.loader.inflector.inflect({
4
+ "cli" => "CLI",
5
+ })
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Falqon
4
+ class CLI
5
+ # @!visibility private
6
+ class Base
7
+ attr_reader :options
8
+
9
+ def initialize(options = {})
10
+ @options = options
11
+ end
12
+
13
+ def call
14
+ validate
15
+ execute
16
+ rescue StandardError => e
17
+ puts e.message
18
+ end
19
+
20
+ protected
21
+
22
+ def validate
23
+ raise NotImplementedError
24
+ end
25
+
26
+ def execute
27
+ raise NotImplementedError
28
+ end
29
+
30
+ def pluralize(count, singular, plural)
31
+ "#{count} #{count == 1 ? singular : plural}"
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Falqon
4
+ class CLI
5
+ # Clear messages from a queue
6
+ #
7
+ # Clearing a subqueue removes all messages and their data from the subqueue.
8
+ #
9
+ # Usage:
10
+ # falqon clear -q, --queue=QUEUE
11
+ #
12
+ # Options:
13
+ # -q, --queue=QUEUE # Queue name
14
+ # [--pending], [--no-pending], [--skip-pending] # Clear only pending messages
15
+ # [--processing], [--no-processing], [--skip-processing] # Clear only processing messages
16
+ # [--dead], [--no-dead], [--skip-dead] # Clear only dead messages
17
+ #
18
+ # If none of the +--pending+, +--processing+, +--scheduled+, or +--dead+ options are specified, all messages are cleared.
19
+ #
20
+ # @example Clear all messages in a queue
21
+ # $ falqon clear --queue jobs
22
+ # Cleared 3 messages from queue jobs
23
+ #
24
+ # @example Clear only pending messages
25
+ # $ falqon clear --queue jobs --pending
26
+ # Cleared 3 messages from queue jobs
27
+ #
28
+ # @example Clear only processing messages
29
+ # $ falqon clear --queue jobs --processing
30
+ # Cleared 3 messages from queue jobs
31
+ #
32
+ # @example Clear only scheduled messages
33
+ # $ falqon clear --queue jobs --scheduled
34
+ # Cleared 3 messages from queue jobs
35
+ #
36
+ # @example Clear only dead messages
37
+ # $ falqon clear --queue jobs --dead
38
+ # Cleared 3 messages from queue jobs
39
+ #
40
+ class Clear < Base
41
+ # @!visibility private
42
+ def validate
43
+ raise "No queue registered with this name: #{options[:queue]}" if options[:queue] && !Falqon::Queue.all.map(&:name).include?(options[:queue])
44
+ raise "--pending, --processing, --scheduled, and --dead are mutually exclusive" if [options[:pending], options[:processing], options[:scheduled], options[:dead]].count(true) > 1
45
+ end
46
+
47
+ # @!visibility private
48
+ def execute
49
+ # Clear messages
50
+ ids = subqueue.clear
51
+
52
+ if options[:pending]
53
+ puts "Cleared #{pluralize(ids.count, 'pending message', 'pending messages')} from queue #{queue.name}"
54
+ elsif options[:processing]
55
+ puts "Cleared #{pluralize(ids.count, 'processing message', 'processing messages')} from queue #{queue.name}"
56
+ elsif options[:scheduled]
57
+ puts "Cleared #{pluralize(ids.count, 'scheduled message', 'scheduled messages')} from queue #{queue.name}"
58
+ elsif options[:dead]
59
+ puts "Cleared #{pluralize(ids.count, 'dead message', 'dead messages')} from queue #{queue.name}"
60
+ else
61
+ puts "Cleared #{pluralize(ids.count, 'message', 'messages')} from queue #{queue.name}"
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def queue
68
+ @queue ||= Falqon::Queue.new(options[:queue])
69
+ end
70
+
71
+ def subqueue
72
+ @subqueue ||= if options[:pending]
73
+ queue.pending
74
+ elsif options[:processing]
75
+ queue.processing
76
+ elsif options[:scheduled]
77
+ queue.scheduled
78
+ elsif options[:dead]
79
+ queue.dead
80
+ else
81
+ queue
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Falqon
4
+ class CLI
5
+ # Delete messages from a queue
6
+ #
7
+ # Deleting a message removes it including its data from the queue.
8
+ #
9
+ # Usage:
10
+ # falqon delete -q, --queue=QUEUE
11
+ #
12
+ # Options:
13
+ # -q, --queue=QUEUE # Queue name
14
+ # [--pending], [--no-pending], [--skip-pending] # Delete only pending messages (default)
15
+ # [--processing], [--no-processing], [--skip-processing] # Delete only processing messages
16
+ # [--dead], [--no-dead], [--skip-dead] # Delete only dead messages
17
+ # [--head=N] # Delete N messages from head of queue
18
+ # [--tail=N] # Delete N messages from tail of queue
19
+ # [--index=N] # Delete message at index N
20
+ # [--range=N M] # Delete messages at index N to M
21
+ # [--id=N] # Delete message with ID N
22
+ #
23
+ # @example Delete all messages in the queue (by default only pending messages are deleted)
24
+ # $ falqon delete --queue jobs
25
+ # Deleted 10 messages from queue jobs
26
+ #
27
+ # @example Delete only pending messages
28
+ # $ falqon delete --queue jobs --pending
29
+ # Deleted 10 pending messages from queue jobs
30
+ #
31
+ # @example Delete only processing messages
32
+ # $ falqon delete --queue jobs --processing
33
+ # Deleted 1 processing message from queue jobs
34
+ #
35
+ # @example Delete only scheduled messages
36
+ # $ falqon delete --queue jobs --scheduled
37
+ # Deleted 1 scheduled message from queue jobs
38
+ #
39
+ # @example Delete only dead messages
40
+ # $ falqon delete --queue jobs --dead
41
+ # Deleted 5 dead messages from queue jobs
42
+ #
43
+ # @example Delete first 5 messages
44
+ # $ falqon delete --queue jobs --head 5
45
+ # Deleted 5 messages from queue jobs
46
+ #
47
+ # @example Delete last 5 messages
48
+ # $ falqon delete --queue jobs --tail 5
49
+ # Deleted 5 messages from queue jobs
50
+ #
51
+ # @example Delete message at index 5
52
+ # $ falqon delete --queue jobs --index 3 --index 5
53
+ # Deleted 1 message from queue jobs
54
+ #
55
+ # @example Delete messages from index 5 to 10
56
+ # $ falqon delete --queue jobs --range 5 10
57
+ # Deleted 6 messages from queue jobs
58
+ #
59
+ # @example Delete message with ID 5
60
+ # $ falqon delete --queue jobs --id 5 --id 1
61
+ # Deleted 2 messages from queue jobs
62
+ #
63
+ class Delete < Base
64
+ # @!visibility private
65
+ def validate
66
+ raise "No queue registered with this name: #{options[:queue]}" if options[:queue] && !Falqon::Queue.all.map(&:name).include?(options[:queue])
67
+
68
+ raise "--pending, --processing, --scheduled, and --dead are mutually exclusive" if [options[:pending], options[:processing], options[:scheduled], options[:dead]].count(true) > 1
69
+
70
+ raise "--head, --tail, --index, and --range are mutually exclusive" if [options[:head], options[:tail], options[:index], options[:range]].count { |o| o } > 1
71
+ raise "--range must be specified as two integers" if options[:range] && options[:range].count != 2
72
+
73
+ raise "--id is mutually exclusive with --head, --tail, --index, and --range" if options[:id] && [options[:head], options[:tail], options[:index], options[:range]].count { |o| o }.positive?
74
+ end
75
+
76
+ # @!visibility private
77
+ def execute
78
+ # Collect identifiers
79
+ ids = if options[:id]
80
+ Array(options[:id])
81
+ elsif options[:index]
82
+ Array(options[:index]).map do |i|
83
+ subqueue.peek(index: i) || raise("No message at index #{i}")
84
+ end
85
+ else
86
+ start, stop = range_options
87
+
88
+ subqueue.range(start:, stop:).map(&:to_i)
89
+ end
90
+
91
+ # Transform identifiers to messages
92
+ messages = ids.map do |id|
93
+ message = Falqon::Message.new(queue, id: id.to_i)
94
+
95
+ raise "No message with ID #{id}" unless message.exists?
96
+
97
+ message
98
+ end
99
+
100
+ # Delete messages
101
+ messages.each(&:delete)
102
+
103
+ if options[:processing]
104
+ puts "Deleted #{pluralize(messages.count, 'processing message', 'processing messages')} from queue #{queue.name}"
105
+ elsif options[:scheduled]
106
+ puts "Deleted #{pluralize(messages.count, 'scheduled message', 'scheduled messages')} from queue #{queue.name}"
107
+ elsif options[:dead]
108
+ puts "Deleted #{pluralize(messages.count, 'dead message', 'dead messages')} from queue #{queue.name}"
109
+ else # options[:pending]
110
+ puts "Deleted #{pluralize(messages.count, 'pending message', 'pending messages')} from queue #{queue.name}"
111
+ end
112
+ end
113
+
114
+ private
115
+
116
+ def queue
117
+ @queue ||= Falqon::Queue.new(options[:queue])
118
+ end
119
+
120
+ def subqueue
121
+ @subqueue ||= if options[:processing]
122
+ queue.processing
123
+ elsif options[:scheduled]
124
+ queue.scheduled
125
+ elsif options[:dead]
126
+ queue.dead
127
+ else # options[:pending]
128
+ queue.pending
129
+ end
130
+ end
131
+
132
+ def range_options
133
+ if options[:tail]
134
+ [
135
+ -options[:tail],
136
+ -1,
137
+ ]
138
+ elsif options[:range]
139
+ [
140
+ options[:range].first,
141
+ options[:range].last,
142
+ ]
143
+ else # options[:head]
144
+ [
145
+ 0,
146
+ options.fetch(:head, 0) - 1,
147
+ ]
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Falqon
4
+ class CLI
5
+ # Kill messages in a queue
6
+ #
7
+ # Killing a message removes it from the pending or scheduled queue, and moves it to the dead queue.
8
+ #
9
+ # Usage:
10
+ # falqon kill -q, --queue=QUEUE
11
+ #
12
+ # Options:
13
+ # -q, --queue=QUEUE # Queue name
14
+ # [--pending], [--no-pending], [--skip-pending] # Kill only pending messages (default)
15
+ # [--processing], [--no-processing], [--skip-processing] # Kill only processing messages
16
+ # [--head=N] # Kill N messages from head of queue
17
+ # [--tail=N] # Kill N messages from tail of queue
18
+ # [--index=N] # Kill message at index N
19
+ # [--range=N M] # Kill messages at index N to M
20
+ # [--id=N] # Kill message with ID N
21
+ #
22
+ # @example Kill all messages in the queue (by default only pending messages are killed)
23
+ # $ falqon kill --queue jobs
24
+ # Killed 10 messages from queue jobs
25
+ #
26
+ # @example Kill only pending messages
27
+ # $ falqon kill --queue jobs --pending
28
+ # Killed 10 pending messages from queue jobs
29
+ #
30
+ # @example Kill only processing messages
31
+ # $ falqon kill --queue jobs --processing
32
+ # Killed 1 processing message from queue jobs
33
+ #
34
+ # @example Kill only scheduled messages
35
+ # $ falqon kill --queue jobs --scheduled
36
+ # Killed 1 scheduled message from queue jobs
37
+ #
38
+ # @example Kill first 5 messages
39
+ # $ falqon kill --queue jobs --head 5
40
+ # Killed 5 messages from queue jobs
41
+ #
42
+ # @example Kill last 5 messages
43
+ # $ falqon kill --queue jobs --tail 5
44
+ # Killed 5 messages from queue jobs
45
+ #
46
+ # @example Kill message at index 5
47
+ # $ falqon kill --queue jobs --index 3 --index 5
48
+ # Killed 1 message from queue jobs
49
+ #
50
+ # @example Kill messages from index 5 to 10
51
+ # $ falqon kill --queue jobs --range 5 10
52
+ # Killed 6 messages from queue jobs
53
+ #
54
+ # @example Kill message with ID 5
55
+ # $ falqon kill --queue jobs --id 5 --id 1
56
+ # Killed 2 messages from queue jobs
57
+ #
58
+ class Kill < Base
59
+ # @!visibility private
60
+ def validate
61
+ raise "No queue registered with this name: #{options[:queue]}" if options[:queue] && !Falqon::Queue.all.map(&:name).include?(options[:queue])
62
+
63
+ raise "--pending, --scheduled, and --processing are mutually exclusive" if [options[:pending], options[:scheduled], options[:processing]].count(true) > 1
64
+
65
+ raise "--head, --tail, --index, and --range are mutually exclusive" if [options[:head], options[:tail], options[:index], options[:range]].count { |o| o } > 1
66
+ raise "--range must be specified as two integers" if options[:range] && options[:range].count != 2
67
+
68
+ raise "--id is mutually exclusive with --head, --tail, --index, and --range" if options[:id] && [options[:head], options[:tail], options[:index], options[:range]].count { |o| o }.positive?
69
+ end
70
+
71
+ # @!visibility private
72
+ def execute
73
+ # Collect identifiers
74
+ ids = if options[:id]
75
+ Array(options[:id])
76
+ elsif options[:index]
77
+ Array(options[:index]).map do |i|
78
+ subqueue.peek(index: i) || raise("No message at index #{i}")
79
+ end
80
+ else
81
+ start, stop = range_options
82
+
83
+ subqueue.range(start:, stop:).map(&:to_i)
84
+ end
85
+
86
+ # Transform identifiers to messages
87
+ messages = ids.map do |id|
88
+ message = Falqon::Message.new(queue, id: id.to_i)
89
+
90
+ raise "No message with ID #{id}" unless message.exists?
91
+
92
+ message
93
+ end
94
+
95
+ # Kill messages
96
+ messages.each(&:kill)
97
+
98
+ if options[:processing]
99
+ puts "Killed #{pluralize(messages.count, 'processing message', 'processing messages')} in queue #{queue.name}"
100
+ elsif options[:scheduled]
101
+ puts "Killed #{pluralize(messages.count, 'scheduled message', 'scheduled messages')} in queue #{queue.name}"
102
+ else # options[:pending]
103
+ puts "Killed #{pluralize(messages.count, 'pending message', 'pending messages')} in queue #{queue.name}"
104
+ end
105
+ end
106
+
107
+ private
108
+
109
+ def queue
110
+ @queue ||= Falqon::Queue.new(options[:queue])
111
+ end
112
+
113
+ def subqueue
114
+ @subqueue ||= if options[:processing]
115
+ queue.processing
116
+ elsif options[:scheduled]
117
+ queue.scheduled
118
+ else # options[:pending]
119
+ queue.pending
120
+ end
121
+ end
122
+
123
+ def range_options
124
+ if options[:tail]
125
+ [
126
+ -options[:tail],
127
+ -1,
128
+ ]
129
+ elsif options[:range]
130
+ [
131
+ options[:range].first,
132
+ options[:range].last,
133
+ ]
134
+ else # options[:head]
135
+ [
136
+ 0,
137
+ options.fetch(:head, 0) - 1,
138
+ ]
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end