Queue It

Gem Version CircleCI

This gem has been develope to manage recurrent processes that need someone (or something) responsable. For example, imagine you have a recurrent task for a certain group of people like responding the chat of your website. A recurrent queue would help you distribuit in a uniform way the workload of the people in the group. This gem installs an engine in your rails app.

Installation

Add to your Gemfile:

gem "rails-queue-it"
bundle install

Then, run the installer:

rails generate queue_it:install

Usage

For the purpose of this explanation we'll use the models Task and User to explain the workaround of this gem. Both models only have one attribute: :name.

Models of the gem

This gem implements the models QueueIt::Queue and QueueIt::Node, each queue has polymorphic relation with a queable object (i.e. a Task instace) and each node has a polymorphic relation with a nodable object (i.e. the object queued).

Before Starting

This gem uses ActiveRecord::Locking::Pessimistic so the behaviour with sqlite is not the same as with Postgres or MySql. Make sure you use one of the latters.

Queable concern

Add the QueueIt::Queable concern to the model you want to have a queue. In this case we'll ilustrate this with the task model:

class Task < ApplicationRecord
  include QueueIt::Queable
end

Concern methods

With the QueueIt::Queable concern added to your model you'll have access to a group of methods to manage the queue's behaviour. Note that in this version of the gem the model with the concern will have only one queue through a has_one relationship included in the concern.

Create a Queue

The method find_or_create_queue! present in the concern will return a new queue (in case it was not created) or the queue realated to the instance model.

task = Task.create!(name: 'Example Task')
task_queue = task.find_or_create_queue!

Add a node to the Queue

The method push_to_queue will allow you to add a nodable in the first position of the queue or the last one depending on the head_node param.

task = Task.create!(name: 'Example Task')
nodable = User.create!(name: 'Gabriel')
head_node = true
# add the user as the nodable object in the first node of the queue
task.push_to_queue(nodable, head_node)

Note that the second value (head_node) is optional with a default true value.

It's not necessary to create the queue manually. The push_to_queue method will execute find_or_create_queue! before adding the node.

Get next nodable/node of the queue

To obtain the next nodable/node of the queue we've implemented the methods get_next_in_queue and get_next_node_in_queue. The first one calls the second one but instead of returning the node returns the nodable object. First, let's add some nodes to the queue:

task = Task.create!(name: 'Example')
task.push_to_queue(User.create!(name: 'Gabriel'))
task.push_to_queue(User.create!(name: 'Leandro'))
task.push_to_queue(User.create!(name: 'Raimundo'))
# Note that the order of the queue is now: [Raimundo's node, Leandro's node, Gabriel's node]

Now with the get_next_in_queue method we'll the obtain user with name 'Raimundo' and the order of the queue will be updated.

nodable = task.get_next_in_queue
nodable.name == 'Raimundo' # true
# the new order of the queue will be: [Leandro's node, Gabriel's node, Raimundo's node]

Now if we would of used get_next_node_in_queue instead of get_next_in_queue method we would of obtained the node containing the user with name 'Raimundo' as nodable and the order of the queue would have been updated like before.

node = task.get_next_in_queue
node.nodable.name == 'Raimundo' # true
# the new order of the queue will be: [Leandro's node, Gabriel's node, Raimundo's node]

Formatted Queue

We included a method to obtain the queue formatted by an attribute/method. Note that the nodables will need to have the attribute or an implementation of the method called.

# creation of the queue
task = Task.create!(name: 'Example')
task.push_to_queue(User.create!(name: 'Gabriel'))
task.push_to_queue(User.create!(name: 'Leandro'))
task.push_to_queue(User.create!(name: 'Raimundo'))
# The order of the queue is now: [Raimundo's node, Leandro's node, Gabriel's node]
response = task.formatted_queue('name')
response == ['Raimundo', 'Leandro', 'Gabriel'] # true

Delete all the nodes of the queue

With the method delete_queue_nodes you'll be able to clean the queue.

task = Task.create!(name: 'Example')
task.push_to_queue(User.create!(name: 'Gabriel'))
task.push_to_queue(User.create!(name: 'Leandro'))
task.push_to_queue(User.create!(name: 'Raimundo'))
# delete de queue nodes
task.delete_queue_nodes
task.queue.size? == 0 # true

Remove a nodable object

In case you need to remove all the nodes of a queue containing an specific nodable you can use the remove_from_queue(nodable) method.

task = Task.create!(name: 'Example')
gabriel = User.create!(name: 'Gabriel')
leandro = User.create!(name: 'Leandro')
raimundo = User.create!(name: 'Raimundo')
task.push_to_queue(gabriel)
task.push_to_queue(leandro)
task.push_to_queue(raimundo)
task.push_to_queue(leandro)
# Now we have a queue that look's like this:
# [Leandro's node, Raimundo's node, Leandro's node, Gabriel's node]
task.remove_from_queue(leandro)
# The queue will now look like this: [Raimundo's node, Gabriel's node]

Development

Models and migrations

  • Create dummy app models with development and testing purposes inside the dummy app spec/dummy:

bin/rails g model user

The User model will be created in spec/dummy/app/models. The user_spec.rb file needs to be deleted, but it is a good idea to leave the factory.

  • Create engine related models inside the engine's root path '/':

bin/rails g model job

The EngineName::Job model will be created in app/models/engine_name. A factory will be added to engine_name/spec/factories/engine_name/jobs.rb, you must to add the class option manually.

  FactoryBot.define do
    factory :job, class: "EngineName::Job" do
      # ...
    end
  end
  • While developing the engine run migrations in the root path bin/rails db:migrate. This will apply the gem and dummy app migrations too.
  • When using in a project, the engine migrations must be copied to it. This can be done by running: bin/rails engine_name:install:migrations

Testing

To run the specs you need to execute, in the root path of the engine, the following command:

bundle exec guard

You need to put all your tests in the /queue_it/spec directory.

Publishing

On master/main branch...

  1. Change VERSION in lib/queue_it/version.rb.
  2. Change Unreleased title to current version in CHANGELOG.md.
  3. Run bundle install.
  4. Commit new release. For example: Releasing v0.1.0.
  5. Create tag. For example: git tag v0.1.0.
  6. Push tag. For example: git push origin v0.1.0.

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request

Credits

Thank you contributors!

Platanus

Queue It is maintained by platanus.

License

Queue It is © 2021 platanus, spa. It is free software and may be redistributed under the terms specified in the LICENSE file.