If we pass Rails controller params, directly to a Sidekiq worker then they are not parsed correctly by Sidekiq when the job is executed. Let's see an example.

class UsersController < ApplicationController def create CreateUserWorker.perform_async(user_params) end private def user_params params.require(:user).permit(:name, :email) end end class CreateUserWorker include Sidekiq::Worker def perform(params) User.create!(params) end end

This is because Sidekiq expects the arguments to be primitive types such as Hash , String , Boolean , Array etc.

Sidekiq uses JSON.generate to generate JSON and then pushes the job data to Redis. When the job is pulled from Redis for execution, Sidekiq parses the saved job data using JSON.parse and then passes it to the worker.

Let's see what is the output of JSON.generate with controller params as argument.

This is not the expected output because when Sidekiq will parse it, we will not get the ActionController::Parameters back.

We can see that the data is not parsed correctly, because the JSON payload was not generated correctly.

Ruby's JSON.generate method treats certain primitive objects such as Hash , String , True , False as special cases when generating JSON representation. Whereas for custom objects, it checks whether the object responds to to_json or not. If yes then it returns the output of calling to_json on that object. If the object does not respond to the to_json method, then it generates its JSON representation considering the object as String. This code can be found here.

In case of ActionController::Parameters objects, Ruby generates JSON representation considering it as String . Even though ActionController::Parameters gets to_json method from Active Support, still Ruby is not able to figure out that ActionController::Parameters objects respond to to_json .

# https://github.com/ruby/ruby/blob/8e517942656f095af2b3417f0df85ae0b216002a/ext/json/generator/generator.c#L1018 else if (rb_respond_to(obj, i_to_json)) { tmp = rb_funcall(obj, i_to_json, 1, Vstate); Check_Type(tmp, T_STRING); fbuffer_append_str(buffer, tmp); }

As ActionController::Parameters respond to to_json it should go into this branch of code but somehow it goes into the final else code branch.

I am not able to figure out why that is happening. Rohit Kumar pointed in comments on this post that when you call JSON.generate , the to_json method is not called on the instance of ActionController::Parameters which is the reason why Ruby does not use the to_json output defined by Active Support for serialization. I have not fully understood yet what does rb_funcall(obj, i_to_json, 1, Vstate); outputs in this case.

So to fix this, we need to pass the params as Hash object to the Sidekiq job. A simple way will be to pass the params after calling to_h on them.

class UsersController < ApplicationController def create CreateUserWorker.perform_async(user_params.to_h) end private def user_params params.require(:user).permit(:name, :email) end end

Importantly, to_h will result into an error when we have not permitted any of the parameters. You can also call to_unsafe_h to pass all the parameters without permitting.