Passing Rails controller params to Sidekiq
Tags: rails, sidekiq, ruby
Published: 2020-04-27 19:25:55

---
title: Passing Rails controller params to Sidekiq
published: true
date: 2020-04-20 18:31:02 UTC
tags: rails,sidekiq,ruby
canonical_url: https://prathamesh.tech/2020/04/21/passing-rails-controller-params-to-sidekiq/
---

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.

<!--kg-card-begin: code-->
```rb
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
```
<!--kg-card-end: code-->

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.

<!--kg-card-begin: code-->
```rb
JSON.generate user_params
=> "\"{\\\"user\\\"=>{\\\"name\\\"=>\\\"Prathamesh\\\", \\\"email\\\"=>\\\"prathamesh@exampless.com\\\"}}\""
```
<!--kg-card-end: code-->

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

<!--kg-card-begin: code-->
```rb
job_data = JSON.generate user_params

JSON.parse job_data
=> "{\"user\"=>{\"name\"=>\"Prathamesh\", \"email\"=>\"prathamesh@exampless.com\"}}"
```
<!--kg-card-end: code-->

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](https://github.com/ruby/ruby/blob/8e517942656f095af2b3417f0df85ae0b216002a/ext/json/generator/generator.c#L996-L1027).

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,](https://github.com/rails/rails/blob/0fac4559e1ccbdba1be8a4457dc666b3e560de94/activesupport/lib/active_support/core_ext/object/json.rb#L36) still Ruby is not able to figure out that `ActionController::Parameters` objects respond to `to_json`.

<!--kg-card-begin: code-->
```rb
# 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);
    }
```
<!--kg-card-end: code-->

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.

<!--kg-card-begin: code-->
```rb
# https://github.com/ruby/ruby/blob/8e517942656f095af2b3417f0df85ae0b216002a/ext/json/generator/generator.c#L1022-L1026

else {
        tmp = rb_funcall(obj, i_to_s, 0);
        Check_Type(tmp, T_STRING);
        generate_json_string(buffer, Vstate, state, tmp);
    }
```
<!--kg-card-end: code-->

I was not able to figure out why that is happening.

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.

<!--kg-card-begin: code-->
```rb
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
```
<!--kg-card-end: code-->

> 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.

Before Rails 5, `ActionController::Parameters` was inheriting from `Hash` class so the JSON serialization and deserialization was working properly without converting the params to a `Hash`. But now because `ActionController::Parameters` do not inherit from `Hash` anymore, we need to pass it to Sidekiq worker as a `Hash`.