Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pass in a hash with symbol as key #3893

Closed
mech opened this issue Jul 18, 2018 · 12 comments
Closed

Pass in a hash with symbol as key #3893

mech opened this issue Jul 18, 2018 · 12 comments

Comments

@mech
Copy link

mech commented Jul 18, 2018

Ruby version: 2.5.1
Sidekiq / Pro / Enterprise version(s): 5.1.3

I have read the Best Practice and know that symbol is not to be used and it will convert to string due to JSON.dump, but when I do a simple test at Rails 5.2, it appear to not be the case.

class TestSymbol < ApplicationJob
  def perform(hash)
    TestMailer.hello(hash['version']).deliver_now
  end
end

TestSymbol.perform_later({version: '123'})

Notice that I use hash['version'] as I expect the symbol to be a String, but it appear to be a symbol anyway.

I thought the argument of {version: '123'} will be converted to {'version' => '123'}, so I should reference it as hash['version'] in my worker code?

@mperham
Copy link
Collaborator

mperham commented Jul 18, 2018

You are using ActuveJob, not Sidekiq, to handle the job. It handles symbols.

@mech
Copy link
Author

mech commented Jul 18, 2018

I think I get what you mean now. Somehow ActiveJob will retain the symbol key in the hash while Sidekiq will convert it to a String. Too totally different approach I guess. Got it!

@mech mech closed this as completed Jul 18, 2018
@olivierlacan
Copy link

@mperham Is this documented behavior because it's extremely surprising to queue a job with a symbol-keyed hash and see exceptions roll in because the code in perform receives a hash with string keys instead.

To me this is an Sidekiq onboarding gotcha that I feel I should be warned against somehow, for example when perform receives hashes with symbol keys as arguments.

It doesn't feel like I should need a gem like this to use Sidekiq without scary surprises before/after JSON serialization: https://github.com/aprescott/sidekiq-symbols

I can definitely use it, but it feels like a bit of unnecessary friction.

@mperham
Copy link
Collaborator

mperham commented Aug 15, 2019

It’s item #1 on the Best Practices wiki page. Sidekiq has been native JSON since day one.

@olivierlacan
Copy link

That's true but as it's written there's some room for confusion:

The arguments you pass to perform_async must be composed of simple JSON datatypes: string, integer, float, boolean, null(nil), array and hash. This means you must not use ruby symbols as arguments.

Reading this I can think that hash is fine to use because technically I'm not using a symbol as the argument to perform, but I am using a hash with symbol keys. I think it'd be better to specify that only hashes with JSON-like string keys should be used.

I'm not saying do my homework for me, but I am saying the library could enforce this best practice (only in the test env to avoid performance issues for example). I've used Sidekiq for years without knowing this hash-related gotcha perhaps because I haven't tried to pass a hash to perform_async until today. It's a sharp edge. Although it didn't take long to recover, it di take an exception monitoring tool outputting worker args for me to notice.

Also, thank you so much for Sidekiq. 99.999999% of the time it brings me joy. ❤️

@mperham
Copy link
Collaborator

mperham commented Aug 15, 2019 via email

@olivierlacan
Copy link

Seems like strict mode wouldn't be on by default which might help newcomers and goofballs like me but at the same time introducing a "strict" deep parameter check would probably piss off people so I guess it could be opt-in in a minor and become opt-out in the next major? Is that how you do things?

@feliperaul
Copy link

@mperham This has bitten me numerous times, because It's not rare for us to use SomeWorker.new.perform(xxx) (because it's a simple PORO) and find out the bug only after deploying (because then SomeWorker.perform_async(xxx) will get serialized and then the ruby hash with symbol keys will stop working).

Im inclined to do something like:

def perform(args)
args = args.with_indifferent_access

so args[:some_symbol_key] will work regardless of the way it was called.

@mperham
Copy link
Collaborator

mperham commented Aug 20, 2019 via email

@feliperaul
Copy link

feliperaul commented Aug 20, 2019 via email

@mperham
Copy link
Collaborator

mperham commented Aug 26, 2019

I'm rethinking this. I just implemented it but HashWithIndifferentAccess only ensures keys work. It won't handle values that are symbols:

perform_async({ :some => :key })
def perform(hash)
  p hash[:some]
end

=> "key"

I'm not sure it's a good idea to supply something that only half works. What's the point if there are still gotchas?

@feliperaul
Copy link

Mike, I see your point.

But even tough I agree that there are still gotchas, you are reducing it's surface area considerably. Using symbols as hash keys is the default way of using hash literals in Ruby ({key: 'value'}), and as a matter of user experience, I think it would greatly reduce the number of bites due to the serialization.

The wiki will still inform users about the need to not use symbols in args, but using string keys ({'some key' => 'value'}) is way less idiomatic in my opinion.

I know it's anecdotal, but this would have helped me a few times.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants