Turning a hash into a string of name-value pairs - ruby

If I'm turning a ruby hash into a string of name-value pairs (to be used in HTTP params, for example), is this the best way?
# Define the hash
fields = {"a" => "foo", "b" => "bar"}
# Turn it into the name-value string
http_params = fields.map{|k,v| "#{k}=#{v}"}.join('&')
I guess my question is:
Is there an easier way to get to http_params? Granted, the above way works and is fairly straightforward, but I'm curious if there's a way to get from the hash to the string without first creating an array (the result of the map method)?

Rails provides to_query method on Hash class. Try:
fields.to_query

From the The Pragmatic Programmer's Guide:
Multiple parameters passed to a yield
are converted to an array if the block
has just one argument.
For example:
> fields = {:a => "foo", :b => "bar"}
> fields.map { |a| a } # => [[:a, "foo"], [:b, "bar"]]
So your code could be simplified like this:
> fields.map{ |a| a.join('=') }.join('&') # => "a=foo&b=bar"

This is probably the best you can do. You could iterate through the pairs in the hash, building the string as you go. But in this case the intermediate string would need to be created and deleted at each step.
Do you have a use-case where this is a performance bottleneck? In general Ruby is doing so much work behind the scenes that worrying about a temporary array like this is probably not worth it. If you are concerned that it may be a problem, consider profiling your code for speed and memory usage, often the results are not what you expect.

In Rails it also had a method to convert hash to params.
Example1: Turn a hash to params
{ param1: "value1", param2: "value2" }.to_query
Result:
"param1=value1&param2=value2"
Example2: Turn params to hahs
params = "param1=value1&param2=value2"
Rack::Utils.parse_nested_query(params)
Result:
{"param1"=>"value1", "param2"=>"value2"}

Related

Convert string into hash in ruby

I have a string like this "{ssl:true,sslAllowInvalidCertificates:true}"
(please note that the string can contain any no. of key/value pairs)
I want to convert this into hash, in Ruby, like this:
{ssl:true,sslAllowInvalidCertificates:true}
(Please note that the output is to be exactly similar to the above. It should not be in 'generic' hash notation like
{"ssl" => "true","sslAllowInvalidCertificates" => "true"}
The MongoDB client library can recognize the option only if it is exactly same as per requirement, else throws error.
How to do this in ruby?
TIA!
TL;DR
To convert your String into a Hash, you either have to parse it yourself, or call Kernel#eval on it. Either way, your real issue seems to be round-tripping this back to a string in your expected format. One way to do that is to re-open the Hash class and add a custom output method, rather than relying on the Hash#to_s method aliased to Hash#inspect.
String to Hash
If you trust the data source and have some mechanism to sanitize or check the String for potential arbitrary code execution, #eval is certainly the easiest thing you can do. I'd personally add a tiny bit of safety by making sure the String isn't tainted first, though. For example:
str = "{ssl:true,sslAllowInvalidCertificates:true}"
raise "string tainted: #{str}" if str.tainted?
hsh = eval str
#=> {:ssl=>true, :sslAllowInvalidCertificates=>true}
However, if you don't trust the source or structure of your String, you can parse and validate the information yourself using some variant of the following as a starting point:
hsh = Hash[str.scan(/\w+/).each_slice(2).to_a]
#=> {:ssl=>true, :sslAllowInvalidCertificates=>true}
Hash to Custom String
If you then want to dump it back out to your custom format as a String, you can monkeypatch the Hash class or add a singleton method to a given Hash instance to provide a #to_mongo method. For example:
class Hash
def to_mongo
str = self.map { |k, v| '%s:%s' % [k, v] }.join ?,
'{%s}' % str
end
end
Calling this method on your Hash instance will yield the results you seem to want:
hsh.to_mongo
#=> "{ssl:true,sslAllowInvalidCertificates:true}"
It seems there is some confusion surrounding the fat arrow syntax for hashes in ruby. You should be able to run eval on the string to generate the following hash:
{:ssl=>true, :sslAllowInvalidCertificates=>true}
You mention that the output cannot be in "generic" hash notation, which I assume is referring to the fat arrow notation used in your example.
Since Ruby 1.9, a new syntax can be used to create a hash
{foo: "bar"}
rather than the previous
{:foo => "bar"}
Interactive ruby consoles, such as irb and pry, try to print human friendly strings for the hash. Creating a hash with either of the two previous syntaxes will produce the same result in the console:
{:foo=>"bar"}
However, in memory, both of the objects are equivalent.
(There is the caveat that your "generic" hash example uses strings as keys. If that's what you're referring to, you can call #symbolize_keys on the hash)

Coercing a scalar or an array to become an array

Sometimes I want a variable to always be an array, whether its a scalar or already an array.
So I normally do:
[variable].flatten
which is compatible with ruby-1.8.5, 1.8.7, 1.9.x.
With this method when variable is a string (variable = "asdf"), it gives me ["asdf"]. If it's already an array (variable = ["asdf","bvcx"]), it gives me: ["asdf","bvcx"].
Does anyone have a better way? "Better" meaning more readable, more performant, succinct or more effective in other ways.
Array(variable)
should do the trick. It uses the little known Kernel#Array method.
The way I do, and think is the standard way, is using [*...]:
variable1 = "string"
variable2 = ["element1", "element2"]
[*variable1] #=> ["string"]
[*variable2] #=> ["element1", "element2"]
You might need something like Array.eat. Most other methods either call #to_a or #to_ary on the object. If you where using [ obj ].flatten that might give surprising results. #flatten will also mangle nested arrays unless called with a level parameter and will make an extra copy of the array.
Active support provides Array.wrap, but that also calls #to_ary, which might or might not be to your liking, depending on your needs.
require 'active_support/core_ext/array/wrap'
class Array
# Coerce an object to be an array. Any object that is not an array will become
# a single element array with object at index 0.
#
# coercing nil returns an empty array.
#
def self.eat( object )
object.nil? and return []
object.kind_of?( Array ) and return object
[object]
end
end # class Array
a = { a: 3 }
p [a].flatten # => [{:a=>3}]
p [*a] # => [[:a, 3]] -> OOPS
p Array a # => [[:a, 3]] -> OOPS
p Array.wrap a # => [{:a=>3}]
p Array.eat a # => [{:a=>3}]

Most efficient way to map a specific string into a hash in Ruby

I'm new to Ruby and am working on a CLI application that parses some reports of mine. I would like to figure out the most efficient way to achieve the following with this line:
MAXCONN: 2000, MAXSSL_CONN: 500, PLAINCONN: 34, AVAILCONN: 1966, IDLECONN: 28, SSLCONN: 0, AVAILSSL: 500
I would like to map this into a hash accordingly:
{ :maxconn => 2000, :maxssl_conn => 500, :plainconn => 34, :availconn => 1966, :idleconn => 28, :sslconn => 0, :availssl => 500 }
The only way I can think to do this is to split at the comma and then again at the semi-colon and map them.
I have a sneaking suspicion there may be some Ruby magic to achieve this in a more efficient and less cumbersome way.
Any input and or tricks / tips would be appreciated as I have a feeling I'll be approaching problems like this relatively often.
We combine the technique for converting a bunch of key-value pairs into a hash
Hash[[[k1, v1], [k2, v2], ...]] #=> { k1 => v1, k2 => v2, ... }
with the regular expression method String#scan, which passes through a string and collects matches in an array, to get
Hash[reports.scan(/(\w+): (\w+)/).map { |(first, second)| [first.downcase.to_sym, second.to_i] }]
This also uses a block with Enumerable#map that interprets arrays as pairs of (first, second) elements in the argument list, extracts these into new elements, and applies conversions to them so as to tailor the resulting hash to your example's specifications (otherwise, you just get a hash of strings mapping to strings).
hash = {}
input.scan /(\w)+\:(\d+)\,/ do |key, val|
hash[key.downcase.to_sym] = val.to_i
end
Seems like the most obvious.
You could do (with some caveats) this:
hash = Hash[*eval(string).map{|k,v| [k.downcase.to_sym, v]}.flatten]
But that's really forcing a one-liner where it shouldn't be.
Of course splitting it isn't a horrible idea:
hash = input.split(",").each_with_object({}) do |str, h|
k,v = str.split(":")
h[k.downcase.to_sym] = v.to_i
end
If you are using ruby1.9, you do not even need to parse that text because ruby1.9 accepts hash literals in the form {symbol: value} when the key is a symbol. Simply, do this:
eval("{#{your_string.downcase}}")
Write the code first, then optimize it
If your code causes a bottleneck on your implementation of Ruby, the first thing you'd have to do is work out if it's the string splitting, or the hash creation, or adding your new hash to whatever array it's in (this is the step I'd be most worried about!), that's causing the bottleneck.
Write functional programming style code - it's usually cleaner, and sometimes faster
With regards to adding your new hash to the array it's in, generally you shouldn't do
array_of_hashes = []
lines.each do |line|
hash = create_hash(line)
array_of_hashes << hash
end
instead, do
array_of_hashes = lines.map do |line|
hash = create_hash(line)
hash
end
The latter is generally better performing, but regardless of that, it's cleaner code, so it's not a premature optimization.

How to convert a ruby integer into a symbol

I have a Ruby array like this
q_id = [1,2,3,4,5,...,100]
I want to iterate through the array and convert into a hash like this
{
:1 => { #some hash} ,
:2 => { #another hash},
...
:100 => {#yet another hash}
}
What is the shortest and most elegant way to accomplish this?
[EDIT : the to_s.to_sym while being handy is not how I want it. Apologies for not mentioning it earlier.]
For creating a symbol, either of these work:
42.to_s.to_sym
:"#{42}"
The #inspect representation of these shows :"42" only because :42 is not a valid Symbol literal. Rest assured that the double-quotes are not part of the symbol itself.
To create a hash, there is no reason to convert the keys to symbols, however. You should simply do this:
q_id = (1..100).to_a
my_hash_indexed_by_value = {}
q_id.each{ |val| my_hash_indexed_by_value[val] = {} }
Or this:
my_hash = Hash[ *q_id.map{ |v| [v,{}] }.flatten ]
Or this:
# Every time a previously-absent key is indexed, assign and return a new hash
my_hash = Hash.new{ |h,val| h[val] = {} }
With all of these you can then index your hash directly with an integer and get a unique hash back, e.g.
my_hash[42][:foo] = "bar"
Unlike JavaScript, where every key to an object must be a string, Hashes in Ruby accept any object as the key.
To translate an integer into a symbol, use to_s.to_sym .. e.g.,:
1.to_s.to_sym
Note that a symbol is more related to a string than an integer. It may not be as useful for things like sorting anymore.
Actually "symbol numbers" aren't a thing in Ruby (try to call the to_sym method on a number). The benefit of using symbols in a hash is about performance, since they always have the same object_id (try to call object_id on strings, booleans, numbers, and symbols).
Numbers are immediate value and, like Symbol objects, they always have the same object_id.
Anyway, using the new hash syntax implies using symbols as keys, but you can always use the old good "hash rocket" syntax
awesome_hash = { 1 => "hello", 2 => "my friend" }
Read about immediate values here:
https://books.google.de/books?id=jcUbTcr5XWwC&pg=PA73&lpg=PA73&dq=immediate+values+singleton+method&source=bl&ots=fIFlAe8xjy&sig=j7WgTA1Cft0WrHwq40YdTA50wk0&hl=en&sa=X&ei=0kHSUKCVB-bW0gHRxoHQAg&redir_esc=y#v=onepage&q&f=false
If you are creating a hard-coded constant numeric symbol, there's a simpler way:
:'99'
This produces the same results as the more complex methods in other answers:
irb(main):001:0> :'99'
=> :"99"
irb(main):002:0> :"#{99}"
=> :"99"
irb(main):003:0> 99.to_s.to_sym
=> :"99"
Of course, this will not work if you're dynamically creating a symbol from a variable, in which case one of the other two approaches is required.
As already stated, :1 is not a valid symbol. Here's one way to do what you're wanting, but with the keys as strings:
Hash[a.collect{|n| [n.to_s, {}] }]
An array of the objects you want in your hash would be so much easier to use, wouldn't it? Even a hash of integers would work pretty well, wouldn't it?
u can use
1.to_s.to_sym
but this will make symbols like :"1"
You can make symbolic keys with Hash[]:
a = Hash[(1..100).map{ |x| ["#{x}".to_sym, {}] }]
Check type of hash keys:
puts a.keys.map(&:class)
=>
Symbol
...
Symbol
Symbol

Ruby: How to loop through an object that may or may not be an array?

I have an each method that is run on some user-submitted data.
Sometimes it will be an array, other times it won't be.
Example submission:
<numbers>
<number>12345</number>
</numbers>
Another example:
<numbers>
<number>12345</number>
<number>09876</number>
</numbers>
I have been trying to do an each do on that, but when there is only one number I get a TypeError (Symbol as array index) error.
I recently asked a question that was tangentally similar. You can easily force any Ruby object into an array using Array.
p Array([1,2,3]) #-> [1,2,3]
p Array(123) #-> [123]
Of course, arrays respond to each. So if you force everying into an array, your problem should be solved.
A simple workaround is to just check if your object responds to :each; and if not, wrap it in an array.
irb(main):002:0> def foo x
irb(main):003:1> if x.respond_to? :each then x else [x] end
irb(main):005:1> end
=> nil
irb(main):007:0> (foo [1,2,3]).each { |x| puts x }
1
2
3
=> [1, 2, 3]
irb(main):008:0> (foo 5).each { |x| puts x }
5
=> [5]
It looks like the problem you want to solve is not the problem you are having.
TypeError (Symbol as array index)
That error tells me that you have an array, but are treating it like a hash and passing in a symbol key when it expects an integer index.
Also, most XML parsers provide child nodes as array, even if there is only one. So this shouldn't be necesary.
In the case of arguments to a method, you can test the object type. This allows you to pass in a single object or an array, and converts to an array only if its not one so you can treat it identically form that point on.
def foo(obj)
obj = [obj] unless obj.is_a?(Array)
do_something_with(obj)
end
Or something a bit cleaner but more cryptic
def foo(obj)
obj = [*obj]
do_something_with(obj)
end
This takes advantage of the splat operator to splat out an array if it is one. So it splats it out (or doesn't change it) and you can then wrap it an array and your good to go.
I was in the same position recently except the object I was working with was either a hash or an array of hashes. If you are using Rails, you can use Array.wrap because Array(hash) converts hashes to an array.
Array({foo: "bar"}) #=> [[:foo, "bar"]]
Array.wrap({foo: "bar"}) #=> [{:foo=>"bar"}]
Array.wrap(123) #=> [123]
Array.wrap([123]) #=> [123]
I sometimes use this cheap little trick:
[might_be_an_array].flatten.each { |x| .... }
Use the splat operator:
[*1] # => [1]
[*[1,2]] # => [1,2]
Like Mark said, you're looking for "respond_to?" Another option would be to use the conditional operator like this:
foo.respond_to? :each ? foo.each{|x| dostuff(x)} : dostuff(foo);
What are you trying to do with each number?
You should try to avoid using respond_to? message as it is not a very object oriented aproach.
Check if is it possible to find in the xml generator code where it is assigning an integer value when there is just one <"number"> tag and modify it to return an array.
Maybe it is a complex task, but I would try to do this in order to get a better OO design.
I don't know much anything about ruby, but I'd assume you could cast (explicitly) the input to an array - especially given that if the input is simply one element longer it's automatically converted to an array.
Have you tried casting it?
If your input is x, use x.to_a to convert your input into an array.
[1,2,3].to_a
=> [1, 2, 3]
1.to_a
=> [1]
"sample string".to_a
=> ["sample string"]
Edit: Newer versions of Ruby seem to not define a default .to_a for some standard objects anymore. You can always use the "explicit cast" syntax Array(x) to achieve the same effect.

Resources