Search code examples
ruby-on-railsrubyhashresquestring-to-symbol

Compare two hashes no matter symbols or strings, rails


I would like to compare two hashes and forces them to be equal:

  • one with Symbols on keys and values
  • the second with only strings.

e.g:

sym_hash = {:id=>58, :locale=>:"en-US"}
string_hash = {"id"=>58, "locale"=>"en-US"}

Try like this does not work:

> sym_hash == string_hash
=> false

I first tried to symbolized the string_hash:

> string_hash.deep_symbolize_keys
=> {:id=>58, :locale=>"en-US"}

But it is still false because sym_hash still has : in front of locale var.

Then I tried to stringified the sym_hash:

> sym_hash.with_indifferent_access
=> {"id"=>58, "locale"=>:"en-US"}

But when I test for equality it is still false for the same reasons.

EDIT

To answer many comments abouy why I wanted those hashes to be equal here, I'll explain what I'm trying to do.

I'm using Reque to manage my jobs. Now I wanted to do a class to avoid having the same* job running, or being enqueued twice in the same time.

(same: for me the same job is a job having the same parameters, I would like to be able to enqueu twice the same jobs having differents ids for instance.)

For that I'm a using the plugin resque-status, so far I'm able to know when a job is running or not. Beside, when I save the params using set I notice that the message written to Redis(because resque-status is using Redis to keep track of the job's status) is not properly saved with symbols.

Here is my class:

# This class is used to run thread-lock jobs with Resque.
#
# It will check if the job with the exact same params is already running or in the queue.
# If the job is not finished, it will returns false,
# otherwise, it will run and returns a the uuid of the job.
#
class JobLock
  def self.run(obj, params = {})
    # Get rid of completed jobs.
    Resque::Plugins::Status::Hash.clear_completed
    # Check if your job is currently running or is in the queue.
    if !detect_processing_job(obj, params)
      job_uuid = obj.create(params)
      Resque::Plugins::Status::Hash.set(job_uuid,
                                        job_name: obj.to_s,
                                        params: params)
      job_uuid
    else
      false
    end
  end

  def self.detect_processing_job(obj, params = {})
    Resque::Plugins::Status::Hash.statuses.detect do |job|
      job['job_name'] == obj.to_s && compare_hashes(job['params'], params)
    end
  end

  def self.compare_hashes(string_hash, sym_hash)
    [sym_hash, string_hash].map do |h|
      h.map { |kv| kv.map(&:to_s) }.sort
    end.reduce :==
  end
end 

And here how I can use it:

JobLock.run(MyAwesomeJob, id: 58, locale: :"en-US")

As you can see I used @mudasobwa's answer but I hope there is a easier way to achieve what I am trying to do!


Solution

  • How about this?

    require 'set'
    
    def sorta_equal?(sym_hash, str_hash)
      return false unless sym_hash.size == str_hash.size
      sym_hash.to_a.to_set == str_hash.map { |pair|
        pair.map { |o| o.is_a?(String) ? o.to_sym : o } }.to_set
    end
    
    sym_hash= {:id=>58, :locale=>:"en-US"}
    
    sorta_equal?(sym_hash, {"id"=>58, "locale"=>"en-US"})           #=> true 
    sorta_equal?(sym_hash, {"locale"=>"en-US", "id"=>58 })          #=> true 
    sorta_equal?(sym_hash, {"id"=>58, "local"=>"en-US", "a"=>"b" }) #=> false 
    sorta_equal?(sym_hash, {"id"=>58, "lacole"=>"en-US"})           #=> false 
    sorta_equal?(sym_hash, {"id"=>58, [1,2,3]=>"en-US"})            #=> false 
    sorta_equal?({}, {})                                            #=> true 
    
    class A; end
    a = A.new
    sorta_equal?({:id=>a, :local=>:b}, {"id"=>a, "local"=>"b"})     #=> true