I'm generating some local variables in a method. I would like to find a way of returning them in a hash with keys and values, but the generation of that very hash ends up in the return itself. How can I avoid this?
def get_map_libs()
libjq = JSON.parse(File.read(URI.open('https://api.cdnjs.com/libraries/jquery', 'r')))['latest']
liblf = JSON.parse(File.read(URI.open('https://api.cdnjs.com/libraries/leaflet', 'r')))['latest']
libbs = JSON.parse(File.read(URI.open('https://api.cdnjs.com/libraries/twitter-bootstrap', 'r')))['latest']
libfa = JSON.parse(File.read(URI.open('https://api.cdnjs.com/libraries/font-awesome', 'r')))['latest']
return {libjq: libjq, liblf: liblf, libbs: libbs, libfa: libfa}
# return local_variables
end
This method works as expected. There should be a way of grabbing local_variables and returning it. However, when I use that commented-out return (return local_variables), it only returns their keys:
[
[0] :libjq,
[1] :liblf,
[2] :libbs,
[3] :libfa
]
I tried building a return hash r = {} and populating it, however that very hash also shows up in the return. I tried deleting it, but that throws an error when I try to delete itself in itself.
Can this be done or do I have to hard code it like above?
The documentation of local_variables tells that it only returns an array with the names of the local variables.
But you could use that method to generate the hash:
local_variables.map { |name| [name, eval(name.to_s)] }.to_h
I think that is a bit error-prone because you might return unexpected variables and their values.
Perhaps it would be better to refector your method to something like this:
LIBRARIES = {
libjq: 'jquery',
liblf: 'leaflet',
libbs: 'twitter-bootstrap',
libfa: 'font-awesome'
}
def library_urls
LIBRARIES.map { |k, v|
[k, JSON.parse(URI.open("https://api.cdnjs.com/libraries/#{v}").read)['latest']]
}.to_h
end
Well, the problem is that you are assigning the result hash to a local variable, so if you want to return a list of local variables, then of course that variable will be included.
The two simplest solutions I can think of would be:
Filter out the name of that local variable.
Just don't assign it. I.e. where you have r = something, just return something without assigning it to r in the first place. Something like this:
def get_map_libs
libjq = :jq
liblf = :lf
libbs = :bs
libfa = :fa
local_variables.map {|var| [var, binding.local_variable_get(var)] }.to_h
end
get_map_libs
#=> { :libjq => :jq, :liblf => :lf, :libbs => :bs, :libfa => :fa }
I have a ruby hash like this
h = {"a" => "1", "b" => "", "c" => "2"}
Now I have a ruby function which evaluates this hash and returns true if it finds a key with an empty value. I have the following function which always returns true even if all keys in the hash are not empty
def hash_has_blank(hsh)
hsh.each do |k,v|
if v.empty?
return true
end
end
return false
end
What am I doing wrong here?
Try this:
def hash_has_blank hsh
hsh.values.any? &:empty?
end
Or:
def hash_has_blank hsh
hsh.values.any?{|i|i.empty?}
end
If you are using an old 1.8.x Ruby
I hope you're ready to learn some ruby magic here. I wouldn't define such a function globally like you did. If it's an operation on a hash, than it should be an instance method on the Hash class you can do it like this:
class Hash
def has_blank?
self.reject{|k,v| !v.nil? || v.length > 0}.size > 0
end
end
reject will return a new hash with all the empty strings, and than it will be checked how big this new hash is.
a possibly more efficient way (it shouldn't traverse the whole array):
class Hash
def has_blank?
self.values.any?{|v| v.nil? || v.length == 0}
end
end
But this will still traverse the whole hash, if there is no empty value
I've changed the empty? to !nil? || length >0 because I don't know how your empty method works.
If you just want to check if any of the values is an empty string you could do
h.has_value?('')
but your function seems to work fine.
I'd consider refactoring your model domain. Obviously the hash represents something tangible. Why not make it an object? If the item can be completely represented by a hash, you may wish to subclass Hash. If it's more complicated, the hash can be an attribute.
Secondly, the reason for which you are checking blanks can be named to better reflect your domain. You haven't told us the "why", but let's assume that your Item is only valid if it doesn't have any blank values.
class MyItem < Hash
def valid?
!invalid?
end
def invalid?
values.any?{|i| i.empty?}
end
end
The point is, if you can establish a vocabulary that makes sense in your domain, your code will be cleaner and more understandable. Using a Hash is just a means to an end and you'd be better off using more descriptive, domain-specific terms.
Using the example above, you'd be able to do:
my_item = MyItem["a" => "1", "b" => "", "c" => "2"]
my_item.valid? #=> false
I want to create a "Config" class that acts somewhere between a hash and a tree. It's just for storing global values, which can have a context.
Here's how I use it:
Config.get("root.parent.child_b") #=> "value"
Here's what the class might look like:
class Construct
def get(path)
# split path by "."
# search tree for nodes
end
def set(key, value)
# split path by "."
# create tree node if necessary
# set tree value
end
def tree
{
:root => {
:parent => {
:child_a => "value",
:child_b => "another value"
},
:another_parent => {
:something => {
:nesting => "goes on and on"
}
}
}
}
end
end
Is there a name for this kind of thing, somewhere between Hash and Tree (not a Computer Science major)? Basically a hash-like interface to a tree.
Something that outputs like this:
t = TreeHash.new
t.set("root.parent.child_a", "value")
t.set("root.parent.child_b", "another value")
desired output format:
t.get("root.parent.child_a") #=> "value"
t.get("root") #=> {"parent" => {"child_a" => "value", "child_b" => "another value"}}
instead of this:
t.get("root") #=> nil
or this (which you get the value from by calling {}.value)
t.get("root") #=> {"parent" => {"child_a" => {}, "child_b" => {}}}
You can implement one in no-time:
class TreeHash < Hash
attr_accessor :value
def initialize
block = Proc.new {|h,k| h[k] = TreeHash.new(&block)}
super &block
end
def get(path)
find_node(path).value
end
def set(path, value)
find_node(path).value = value
end
private
def find_node(path)
path.split('.').inject(self){|h,k| h[k]}
end
end
You could improve implementation by setting unneeded Hash methods as a private ones, but it already works the way you wanted it. Data is stored in hash, so you can easily convert it to yaml.
EDIT:
To meet further expectations (and, convert to_yaml by default properly) you should use modified version:
class TreeHash < Hash
def initialize
block = Proc.new {|h,k| h[k] = TreeHash.new(&block)}
super &block
end
def get(path)
path.split('.').inject(self){|h,k| h[k]}
end
def set(path, value)
path = path.split('.')
leaf = path.pop
path.inject(self){|h,k| h[k]}[leaf] = value
end
end
This version is slight trade-off, as you cannot store values in non-leaf nodes.
I think the name for the structure is really a nested hash, and the code in the question is a reinvention of javascript's dictionaries. Since a dictionary in JS (or Python or ...) can be nested, each value can be another dictionary, which has its own key/val pairs. In javascript, that's all an object is.
And the best bit is being able to use JSON to define it neatly, and pass it around:
tree : {
'root' : {
'parent' : {
'child_a' : "value",
'child_b' : "another value"
},
'another_parent' : {
'something' : {
'nesting' : "goes on and on"
}
}
}
};
In JS you can then do tree.root.parent.child_a.
This answer to another question suggests using the Hashie gem to convert JSON objects into Ruby objects.
I think this resembles a TreeMap data structure similar to the one in Java described here. It does the same thing (key/value mappings) but retrieval might be different since you are using the nodes themselves as the keys. Retrieval from the TreeMap described is abstracted from the implementation since, when you pass in a key, you don't know the exact location of it in the tree.
Hope that makes sense!
Er... it can certainly be done, using a hierarchical hash table, but why do you need the hierarchy? IF you only need exactly-matching get and put, why can't you just make a single hash table that happens to use a dot-separated naming convention?
That's all that's needed to implement the functionality you've asked for, and it's obviously very simple...
Why use a hash-like interface at all? Why not use chaining of methods to navigate your tree? For example config.root.parent.child_b and use instance methods and if needed method_missing() to implement them?
I have a string which looks like a hash:
"{ :key_a => { :key_1a => 'value_1a', :key_2a => 'value_2a' }, :key_b => { :key_1b => 'value_1b' } }"
How do I get a Hash out of it? like:
{ :key_a => { :key_1a => 'value_1a', :key_2a => 'value_2a' }, :key_b => { :key_1b => 'value_1b' } }
The string can have any depth of nesting. It has all the properties how a valid Hash is typed in Ruby.
For different string, you can do it without using dangerous eval method:
hash_as_string = "{\"0\"=>{\"answer\"=>\"1\", \"value\"=>\"No\"}, \"1\"=>{\"answer\"=>\"2\", \"value\"=>\"Yes\"}, \"2\"=>{\"answer\"=>\"3\", \"value\"=>\"No\"}, \"3\"=>{\"answer\"=>\"4\", \"value\"=>\"1\"}, \"4\"=>{\"value\"=>\"2\"}, \"5\"=>{\"value\"=>\"3\"}, \"6\"=>{\"value\"=>\"4\"}}"
JSON.parse hash_as_string.gsub('=>', ':')
Quick and dirty method would be
eval("{ :key_a => { :key_1a => 'value_1a', :key_2a => 'value_2a' }, :key_b => { :key_1b => 'value_1b' } }")
But it has severe security implications.
It executes whatever it is passed, you must be 110% sure (as in, at least no user input anywhere along the way) it would contain only properly formed hashes or unexpected bugs/horrible creatures from outer space might start popping up.
The string created by calling Hash#inspect can be turned back into a hash by calling eval on it. However, this requires the same to be true of all of the objects in the hash.
If I start with the hash {:a => Object.new}, then its string representation is "{:a=>#<Object:0x7f66b65cf4d0>}", and I can't use eval to turn it back into a hash because #<Object:0x7f66b65cf4d0> isn't valid Ruby syntax.
However, if all that's in the hash is strings, symbols, numbers, and arrays, it should work, because those have string representations that are valid Ruby syntax.
I had the same problem. I was storing a hash in Redis. When retrieving that hash, it was a string. I didn't want to call eval(str) because of security concerns. My solution was to save the hash as a json string instead of a ruby hash string. If you have the option, using json is easier.
redis.set(key, ruby_hash.to_json)
JSON.parse(redis.get(key))
TL;DR: use to_json and JSON.parse
Maybe YAML.load ?
The solutions so far cover some cases but miss some (see below). Here's my attempt at a more thorough (safe) conversion. I know of one corner case which this solution doesn't handle which is single character symbols made up of odd, but allowed characters. For example {:> => :<} is a valid ruby hash.
I put this code up on github as well. This code starts with a test string to exercise all the conversions
require 'json'
# Example ruby hash string which exercises all of the permutations of position and type
# See http://json.org/
ruby_hash_text='{"alpha"=>{"first second > third"=>"first second > third", "after comma > foo"=>:symbolvalue, "another after comma > foo"=>10}, "bravo"=>{:symbol=>:symbolvalue, :aftercomma=>10, :anotheraftercomma=>"first second > third"}, "charlie"=>{1=>10, 2=>"first second > third", 3=>:symbolvalue}, "delta"=>["first second > third", "after comma > foo"], "echo"=>[:symbol, :aftercomma], "foxtrot"=>[1, 2]}'
puts ruby_hash_text
# Transform object string symbols to quoted strings
ruby_hash_text.gsub!(/([{,]\s*):([^>\s]+)\s*=>/, '\1"\2"=>')
# Transform object string numbers to quoted strings
ruby_hash_text.gsub!(/([{,]\s*)([0-9]+\.?[0-9]*)\s*=>/, '\1"\2"=>')
# Transform object value symbols to quotes strings
ruby_hash_text.gsub!(/([{,]\s*)(".+?"|[0-9]+\.?[0-9]*)\s*=>\s*:([^,}\s]+\s*)/, '\1\2=>"\3"')
# Transform array value symbols to quotes strings
ruby_hash_text.gsub!(/([\[,]\s*):([^,\]\s]+)/, '\1"\2"')
# Transform object string object value delimiter to colon delimiter
ruby_hash_text.gsub!(/([{,]\s*)(".+?"|[0-9]+\.?[0-9]*)\s*=>/, '\1\2:')
puts ruby_hash_text
puts JSON.parse(ruby_hash_text)
Here are some notes on the other solutions here
#Ken Bloom and #Toms Mikoss's solutions use eval which is too scary for me (as Toms rightly points out).
#zolter's solution works if your hash has no symbols or numeric keys.
#jackquack's solution works if there are no quoted strings mixed in with the symbols.
#Eugene's solution works if your symbols don't use all the allowed characters (symbol literals have a broader set of allowed characters).
#Pablo's solution works as long as you don't have a mix of symbols and quoted strings.
This short little snippet will do it, but I can't see it working with a nested hash. I think it's pretty cute though
STRING.gsub(/[{}:]/,'').split(', ').map{|h| h1,h2 = h.split('=>'); {h1 => h2}}.reduce(:merge)
Steps
1. I eliminate the '{','}' and the ':'
2. I split upon the string wherever it finds a ','
3. I split each of the substrings that were created with the split, whenever it finds a '=>'. Then, I create a hash with the two sides of the hash I just split apart.
4. I am left with an array of hashes which I then merge together.
EXAMPLE INPUT: "{:user_id=>11, :blog_id=>2, :comment_id=>1}"
RESULT OUTPUT: {"user_id"=>"11", "blog_id"=>"2", "comment_id"=>"1"}
I prefer to abuse ActiveSupport::JSON. Their approach is to convert the hash to yaml and then load it. Unfortunately the conversion to yaml isn't simple and you'd probably want to borrow it from AS if you don't have AS in your project already.
We also have to convert any symbols into regular string-keys as symbols aren't appropriate in JSON.
However, its unable to handle hashes that have a date string in them (our date strings end up not being surrounded by strings, which is where the big issue comes in):
string = '{'last_request_at' : 2011-12-28 23:00:00 UTC }'
ActiveSupport::JSON.decode(string.gsub(/:([a-zA-z])/,'\\1').gsub('=>', ' : '))
Would result in an invalid JSON string error when it tries to parse the date value.
Would love any suggestions on how to handle this case
works in rails 4.1 and support symbols without quotes {:a => 'b'}
just add this to initializers folder:
class String
def to_hash_object
JSON.parse(self.gsub(/:([a-zA-z]+)/,'"\\1"').gsub('=>', ': ')).symbolize_keys
end
end
Please consider this solution. Library+spec:
File: lib/ext/hash/from_string.rb:
require "json"
module Ext
module Hash
module ClassMethods
# Build a new object from string representation.
#
# from_string('{"name"=>"Joe"}')
#
# #param s [String]
# #return [Hash]
def from_string(s)
s.gsub!(/(?<!\\)"=>nil/, '":null')
s.gsub!(/(?<!\\)"=>/, '":')
JSON.parse(s)
end
end
end
end
class Hash #:nodoc:
extend Ext::Hash::ClassMethods
end
File: spec/lib/ext/hash/from_string_spec.rb:
require "ext/hash/from_string"
describe "Hash.from_string" do
it "generally works" do
[
# Basic cases.
['{"x"=>"y"}', {"x" => "y"}],
['{"is"=>true}', {"is" => true}],
['{"is"=>false}', {"is" => false}],
['{"is"=>nil}', {"is" => nil}],
['{"a"=>{"b"=>"c","ar":[1,2]}}', {"a" => {"b" => "c", "ar" => [1, 2]}}],
['{"id"=>34030, "users"=>[14105]}', {"id" => 34030, "users" => [14105]}],
# Tricky cases.
['{"data"=>"{\"x\"=>\"y\"}"}', {"data" => "{\"x\"=>\"y\"}"}], # Value is a `Hash#inspect` string which must be preserved.
].each do |input, expected|
output = Hash.from_string(input)
expect([input, output]).to eq [input, expected]
end
end # it
end
Here is a method using whitequark/parser which is safer than both gsub and eval methods.
It makes the following assumptions about the data:
Hash keys are assumed to be a string, symbol, or integer.
Hash values are assumed to be a string, symbol, integer, boolean, nil, array, or a hash.
# frozen_string_literal: true
require 'parser/current'
class HashParser
# Type error is used to handle unexpected types when parsing stringified hashes.
class TypeError < ::StandardError
attr_reader :message, :type
def initialize(message, type)
#message = message
#type = type
end
end
def hash_from_s(str_hash)
ast = Parser::CurrentRuby.parse(str_hash)
unless ast.type == :hash
puts "expected data to be a hash but got #{ast.type}"
return
end
parse_hash(ast)
rescue Parser::SyntaxError => e
puts "error parsing hash: #{e.message}"
rescue TypeError => e
puts "unexpected type (#{e.type}) encountered while parsing: #{e.message}"
end
private
def parse_hash(hash)
out = {}
hash.children.each do |node|
unless node.type == :pair
raise TypeError.new("expected child of hash to be a `pair`", node.type)
end
key, value = node.children
key = parse_key(key)
value = parse_value(value)
out[key] = value
end
out
end
def parse_key(key)
case key.type
when :sym, :str, :int
key.children.first
else
raise TypeError.new("expected key to be either symbol, string, or integer", key.type)
end
end
def parse_value(value)
case value.type
when :sym, :str, :int
value.children.first
when :true
true
when :false
false
when :nil
nil
when :array
value.children.map { |c| parse_value(c) }
when :hash
parse_hash(value)
else
raise TypeError.new("value of a pair was an unexpected type", value.type)
end
end
end
and here are some rspec tests verifying that it works as expected:
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe HashParser do
describe '#hash_from_s' do
subject { described_class.new.hash_from_s(input) }
context 'when input contains forbidden types' do
where(:input) do
[
'def foo; "bar"; end',
'`cat somefile`',
'exec("cat /etc/passwd")',
'{:key=>Env.fetch("SOME_VAR")}',
'{:key=>{:another_key=>Env.fetch("SOME_VAR")}}',
'{"key"=>"value: #{send}"}'
]
end
with_them do
it 'returns nil' do
expect(subject).to be_nil
end
end
end
context 'when input cannot be parsed' do
let(:input) { "{" }
it 'returns nil' do
expect(subject).to be_nil
end
end
context 'with valid input' do
using RSpec::Parameterized::TableSyntax
where(:input, :expected) do
'{}' | {}
'{"bool"=>true}' | { 'bool' => true }
'{"bool"=>false}' | { 'bool' => false }
'{"nil"=>nil}' | { 'nil' => nil }
'{"array"=>[1, "foo", nil]}' | { 'array' => [1, "foo", nil] }
'{foo: :bar}' | { foo: :bar }
'{foo: {bar: "bin"}}' | { foo: { bar: "bin" } }
end
with_them do
specify { expect(subject).to eq(expected) }
end
end
end
end
I built a gem hash_parser that first checks if a hash is safe or not using ruby_parser gem. Only then, it applies the eval.
You can use it as
require 'hash_parser'
# this executes successfully
a = "{ :key_a => { :key_1a => 'value_1a', :key_2a => 'value_2a' },
:key_b => { :key_1b => 'value_1b' } }"
p HashParser.new.safe_load(a)
# this throws a HashParser::BadHash exception
a = "{ :key_a => system('ls') }"
p HashParser.new.safe_load(a)
The tests in https://github.com/bibstha/ruby_hash_parser/blob/master/test/test_hash_parser.rb give you more examples of the things I've tested to make sure eval is safe.
This method works for one level deep hash
def convert_to_hash(str)
return unless str.is_a?(String)
hash_arg = str.gsub(/[^'"\w\d]/, ' ').squish.split.map { |x| x.gsub(/['"]/, '') }
Hash[*hash_arg]
end
example
> convert_to_hash("{ :key_a => 'value_a', :key_b => 'value_b', :key_c => '' }")
=> {"key_a"=>"value_a", "key_b"=>"value_b", "key_c"=>""}
I came to this question after writing a one-liner for this purpose, so I share my code in case it helps somebody. Works for a string with only one level depth and possible empty values (but not nil), like:
"{ :key_a => 'value_a', :key_b => 'value_b', :key_c => '' }"
The code is:
the_string = '...'
the_hash = Hash.new
the_string[1..-2].split(/, /).each {|entry| entryMap=entry.split(/=>/); value_str = entryMap[1]; the_hash[entryMap[0].strip[1..-1].to_sym] = value_str.nil? ? "" : value_str.strip[1..-2]}
Ran across a similar issue that needed to use the eval().
My situation, I was pulling some data from an API and writing it to a file locally. Then being able to pull the data from the file and use the Hash.
I used IO.read() to read the contents of the file into a variable. In this case IO.read() creates it as a String.
Then used eval() to convert the string into a Hash.
read_handler = IO.read("Path/To/File.json")
puts read_handler.kind_of?(String) # Returns TRUE
a = eval(read_handler)
puts a.kind_of?(Hash) # Returns TRUE
puts a["Enter Hash Here"] # Returns Key => Values
puts a["Enter Hash Here"].length # Returns number of key value pairs
puts a["Enter Hash Here"]["Enter Key Here"] # Returns associated value
Also just to mention that IO is an ancestor of File. So you can also use File.read instead if you wanted.
I had a similar issue when trying to convert a string to a hash in Ruby.
The result from my computations was this:
{
"coord":{"lon":24.7535,"lat":59.437},
"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],
"base":"stations",
"main":{"temp":283.34,"feels_like":281.8,"temp_min":282.33,"temp_max":283.34,"pressure":1021,"humidity":53},
"visibility":10000,
"wind":{"speed":3.09,"deg":310},
"clouds":{"all":75},
"dt":1652808506,
"sys":{"type":1,"id":1330,"country":"EE","sunrise":1652751796,"sunset":1652813502},
"timezone":10800,"id":588409,"name":"Tallinn","cod":200
}
I checked the type value and confirmed that it was of the String type using the command below:
result =
{
"coord":{"lon":24.7535,"lat":59.437},
"weather":[{"id":803,"main":"Clouds","description":"broken clouds","icon":"04d"}],
"base":"stations",
"main":{"temp":283.34,"feels_like":281.8,"temp_min":282.33,"temp_max":283.34,"pressure":1021,"humidity":53},
"visibility":10000,
"wind":{"speed":3.09,"deg":310},
"clouds":{"all":75},
"dt":1652808506,
"sys":{"type":1,"id":1330,"country":"EE","sunrise":1652751796,"sunset":1652813502},
"timezone":10800,"id":588409,"name":"Tallinn","cod":200
}
puts result.instance_of? String
puts result.instance_of? Hash
Here's how I solved it:
All I had to do was run the command below to convert it from a String to a Hash:
result_new = JSON.parse(result, symbolize_names: true)
And then checked the type value again using the commands below:
puts result_new.instance_of? String
puts result_new.instance_of? Hash
This time it returned true for the Hash