How to have the same behavior than 'params' function? - ruby-on-rails

Controller functions receivent parameters like
{"v1" => { "v2" => "1", "v3" => "" }, "v4" => "true"}
The params function allows to use
x = params[:v1], equivalent to x = params["v1"]
if params[:v4], equivalent to ["true", "1"].include?(params["v4"])
if (params[:v1][:v2] == 1), equivalent to params["v1"]["v2"] == "1"
Is there any method to have the behavior than params function, but with other datas ?
I want to be able to write something like that...
my_params = {"v1" => { "v2" => "1", "v3" => "" }, "v4" => "true"}
x = my_params[:v1]
if my_params[:v4]
if (my_params[:v1][:v2] == 1)
Or with a function some_function
x = some_function(my_params)[:v1]
if some_function(my_params)[:v4]
if some_function(my_params)[:v1][:v2] == 1)
I'm in Rails 2.

You want a hash with indifferent access:
h = { a: { b: 1, 'c' => 2 } }
=> {:a=>{:b=>1, "c"=>2}}
h[:a][:c]
=> nil
h2 = h.with_indifferent_access
=> {"a"=>{"b"=>1, "c"=>2}}
h2['a'][:c]
=> 2
h2[:a][:c]
=> 2

Related

How to search nested hash of arrays and arrays of hash and only return matching object from the parent node?

Say I have the below ruby hash nested
hash_or_array = [{
"book1" => "buyer1",
"book2" => {
"book21" => "buyer21", "book22" => ["buyer23", "buyer24", true]
},
"book3" => {
"0" => "buyer31", "1" => "buyer32", "2" => "buyer33",
"3" => [{
"4" => "buyer34",
"5" => [10, 11],
"6" => [{
"7" => "buyer35"
}]
}]
},
"book4" => ["buyer41", "buyer42", "buyer43"],
"book5" => {
"book5,1" => "buyer5"
}
}]
And I want to search for a string that matches buyer35. On match, I want it to return the following result
"book3" => {
"3" => [{
"6" => [{
"7" => "buyer35"
}]
}]
}]
All, other non matching keys,values, arrays should be omitted. I have the following example, but it doesn't quite work
def search(hash)
hash.each_with_object({}) do |(key, value), obj|
if value.is_a?(Hash)
returned_hash = search(value)
obj[key] = returned_hash unless returned_hash.empty?
elsif value.is_a?(Array)
obj[key] = value if value.any? { |v| matches(v) }
elsif matches(key) || matches(value)
obj[key] = value
end
end
end
def matches(str)
match_criteria = /#{Regexp.escape("buyer35")}/i
(str.is_a?(String) || str == true || str == false) && str.to_s.match?(match_criteria)
end
....
=> search(hash_or_array)
Any help is appreciated. I realize, I need to use recursion, but can't quite figure how to build/keep track of the matched node from the parent node.
You can use the following recursive method.
def recurse(obj, target)
case obj
when Array
obj.each do |e|
case e
when Array, Hash
rv = recurse(e, target)
return [rv] unless rv.nil?
when target
return e
end
end
when Hash
obj.each do |k,v|
case v
when Array, Hash
rv = recurse(v, target)
return {k=>rv} unless rv.nil?
when target
return {k=>v}
end
end
end
nil
end
recurse(hash_or_array, "buyer35")
#=> [{"book3"=>{"3"=>[{"6"=>[{"7"=>"buyer35"}]}]}}]
recurse(hash_or_array, "buyer24")
#=>[{"book2"=>{"book22"=>"buyer24"}}]
recurse(hash_or_array, "buyer33")
#=> [{"book3"=>{"2"=>"buyer33"}}]
recurse(hash_or_array, 11)
#=> [{"book3"=>{"3"=>[{"5"=>11}]}}]
recurse(hash_or_array, "buyer5")
#=>[{"book5"=>{"book5,1"=>"buyer5"}}]
If desired, one may write, for example,
recurse(hash_or_array, "buyer35").first
#=> {"book3"=>{"3"=>[{"6"=>[{"7"=>"buyer35"}]}]}}

get rid of "duplicates" in nested hash

I have a nested hash:
given = {
"AA" => {
:GE => nil,
"GE" => "successful",
:GR => nil,
:ZG => nil,
"ZG" => "successful",
},
"BB" => {
:MM => nil,
"MM" => "successful",
:GR => nil,
:ZZ => nil,
"ZZ" => "successful",
}
}
and my goal is to transform it into a new hash without the duplicates eg. :GE/"GE" and :ZG/"ZG" and so on.
goal = {
"AA" => {
:GE => "successful",
:GR => nil,
:ZG => "successful",
},
"BB" => {
:MM => "successful",
:GR => nil,
:ZZ => "successful",
}
}
My attempt is with Rails method index_by
given.map do |key, value|
value.index_by {|r| value[r]}
end
Or with
given.each { |key,value| temp_hash = {} (value.each { |va| va[0].each { |k,v| temp_hash|key| << val }}) given_hash[k.to_sym] = temp_hash if given.has_key?(k.to_sym)}
But I'm kind of stuck. Any help is greatly appreciated!
I'm assuming that in the case of { :GE => nil, "GE" => "successful" }, you want to use the first truthy value ("successful") and make sure the key is a symbol:
result = given.transform_values do |inner_hsh|
inner_hsh.group_by do |k,v|
k.to_sym
end.transform_values do |key_vals|
key_vals.to_h.values.find(&:itself)
end
end
Assuming that #max's assumption is correct, and also that the order of the keys in inner hashes is not important, here is another way to produce the desired return value.
given.each_with_object({}) do |(k,v),h|
h[k] = v.sort_by { |_,w| w.nil? ? 1 : 0 }.uniq { |m,_| m.to_s }.to_h
end
#=> {"AA"=>{"GE"=>"successful", "ZG"=>"successful", :GR=>nil},
# "BB"=>{"MM"=>"successful", "ZZ"=>"successful", :GR=>nil}}
See Array#uniq, particularly the sentence, "self is traversed in order, and the first occurrence is kept.".
For
k = "AA"
v = { :GE=>nil, "GE"=>"successful", :GR=>nil, :ZG=>nil, "ZG"=>"successful" }
we compute
a = v.sort_by { |_,w| w.nil? ? 1 : 0 }
#=> [["GE", "successful"], ["ZG", "successful"],
# [:GE, nil], [:GR, nil], [:ZG, nil]]
b = a.uniq { |m,_| m.to_s }
#=> [["GE", "successful"], ["ZG", "successful"], [:GR, nil]]
because "GE" precedes :GE and "ZG" precedes :ZG. Lastly,
h[k] = b.to_h
#=> {"GE"=>"successful", "ZG"=>"successful", :GR=>nil}

How to recursively check for presence of specific key

Given a Ruby hash of parameters that are infinitely nested, I want to write a function that returns true if a given key is in those parameters.
This is the function I have so far, but it's not quite right, and I'm at a loss as to why:
def has_key(hash, key)
hash.each do |k, v|
if k == key
return true
elsif v.class.to_s == "Array"
v.each do |inner_hash|
return has_key(inner_hash,key)
end
else
return false
end
end
end
The method should return the following results:
# all check for presence of "refund" key
has_key({
"refund" => "2"
}, "refund")
=> true
has_key({
"whatever" => "3"
}, "refund")
=> false
has_key({
"whatever" => "3",
"child_attributes" => [{
"refund" => "1"
}]
}, "refund")
=> true
has_key({
"whatever" => "3",
"child_attributes" => [{
"nope" => "4"
}]
}, "refund")
=> false
has_key({
"whatever" => "3",
"child_attributes" => [{
"a" => "1",
"refund" => "2"
}]
}, "refund")
=> true
has_key({
"whatever" => "3",
"child_attributes" => [
{"a" => "1", "b" => "2"},
{"aa" => "1", "refund" => "2"}
]
}, "refund")
=> true
has_key({
"whatever" => "3",
"child_attributes" => [
{"a" => "1", "b" => "2"},
{"grand_child_attributes" => [
{"test" => "3"}
]}
]
}, "refund")
=> false
has_key({
"whatever" => "3",
"child_attributes" => [
{"a" => "1", "b" => "2"},
{"grand_child_attributes" => [
{"test" => "3"}, {"refund" => "5"}
]}
]
}, "refund")
=> true
has_key({
"whatever" => "3",
"child_attributes" => [
{"a" => "1", "b" => "2"},
{"grand_child_attributes" => [
{"test" => "3", "refund" => "5"}
]}
]
}, "refund")
=> true
The problem with your code seems to be in here:
elsif v.class.to_s == "Array"
v.each do |inner_hash|
return has_key(inner_hash,key)
end
else
This would always return has_key(inner_array[0]) without checking subsequent values. The fix is to return only if it's true, else continue checking, like this:
elsif v.class.to_s == "Array"
v.each do |inner_hash|
if(has_key(inner_hash,key))
return true
end
end
else
return false
The following will work.
def has_key(hash, key)
hash.each do |k, v|
return true if k == key
if v.is_a? Array
v.each do |h|
rv = has_key(h, key)
return rv if rv
end
end
end
false
end
This passes all your tests. One more:
h = { "a" => 1,
"b" => [{ "c" => 2, "d" => 3 },
{"e"=> [{ "f" => "4" },
{ "g" => [{ "h" => 5 },
{ "i" => 6, "refund" => 7 }
]
}
]
}
]
}
has_key h, "refund"
#=> true
h["b"][1]["e"][1]["g"] = [{ "h"=>5 }]
h
#=> {"a"=>1, "b"=>[{"c"=>2, "d"=>3}, {"e"=>[{"f"=>"4"}, {"g"=>[{"h"=>5}]}]}]}
has_key h, "refund"
#=> false
Inspired by #Wand's answer, for
h = {"a"=>"3", "b"=>[{"c"=>"1", "d"=>"2"}, {"e"=>[{"test"=>"3", "refund"=>"5"}]}]}
you don't have to load JSON:
str = h.to_s
#=> "{\"a\"=>\"3\", \"b\"=>[{\"c\"=>\"1\", \"d\"=>\"2\"}, {\"e\"=>[{\"test\"=>\"3\", \"refund\"=>\"5\"}]}]}"
str =~ /\"refund\"=>/
#=> 60 (truthy)
I confess to being a little uncomfortable with any approach that converts the hash to a string, and then parsing the string, for fear that string formats may change in future.
I'd do something like:
class Hash
def key_exists?(key)
self.keys.include?(key) ||
self.values.any?{ |v|
Hash === v &&
v.key_exists?(key)
}
end
end
{'a' => 1}.key_exists?('a') # => true
{'b' => 1}.key_exists?('a') # => false
{'b' => {}}.key_exists?('a') # => false
{'b' => {'a' => {}}}.key_exists?('a') # => true
{'b' => {'a' => 1}}.key_exists?('a') # => true
{'b' => {'b' => {}}}.key_exists?('a') # => false
{'b' => {'b' => {'a' => 1}}}.key_exists?('a') # => true
Insert all the usual warnings about extending core classes and recommendations to use the alternative ways of doing it here.
Note: Similarly, "iterate over every key in nested hash" could be used to easily determine a true/false value and it demonstrates the safe way to extend a core class.
You could convert the hash to JSON and then check whether the JSON has "refund": present in it, as any key will be serialised to JSON in the form "key":.
require "json"
hash.to_json.include?('"refund":')
Give this a shot, I tried it, seems to work fine for me
def has_key(hash, key)
if hash.keys.include?(key)
true
else
# get the values of the current hash, flatten out to not include arrays
new_hash = hash.values.flatten
# then filter out any element that's not a hash
new_hash = new_hash.select {|b| b.is_a?(Hash)}
# merge all the hashes into single hash
new_hash = new_hash.inject {|first, second| first.merge(second)}
if new_hash
has_key(new_hash, key)
else
false
end
end
end

Ruby/Rails, appending hash to array which belongs to a hash

I have this situation:
skeleton =
{
"timeline" =>
{
"data" => []
}
}
template =
{
"A" => "",
"B" => "",
"C" => "",
}
From the controller I make a query which returns me an array of hashes:
#cdr = Cdr.select("start, end, clid")
then I iterate over the array and set the "template" hash fields and in the last step I append this hash to an array which belongs to "skeleton" hash:
#cdr.each do |cdr|
template["A"] = cdr.start
template["B"] = cdr.end
template["C"] = cdr.clid
skeleton["timeline"]["data"] << template
end
so to expected result is:
skeleton =
{
"timeline" =>
{
"data" => [
{
"A" => "sample1",
"B" => "sample2",
"C" => "sample3",
},
{
"A" => "sample4",
"B" => "sample5",
"C" => "sample6",
}
]
}
}
but the real result I'm getting is:
skeleton =
{
"timeline" =>
{
"data" => [
{
"A" => "sample1",
"B" => "sample2",
"C" => "sample3",
},
{
"A" => "sample1",
"B" => "sample2",
"C" => "sample3",
}
]
}
}
all entries in the array contain same data. Why?
Try creating a new template array through each cycle through. I don't think you can change the value of the key while it is being used as a key.
#cdr.each do |cdr|
temp_inst = template.clone
temp_inst["A"] = cdr.start
temp_inst["B"] = cdr.end
temp_inst["C"] = cdr.clid
skeleton["timeline"]["data"] << temp_inst
end

Hash - nested traversal - Ruby - (Any one know this)

I have this hash
hasha = {"a" => "b","a_a" => {"x_y" => "sreeraj","a_b" => "hereIam"}}
I need to change this to
hasha = {"a" => "b","a-a" => {"x-y" => "sreeraj","a-b" => "hereIam"}}
i.e. I need to change all keys containing "_"(underscore) to "-"(minus). How can I do this?
This is might not be the smarter one, but it works:
def rep_key(hash={})
newhash={}
hash.each_pair do |key,val|
val = rep_key(val) if val.class == Hash
newhash[key.sub(/_/,'-')] = val
end
newhash
end
where:
hasha = {"a" => "b","a_a" => {"x_y" => "sreeraj","a_b" => "hereIam"}}
newhash = rep_key hasha
puts newhash.inspect
gives:
newhash = {"a" => "b","a-a" => {"x-y" => "sreeraj","a-b" => "hereIam"}}
Try recursion.
def replace_all(x, a, b)
return if x.class != Hash
y = Hash.new
x.each do |k,v|
if(v.class == Hash)
v = replace_all(v, a, b)
end
if k.class == String and k.include?(a)
y[k.gsub(a,b)] = v
else
y[k] = v
end
end
return y
end
hasha = {"a" => "b","a_a" => {"x_y" => "sreeraj","a_b" => "hereIam"}}
puts replace_all(hasha, "_", "-")

Resources