Why isn't this working for recursively reading from hash? - ruby

#hash is a global hash that looks something like:
#hash = {
"xmlns:xsi" =>"http://www.w3.org/2001/XMLSchema-instance",
"xsi:noNamespaceSchemaLocation"=>"merchandiser.xsd",
"header" =>[{"merchantId"=>["35701"],
"merchantName" =>["Lingerie.com"],
"createdOn" =>["2011-09-23/00:33:35"]}],
"trailer" =>[{"numberOfProducts"=>["0"]}]
}
And I would expect this to work if I call the method below like:
def amethod
hash_value("header", "merchantName") // returns "Lingerie.com"
end
def hash_value *attributes, hash = nil
hash = #hash unless hash
att = attributes.delete_at.first
attributes.empty? ? hash[att].first : hash_value(attributes, hash[att].first)
end

You can't have a default argument after a splat arg. Instead, require that the attribute list be passed as an array, like so:
def hash_value(attributes, hash = #hash)
return hash if attributes.empty?
hash_value(attributes[1..-1], hash[attributes.first].first)
end
p hash_value(["header", "merchantName"]) # => "Lingerie.com"
p hash_value(["trailer", "numberOfProducts"]) # => "0"

Try this:
def hash_value(*attributes, hash)
hash = #hash unless hash
att = attributes.delete_at.first
attributes.empty? ? hash[att].first : hash_value(attributes, hash[att].first)
end

Related

Converting Ruby Hash into string with escapes

I have a Hash which needs to be converted in a String with escaped characters.
{name: "fakename"}
and should end up like this:
'name:\'fakename\'
I don't know how this type of string is called. Maybe there is an already existing method, which I simply don't know...
At the end I would do something like this:
name = {name: "fakename"}
metadata = {}
metadata['foo'] = 'bar'
"#{name} AND #{metadata}"
which ends up in that:
'name:\'fakename\' AND metadata[\'foo\']:\'bar\''
Context: This query a requirement to search Stripe API: https://stripe.com/docs/api/customers/search
If possible I would use Stripe's gem.
In case you can't use it, this piece of code extracted from the gem should help you encode the query parameters.
require 'cgi'
# Copied from here: https://github.com/stripe/stripe-ruby/blob/a06b1477e7c28f299222de454fa387e53bfd2c66/lib/stripe/util.rb
class Util
def self.flatten_params(params, parent_key = nil)
result = []
# do not sort the final output because arrays (and arrays of hashes
# especially) can be order sensitive, but do sort incoming parameters
params.each do |key, value|
calculated_key = parent_key ? "#{parent_key}[#{key}]" : key.to_s
if value.is_a?(Hash)
result += flatten_params(value, calculated_key)
elsif value.is_a?(Array)
result += flatten_params_array(value, calculated_key)
else
result << [calculated_key, value]
end
end
result
end
def self.flatten_params_array(value, calculated_key)
result = []
value.each_with_index do |elem, i|
if elem.is_a?(Hash)
result += flatten_params(elem, "#{calculated_key}[#{i}]")
elsif elem.is_a?(Array)
result += flatten_params_array(elem, calculated_key)
else
result << ["#{calculated_key}[#{i}]", elem]
end
end
result
end
def self.url_encode(key)
CGI.escape(key.to_s).
# Don't use strict form encoding by changing the square bracket control
# characters back to their literals. This is fine by the server, and
# makes these parameter strings easier to read.
gsub("%5B", "[").gsub("%5D", "]")
end
end
params = { name: 'fakename', metadata: { foo: 'bar' } }
Util.flatten_params(params).map { |k, v| "#{Util.url_encode(k)}=#{Util.url_encode(v)}" }.join("&")
I use it now with that string, which works... Quite straigt forward:
"email:\'#{email}\'"
email = "test#test.com"
key = "foo"
value = "bar"
["email:\'#{email}\'", "metadata[\'#{key}\']:\'#{value}\'"].join(" AND ")
=> "email:'test#test.com' AND metadata['foo']:'bar'"
which is accepted by Stripe API

Output value of keys in hash by method in ruby

I wrote weird, little and simple DSL on ruby, and I stuck on output values of keys.
I need to output values of concrete key of hash by that command:
p config.key1
#> Output down below:
#> "value1"
# here's key1 is key of hash, i want to output value of key that i wrote in method call.
# p config.key_name for example
There's my configus realisation:
require "deep_merge/rails_compat"
class Configus
class InHash
attr_reader :inner_hash
def initialize
#inner_hash = {}
end
def method_missing(name, *args, &block)
if block_given?
context = InHash.new
context.instance_eval &block
result = context.inner_hash
else
result = args
end
#inner_hash[name] = result
end
end
def self.config(environment, parent = nil, &block)
in_hash = InHash.new
in_hash.instance_eval &block
keys = in_hash.inner_hash.keys
index = keys.find_index(environment)
if parent && environment
parent_hash = in_hash.inner_hash[parent]
adopted_hash = in_hash.inner_hash[environment]
merged_hash = parent_hash.deeper_merge!(adopted_hash, { :overwrite_arrays => "TRUE" })
elsif environment == keys[index]
"#{environment.capitalize} hash: " + in_hash.inner_hash[environment]
end
end
end
Here's my init.rb:
require "./configus"
require "pry"
config = Configus.config :staging, :production do
production do
key1 "value1"
key2 "value2"
group1 do
key3 "value3"
key4 "value4"
end
end
staging do
key2 "new value2"
group1 do
key4 "new value4"
end
end
development do
key1 "new value1"
key2 "value2"
group1 do
key3 "new value3"
key4 "value4"
end
end
productionisgsgsd do
key10 "value10"
end
end
puts config.key1
#> I wrote output down below:
Traceback (most recent call last):
init.rb:35:in `<main>': undefined method `key1' for #<Hash:0x000056261379cdb0> (NoMethodError)
Did you mean? key
key?
keys
I just want to output value of concrete key of hash with concrete command:
p config.key_name
But i don't know how to do that and because of my stupid problem i want to ask you guys.
P.S Sorry for my english skill and that weird realisation of dsl ¯\_(ツ)_/¯
The issue is that you return a normal hash from your Configus.config method. Instead you should return your special InHash instance which you're able to customize behaviour.
The current issue lies here:
def self.config(environment, parent = nil, &block)
in_hash = InHash.new
in_hash.instance_eval &block
keys = in_hash.inner_hash.keys
index = keys.find_index(environment)
if parent && environment
parent_hash = in_hash.inner_hash[parent]
adopted_hash = in_hash.inner_hash[environment]
merged_hash = parent_hash.deeper_merge!(adopted_hash, { :overwrite_arrays => "TRUE" })
# ^ the above line returns a normal hash from the method
in_hash # <= add this to return your in_hash object
elsif environment == keys[index]
"#{environment.capitalize} hash: " + in_hash.inner_hash[environment]
end
end
The problem is not entirely solved with the above, since this now returns your InHash instance, but your InHash has no way of fetching keys, you can only set them. So you want to add a way to fetch values:
def method_missing(name, *args, &block)
if block_given?
context = InHash.new
context.instance_eval &block
#inner_hash[name] = context # set to the nested InHash instance
elsif args.empty?
#inner_hash[name] # without arguments given read the value
else
#inner_hash[name] = args # set to the arguments provided
end
end
The following code:
config = Configus.config :staging, :production do
# ...
end
Should now set config to an InHash instance, that has your custom behaviour defined. The args.empty? check in method_missing will return a value if no further arguments are given. Meaning that:
config.key1
Should now return your ["value1"] value. #inner_hash[name] = args will always set the value to an array. If you don't want this you might want to change it to:
#inner_hash[name] = args.size > 1 ? args : args.first

Deep Convert OpenStruct to JSON

I have an OpenStruct that is nested with many other OpenStructs. What's the best way to deeply convert them all to JSON?
Ideally:
x = OpenStruct.new
x.y = OpenStruct.new
x.y.z = OpenStruct.new
z = 'hello'
x.to_json
// {y: z: 'hello'}
Reality
{ <OpenStruct= ....> }
There is no default methods to accomplish such task because the built-in #to_hash returns the Hash representation but it doesn't deep converts the values.
If a value is an OpenStruct, it's returned as such and it's not converted into an Hash.
However, this is not that complicated to solve. You can create a method that traverses each key/value in an OpenStruct instance (e.g. using each_pair), recursively descends into the nested OpenStructs if the value is an OpenStruct and returns an Hash of just Ruby basic types.
Such Hash can then easily be serialized using either .to_json or JSON.dump(hash).
This is a very quick example, with an update from #Yuval Rimar for arrays of OpenStructs:
def openstruct_to_hash(object, hash = {})
case object
when OpenStruct then
object.each_pair do |key, value|
hash[key] = openstruct_to_hash(value)
end
hash
when Array then
object.map { |v| openstruct_to_hash(v) }
else object
end
end
openstruct_to_hash(OpenStruct.new(foo: 1, bar: OpenStruct.new(baz: 2)))
# => {:foo=>1, :bar=>{:baz=>2}}
Fixes to above solution to handle arrays
def open_struct_to_hash(object, hash = {})
object.each_pair do |key, value|
hash[key] = case value
when OpenStruct then open_struct_to_hash(value)
when Array then value.map { |v| open_struct_to_hash(v) }
else value
end
end
hash
end
Here's yet another approach, modified from lancegatlin's answer. Also adding the method to the OpenStruct class itself.
class OpenStruct
def deep_to_h
each_pair.map do |key, value|
[
key,
case value
when OpenStruct then value.deep_to_h
when Array then value.map {|el| el === OpenStruct ? el.deep_to_h : el}
else value
end
]
end.to_h
end
in initializers/open_struct.rb:
require 'ostruct'
# Because #table is a instance variable of OpenStruct and Object#as_json returns Hash of instance variables.
class OpenStruct
def as_json(options = nil)
#table.as_json(options)
end
end
Usage:
OpenStruct.new({ a: { b: 123 } }).as_json
# Result
{
"a" => {
"b" => 123
}
}
Edit:
This seems to do almost the same thing (notice the keys are symbols instead of strings)
OpenStruct.new({ a: { b: 123 } }).marshal_dump
# Result
{
:a => {
:b => 123
}
}
Same function that can accept arrays as an input too
def openstruct_to_hash(object, hash = {})
case object
when OpenStruct then
object.each_pair do |key, value|
hash[key] = openstruct_to_hash(value)
end
hash
when Array then
object.map { |v| openstruct_to_hash(v) }
else object
end
end
None of the above worked for me with a copy and paste. Adding this solution:
require 'json'
class OpenStruct
def deep_to_h
each_pair.map do |key, value|
[
key,
case value
when OpenStruct then value.deep_to_h
when Array then value.map {|el| el.class == OpenStruct ? el.deep_to_h : el}
else value
end
]
end.to_h
end
end
json=<<HERE
{
"string": "fooval",
"string_array": [
"arrayval"
],
"int": 2,
"hash_array": [
{
"string": "barval",
"string2": "bazval"
},
{
"string": "barval2",
"string2": "bazval2"
}
]
}
HERE
os = JSON.parse(json, object_class: OpenStruct)
puts JSON.pretty_generate os.to_h
puts JSON.pretty_generate os.deep_to_h

setting a nested value in PStore

PStore is a data-serializer in the Stdlib which is human-readable like JSON or YAML.
Everything has to be done in a "transaction", e.g.
storage = PStore.new("db")
storage.transaction do |db|
db[:key] = {}
db[:key][:sub_key] = "val"
return db[:key][:sub_key]
end
# ==> "val"
I want to be able to make a method like this:
def store(storage, list_of_keys, value)
storage.transaction do |db|
db[list_of_keys[0][list_of_keys[1]][list_of_keys[n]] = value
end
end
Conceptually I think currying would be useful here, but I've only done it in Javascript.
It doesn't seem to work to store the references to the intermediate values in variables like this:
storage.transaction do |db|
db_val_1 = db[keys[0]]
db_val_1 = value
end
I don't know exactly what you want. But my guess is you wanna store a value inside a nested hash with given list of keys. Since it's a nested has, I would use, well, a hash obviously. Here is a full working code for what I think you are trying to do:
require 'pstore'
require 'minitest/autorun'
storage = PStore.new("data_file.pstore")
def nest(key_list, value)
reverse_key_list = key_list.reverse
inner_most = {"#{reverse_key_list.shift}" => value}
reverse_key_list.reduce(inner_most) {|acc, current| {"#{current}" => acc }}
end
def nested_store(storage, key_list, value)
root = key_list.shift
data = nest(key_list, value)
storage.transaction { storage[root] = data }
end
describe "pstore test" do
before do
#key_list = %w(root sub1 sub2 sub3)
#val = "value"
end
it "should nest the list" do
nest(#key_list, #val).must_equal({"root" => {"sub1" => {"sub2" => {"sub3" => "value" }}}})
end
it "store the nested list" do
storage = PStore.new("data_file.pstore")
nested_store(storage, #key_list, #val)
storage.transaction do
storage["root"]["sub1"]["sub2"]["sub3"].must_equal "value"
end
end
end

Populate hash with array keys and default value

Stuck on a Code Wars Challenge: Complete the solution so that it takes an array of keys and a default value and returns a hash with all keys set to the default value.
My answer results in a parse error:
def solution([:keys, :default_value])
return { :keys => " ", :default_value => " " }
end
Am I missing something to do with returning a hash key with all the keys set to the default value?
Do as below :
def solution(keys,default_val)
Hash[keys.product([default_val])]
end
solution([:key1,:key2],12) # => {:key1=>12, :key2=>12}
Read Array#product and Kernel#Hash.
I'd advise amending your solution to this:
def solution(keys, default_value)
hash = {}
keys.each do |key|
value = default_value.dup rescue default_value
hash[key] = value
end
hash
end
The dup is to work around the nasty case where default_value is a string and you then do e.g.:
hash[:foo] << 'bar'
… with your version, this would modify multiple values in place instead of a single one.

Resources