Tag: Rails

Adding reentrancy and a on failure fallback for your Sidekiq workers

Few months ago I've created a post about reentrancy: Ruby (Rails, Sinatra) background processing – Reentrancy for your workers is a must be!.

In this post, I will present a nice way to implement such feature for your Sidekiq workers.

Simple reentrancy

Normally a Sidekiq worker looks similar to this one:

class ExampleWorker
  include Sidekiq::Worker

  def perform(*args)
    # Background logic here
  end
end

and if something goes wrong, you should see it in your Sidekiq log (or a bugtracker like Errbit). However there is no reentrancy there. You could catch exceptions and handle reentrancy with this code:

class ExampleWorker
  include Sidekiq::Worker

  def perform(*args)
    # Background logic here
  rescue => exception
    # Do something on failure before reraising
    raise exception
  end
end

but it is not too elegant and if you have multiple Sidekiq workers, than probably you will end-up with a lot of code duplication.

Making your reentrancy code more fancy

Instead of handling reentrancy in every single worker, you could just create a base worker class, that would provide such functionality for all the workers that would inherit from the base one:

class BaseWorker
  include Sidekiq::Worker

  def perform(*args)
    # you need to implement the execute method
    # execute method should contain code you want to execute in the background
    execute(*args)
  rescue => exception
    after_failure(*args) if respond_to?(:after_failure)
    raise exception
  end
end

Now, instead of implementing a perform method in every worker, you need to name it (or rename) execute. Perform method will act as a wrapper that will try to execute your worker code and if it fails, will run the after_failure method (if it exists).

Note that the error will be reraised, but now we have a fallback to do for example some database status changes.

class KeywordsWorker < BaseWorker
  def execute(keyword_name)
    KeywordsService.new.remotely(keyword_name)
  end

  # Bring to an expire state if something goes wrong
  def after_failure(keyword_name)
    KeywordsService.new.expire(keyword_name)
  end
end

Of course we might have workers, that won't require reentrancy at all. Then we just skip the after_failure method and thanks to the respond_to? method, everything will work normally.

Ruby on Rails: Migrating to Devise from your own authentication engine – Using custom Encryptors

Having your own authentication engine can be fun. You get to know how things work, why you should use salt, pepper, SHA2 instead of MD5 and much more. It also allows you to work with many old systems built before anyone heard about Devise. Still, I must say, that in old, maintained systems, sometimes it is worth throwing your own solution in favour of something that is already out there. Thanks to this, you won't have to support the whole dedicated authentication engine stack (code, tests, docs). I decided to do exactly this: move from my own engine that was maintained for last 6 years to the Devise based authentication.

Moving whole controllers logic is quite simple: you just drop whatever you have and you use Devise stuff ;) but what about your custom, self-build encryption engine? The easiest approach would be to reset all the passwords and ask users to provide a new one again. Unfortunately it is not as user-friendly as we would want it to be. Users might feel afraid that we ask them for their password again not in the sign in process.

Luckily there's a much easier approach: you can just use your current custom encryptor with Devise (as long as it is safe).

To do this, you need to do following things:

  1. Adding devise-encryptable to your Gemfile
  2. Moving your encryption logic to a Devise proper namespace
  3. Setting your encryption engine as a default one for Devise

After that, you should be able to use Devise with any encryption engine you used to.

Adding devise-encryptable to your Gemfile

This is definitely the easiest part. In your Gemfile file just:

gem 'devise'
gem 'devise-encryptable'

and run bundle install.

Moving your encryption logic to a Devise proper namespace

This is the hardest part. Create a file in your initializers (or add it to /config/initializers/devise.rb). It should contain your encryptor inside following modules:

module Devise
  module Encryptable
    module Encryptors
      # Here you should but encryption class that inherits from Base
    end
  end
end

Inside of it, you need to create a class that will correspond to your encrypion engine. It must inherit from Encryptors Base class and should contain one method called digest:

module Devise
  module Encryptable
    module Encryptors
      class CustomAuthentication < Base
        def self.digest(password, stretches, salt, pepper)
        end
      end
    end
  end
end

This method accepts following parameters:

  1. password - password provided by user
  2. stretches - cost for hashing the password (default 10)
  3. salt - salt that should be used for hashing
  4. pepper - pepper that should be used for hashing

Now, once you have all of this, you should just implement your logic in the digest method and return a password hash. For example like this one:

require "digest/sha2"

module Devise
  module Encryptable
    module Encryptors
      class CustomAuthentication < Base
        def self.digest(password, stretches, salt, pepper)
          string_to_hash = "#{pepper}#{salt}#{password.reverse}"
          Digest::SHA2.hexdigest(string_to_hash)
        end
      end
    end
  end
end

Setting your encryption engine as a default one for Devise

Now in your devise.rb config file set:

# Require the `devise-encryptable` gem when using anything other than bcrypt
config.encryptor = :custom_authentication

also keep in mind, that your devise using models should include encryptable options as well:

devise :database_authenticatable,
  :trackable, :encryptable, :confirmable, :recoverable,
  :registerable, :validatable, :lockable, :rememberable,
  :omniauthable, omniauth_providers: [:facebook]

Copyright © 2025 Closer to Code

Theme by Anders NorenUp ↑