Ruby - Assign key value hash pairs to existing variables - ruby

How to assign key value pairs to exiting hash? I have the following code and I want to append some key value pairs to result variable.
def extra_variables
result = ansible_vars_from_objects(#handle.object, {})
result = ansible_vars_from_options(result)
#handle.log(:info, "Extra vars is: #{result}")
ansible_vars_from_ws_values(result)
end
Here is the log output of the result variable:
[----] I, [2022-03-08T21:31:41.701307 #322:2acf0cb72fb8] INFO -- automation: Q-task_id([r345_miq_provision_1235]) <AEMethod launch_ansible_job> Extra vars is: {"ansible_ssh_user"=>"ubuntu"}

Use the Hash#merge! Method
There's a built-in method for merging a Hash object into another Hash in-place: Hash#merge!. The main caveat is that Hash objects must have unique keys, so keep this in mind if you're trying to merge objects with the same top-level keys because the last key in the insertion order wins.
For example:
hash = {a: 1, b:2}
other_hash = {c: 3}
hash.merge! other_hash
hash
#=> {:a=>1, :b=>2, :c=>3}
Watch Out for Parsing Issues When Merging Hash Literals
Also note that if you're trying to merge Hash literals, you'll need to enclose the Hash in parentheses so that the interpreter doesn't think you're trying to pass a block. For example, you'd need to use:
hash.merge!({d: 4})
to avoid Ruby thinkings {d: 4} was a block passed to #merge!, but so far as I know this isn't a problem in any currently-supported Ruby when using a variable as the argument to #merge!. However, it's something to keep in mind if you get an exception like:
syntax error, unexpected ':', expecting '}' (SyntaxError)
which is pretty uninformative, but as of Ruby 3.1.1 that's the exception raised by this particular parsing issue.

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)

Ruby - Downcase on string values in a hash that also contains nil values

Currently attempting to adjust all the string values of a hash to lowercase. This hash contains multiple value types (nil, string, integer) so running across the entire hash with downcase spits out an error due to the nil values in the hash.
NoMethodError: undefined method "downcase!" for nil:NilClass
I'm pretty brand new to Ruby and I wasn't sure how best to run through this hash and skip the nil or integer values. I've attempted to use .map to convert every value to string and then downcase the values, but I'm either not seeing any difference in the end result or I get an error.
testHash = testHash.map(&:to_s)
testHash.map(&:downcase!)
(I've also attempted testHash.each {|k,v| v.downcase!}
I'm sure most of this is me just not knowing how to write this out in Ruby correctly. Please let me know if you need additional info and thank you.
Check if the value is a valid string before downcasing
testHash.each { |k, v| v.downcase! if v.is_a?(String) }
h = {a: nil, b: "Someword", c: 1}
h.map {|k,v| v.downcase! if v.is_a? String}
puts h #=> {:a=>nil, :b=>"someword", :c=>1}
This will work. I am mapping through the hash and check for values that are strings, and then running the downcase! method.

Why is ** optional when "splatting" keyword arguments?

Given this method definition:
def foo(a = nil, b: nil)
p a: a, b: b
end
When I invoke the method with a single hash argument, the hash is always implicitly converted to keyword arguments, regardless of **:
hash = {b: 1}
foo(hash) #=> {:a=>nil, :b=>1}
foo(**hash) #=> {:a=>nil, :b=>1}
I can pass another (empty) hash as a workaround:
foo(hash, {}) #=> {:a=>{:b=>1}, :b=>nil}
But, this looks pretty cumbersome and awkward.
I would have expected Ruby to handle this more like arrays are handled, i.e.:
foo(hash) #=> {:a=>{:b=>1}, :b=>nil}
foo(**hash) #=> {:a=>nil, :b=>1}
And using literals:
foo({b: 1}) #=> {:a=>{:b=>1}, :b=>nil}
foo(b: 1) #=> {:a=>nil, :b=>1}
foo(**{b: 1}) #=> {:a=>nil, :b=>1}
The current implementation looks like a flaw and the way I was expecting it to work seems obvious.
Is this an overlooked edge case? I don't think so. There's probably a good reason that it wasn't implemented this way.
Can someone enlighten me, please?
As for the lack of ** part:
My guess is that, to make method invocation simple, Ruby always once interprets the key: value form without the braces as a hash with omitted braces, whether it is actually going to be interpreted as such hash or as keyword arguments.
Then, in order to interpret that as keyword arguments, ** is implicitly applied to it.
Therefore, if you had passed an explicit hash, it will not make difference to the process above, and there is room for it to be interpreted either as an actual hash or as keyword arguments.
What happens when you do pass ** explicitly like:
method(**{key: value})
is that the hash is decomposed:
method(key: value)
then is interpreted as a hash with omitted braces:
method({key: value})
then is interpreted either as a hash or as a keyword argument.
As for keyword arguments having priority over other arguments, see this post on Ruby core: https://bugs.ruby-lang.org/issues/11967.

Behavior of Array#reject and Array#map iterater

I have a hash of dates:
values = {a: Time.now, b: Time.now - 3.days}
# => {:a=>2015-11-24 22:35:56 +0900, :b=>2015-11-21 22:35:56 +0900}
To select the values whose date is within one day from now, I wrote:
values.reject{|i, v| v < Time.now - 1.day}
# => {:a=>2015-11-24 22:35:56 +0900}
When I use i[1] instead of v, that doesn't work,
values.reject{|i| i[1] < Time.now - 1.day}
# => NoMethodError: undefined method `<' for nil:NilClass
while with map, it works.
values.map{|i| i[1]}
# => [2015-11-24 22:35:56 +0900, 2015-11-21 22:35:56 +0900]
Why does Array#reject behave differently?
http://ruby-doc.org/core-2.2.3/Hash.html#method-i-reject
Hash#reject yields the key and the value to the block. i is just the key. Your keys are symbols; presumably array accessor (i[1]) on symbols returns nil.
Hash#map isn't a thing, I guess. You're probably getting Enumerable#map. Hash is an Enumerable, so you can still call map on hashes.
http://ruby-doc.org/core-2.2.3/Enumerable.html#method-i-map
Enumerable#map is yielding each key-value pair from the hash as a 2-element array:[:a, 2015-11-24 22:35:56 +0900]
BTW in many languages hashes are called associative arrays. Rubyists usually call them hashes.
reject is a method on Hash, which takes a block with two block variables: the key and the value. If you pass it a block with only one variable like so:
some_hash.reject{|x| ...}
then x will be assigned the key, and its corresponding value is not accessible. Doing x[1] calls the symbol method [] on x, which gives nil because :a and :b are only one character long.
On the other hand, Enumerable#map implicitly casts the hash to an array, and takes a block with one block variable. If you actually pass it a block with one variable like so:
some_hash.map{|x| ...}
then x will be an array that has a key-value pair. Doing x[1] calls the array method [], and returns the value of the hash.
If you instead pass a block with two variables like so:
some_hash.map{|x, y| ...}
then destruction will be implicitly applied to adjust the number of variables, i.e., the live about would be interpreted as:
some_hash.map{|(x, y)| ...}
and x, y would restrictively be the key and the value.
Probably you were confused because Array#reject takes a block with only one variable, and works differently with Hash#reject.

Behavior of altered array keys in hashes

Ruby allows for a mutable object to be used as a hash key, and I was curious how this worked when the object is updated. It seems like the referenced object is irretrievable from key requests if it's updated.
key = [1,2]
test = {key => 12}
test # => {[1, 2] => 12}
test[key] # => 12
test[[1,2]] # => 12
test[[1,2,3]] # => nil
key << 3
test # => {[1, 2, 3] => 12}
test[key] # => nil
test[[1,2]] # => nil
test[[1,2,3]] # => nil
Why does this work this way? Why can't I provide a key to the hash which will return the value associated with the list I original used as a key?
According to the documentation:
Two objects refer to the same hash key when their hash value is identical and the two objects are eql? to each other.
Mutating a key doesn't change the hash it's stored under. After you mutate the key, trying to index with [1,2] matches the hash but not eql?, while [1,2,3] matches the eql? but isn't found by hash.
See this article for a more elaborate explanation.
You can rehash test, however, to recalculate the hashes based on current key values:
test.rehash
test[[1,2,3]] # => 12
class D
end
p D.new.methods.include?(:hash) #=> true
# so the D instance has a hash method. What does it do?
p D.new.hash #=> -332308361 # just some number
(Almost) every object in Ruby has a hash method. The Hash calls this method when the object is used as a key, and uses the resulting number to store and retrieve the key. (There are smart procedures to handle duplicate numbers (hash collisions)). Retrieving goes like this:
a_hash[[1,2,3]]
# the a_hash calls the hash method to the [1,2,3] object
# and checks if it has stored a value for the resulting number.
This number is only created once: when the key is added to the hash instance.
Problems arise when you start messing with the key after including it in a hash: the hashmethod of the object will differ from the one stored in the hash.
Don't do that, or
consider not using mutable objects as keys, or
remember to do a timely:
a_hash.rehash
which will recalculate all hash numbers.
Note: For strings keys, a copy is used for calculating the hash number, so modifying the original key won't matter.
It would be inconvenient if the identity of an array matters as the hash key. If you have a hash with a key [1, 2], you want to be able to access that with a different array object [1, 2] that has the same content. You want access by the content, not the identity. That would mean that what particular object (with the particular object id) is stored as a key does not matter for a hash. All that matters is the content of the key at the time it was assigned to the hash.
Therefore, after doing key << 3, it makes sense that test[key] or test[[1, 2, 3]] does not return the stored value anymore because key at the time of assignment to test was [1, 2].
The tricky thing is that test[[1, 2]] also returns nil. That is the limitation of Ruby.
If you want the hash to reflect the change made in the key objects, there is a method Hash#rehash.
test.rehash
test[key] # => 12
test[[1,2]] # => nil
test[[1,2,3]] # => 12

Resources