[Feature Proposal] ActiveJob 'params' attribute support

Hi, I’m junior developer on Rails and trying to implement multi-tenant sharding support to project that i’m working on.

I’m trying implement this feature as it said in the guides here on section 7 Caveats which is:

7.1 Automatic swapping for horizontal sharding

While Rails now supports an API for connecting to and swapping connections of shards, it does not yet support an automatic swapping strategy. Any shard swapping will need to be done manually in your app via a middleware or around_action .

I defined an around_action method both on ApplicationController and ApplicationMailer that decides with shard should be connected based on the tenant. I get the tenant information via current_user method defined by Devise gem in controller but i don’t have the same on ApplicationMailer so i tried to pass the tenant information as parameter to mailer like this:

AccountMailer.with(account: current_user.account).inform_account.deliver_later

with this i can reach the tenant information in ApplicationMailer like this:

class ApplicationMailer < ActionMailer::Base
  around_action :db_shard, unless: :over_account_layer?


  def db_shard(&block)
    ActiveRecord::Base.connected_to(role: :writing, shard: fetch_shard, &block)
  end

  def fetch_shard
    params[:account].shard.to_sym
  end

  def over_account_layer?
    # If the mail is not bounded by a single account then DB connection must be set in that mailer
    params[:over_account_layer]
  end
end

This is my solution for the multi-tenant sharding support. But when i tried to do the same on the background jobs i couldn’t find the same functionality in the ActiveJob as in ActionMailer . ActiveJob has an attribute called arguments which is array not hash. I can do the similar functionality with this:

class ApplicationJob < ActiveJob::Base
  around_perform :db_shard, unless: :the_job_over_account_layer?
    
  def db_shard(&block)
    ActiveRecord::Base.connected_to(role: :writing, shard: fetch_shard, &block)
  end

  def fetch_shard
    # Assuming first argument will be always `Account` object
     arguments.first.shard.to_sym
  end

  def the_job_over_account_layer?
    # Things are getting difficult to handle here for me. I don't have a smooth solution here for now
    # to disable sharding here and let the sharding on the subclass.
  end
end

When i call a background job like this:

ExportProductsJob.perform_later(account, products)

I can get the account information with arguments.first. But i think this is not a smooth way to do it since its not explicit. I can pass all the parameters in hash to a job but this way the arguments array would looks like this:

#=> [{account: (account object), products: (products array)}]

Only one hash element in an array.

My Proposal:

It would be nice if i can call a background job similar to mailer like this:

ExportProductsJob.with(account: account).perform_later(account, products)

so that i could reach the account data in ApplicationJob as in ApplicationMailer with:

 def fetch_shard
    params[:account].shard.to_sym
 end

This is my first post here and I tried my best while i explaining the situation.I’m sorry for my grammar and semantic mistakes. Thanks in advance.

2 Likes

Take a look at how the apartment gem is doing it on a global level for all jobs:

I wrote a bit of a write-up on how to pass the “account” information from controller to ActiveJob here:

Let me know if that helps.

First of all sorry for the late reply. I examined the apartment gem and it seems very nice. But our project already a big one and its hard to change the whole architecture according to this structure. I wish we had this structure when the project first created. If we start a new project similar to this i will use this gem. Aside this i saw passing parameters with ActiveSupport::CurrentAttributes, in the link you shared. It seems easy to pass the data and use it but on the other hand i feel like this is too dangerous to use since the data sets on the global state.