2
\$\begingroup\$

I am writing an API wrapper and the endpoint takes dates in a very specific format.

The user of the API can pass in the parameters in whatever format they prefer, but regardless of what they pass in, I want to be able to clean up their input prior to submitting their query.

My question centers around the best way to update the options hash in place, and I have thought of a few possible ways to implement.

  1. A helper method inside the class so you can overwrite options = reformat_hash(options)
  2. A singleton on that specific variable

    def options.clean_up!
     # see internals below
    end
    
  3. Or open up Hash and do the cleaning from the class

    class Hash
     def clean_hash!
     self.each { |key, value|
     if value.is_a? Date
     self[key] = value.strftime('%Y-%m-%d %H:%M:%S')
     else
     self[key] = value.to_s
     end
     }
     end
    end
    

    so that I can just call it on whatever the variable may be named like:

    def api_request(options={})
     options.clean_hash!
     # the options variable is now clean and I can pass it to the api
     HHTParty.get(path, :query => options).parsed_response
    end
    

Is there a best practice for modifying or formatting hashes after they're passed into a method?

I feel like #3 is the neatest, but should I be worried about opening up Hash to do this?

asked Jul 19, 2012 at 3:30
\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

Here, while it may look nice, the method clean_hash is not general enough to be valid across all Hashes. So adding a method such as clean_hash to all Hashes would only serve to increase the coupling which is bad. A second problem is that you are mutating your method argument which is almost never advisable.

The solution is to define the clean method outside, perhaps as a part of your internal API object and call HHTParty.get(path, :query => clean(options)).parsed_response.

I would also define the clean method this way

def clean(opt)
 Hash[opt.collect{|k,v| [k,v.is_a? Date : v.strftime('%Y-%m-%d %H:%m:%S'):v.to_s ]}]
end
answered Jul 19, 2012 at 4:08
\$\endgroup\$
1
  • \$\begingroup\$ thanks blufox. I think I will include it in the get query like that, although I may use a different enumerable based on this gist.github.com/3158814. \$\endgroup\$ Commented Jul 22, 2012 at 8:33
2
\$\begingroup\$

Rather than extend the hash class with something that "isn't general enough to be valid across all classes" (blufox: well said), make a subclass of Hash named CleanHash.

I added || value.is_a?(Time) so you could format Time in addition to Date.

class CleanHash < Hash
 def self.[](opts)
 super(opts).clean!
 end
 def []=(key,value)
 super(key,clean(value))
 end
 def clean(value)
 if value.is_a?(Date) || value.is_a?(Time)
 value.strftime('%Y-%m-%d %H:%M:%S')
 else
 value.to_s
 end
 end
 def clean!
 self.each { |key, value|
 self[key] = value # don't clean(value) or it will clean twice
 }
 end
end

Examples below.

# example 1
h = CleanHash[:s=>"x",:n=>9,:d=>Date.today,:t=>Time.now]
# => {:s=>"x", :t=>"2012-07-20 09:21:18", :d=>"2012-07-20 00:00:00", :n=>"9"}
h[:d2] = Date.yesterday
# => {:d2=>"2012-07-19 00:00:00", :s=>"x", :t=>"2012-07-20 09:21:18", :d=>"2012-07-20 00:00:00", :n=>"9"}
# example 2
def api_request(options={})
 clean_options = CleanHash[options]
 # the options variable is now clean and I can pass it to the api
 HHTParty.get(path, :query => clean_options).parsed_response
end

It's still a Hash so you can pass it around as if it were a Hash.

h = CleanHash[:foo=>Date.today]
#=> {:foo=>"2012-07-20 00:00:00"}
h.class
#=> CleanHash
h.is_a?(Hash)
#=> true
answered Jul 20, 2012 at 13:36
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.