Hash key access via symbol not string [duplicate] - ruby

This question already has answers here:
Best way to convert strings to symbols in hash
(31 answers)
Closed 7 years ago.
I have the following code. I don't understand why table['something'] is different from table[:something].
require 'json'
data = '[{"type":"local","db":"all","user":"postgres","addr":null,"method":"ident"},{"type":"local","db":"all","user":"all","addr":null,"method":"ident"},{"type":"host","db":"all","user":"all","addr":"0.0.0.0/0","method":"md5"},{"type":"host","db":"all","user":"all","addr":"::1/128","method":"md5"},{"type":"local","db":"communicator","user":"communicator","addr":" ","method":"trust"}]'
table = JSON.parse(data)
table.each do | auth |
if (auth['user'])
print( '\n\n\n' + auth[:user] )
end
end
And on line with print( '\n\n\n' + auth[:user] ), I'm receiving an error:
TypeError: no implicit conversion of nil into String
from (irb):88:in `+'
This means that accessing table via :key is different from 'key'.
Why? How do I convert table to make it work with :key instead of 'key'?

The problem is that {"user":"postgres"} in JSON means:
{"user" => "postgres"}
in Ruby whereas in Ruby it means:
{:user => "postgres"}
Since you have it as JSON, you need to access the value as:
auth["user"]
and not as:
auth[:user]
which is nil since the hash lacks the key :user.

Because 'key' is a String and :key is a Symbol - those are two different things in Ruby.
It can be somewhat confusing, because :'key', or 'key': will also be a Symbol To make it work, just access Hash fields with a Symbol, like:
if (auth[:user])
To convert String indexed Hash to Symbol indexed Hash, refer to this question:
Best way to convert strings to symbols in hash

A Symbol :key is something other than a String 'key', just like a number 3 is something other than a String '3'. To convert a String to a key you can use key.to_sym
About the differance between the two other SO questions have been asked.
What's the difference between a string and a symbol in Ruby?
See also this article
EDIT
A solution to change your keys to symbol if you have no control over the source
require 'json'
data = '[{"type":"local","db":"all","user":"postgres","addr":null,"method":"ident"},{"type":"local","db":"all","user":"all","addr":null,"method":"ident"},{"type":"host","db":"all","user":"all","addr":"0.0.0.0/0","method":"md5"},{"type":"host","db":"all","user":"all","addr":"::1/128","method":"md5"},{"type":"local","db":"communicator","user":"communicator","addr":" ","method":"trust"}]'
table = JSON.parse(data)
p table
# [{"type"=>"local", "db"=>"all", "user"=>"postgres", "addr"=>nil, "method"=>"ident"}, {"type"=>"local", "db"=>"all", "user"=>"all", "addr"=>nil, "method"=>"ident"}, {"type"=>"host", "db"=>"all", "user"=>"all", "addr"=>"0.0.0.0/0", "method"=>"md5"}, {"type"=>"host", "db"=>"all", "user"=>"all", "addr"=>"::1/128", "method"=>"md5"}, {"type"=>"local", "db"=>"communicator", "user"=>"communicator", "addr"=>" ", "method"=>"trust"}]
table.map!{|auth|
new_auth = {}
auth.each{|k, v| new_auth[k.to_sym] = v}
new_auth
}
p table
# [{:type=>"local", :db=>"all", :user=>"postgres", :addr=>nil, :method=>"ident"}, {:type=>"local", :db=>"all", :user=>"all", :addr=>nil, :method=>"ident"}, {:type=>"host", :db=>"all", :user=>"all", :addr=>"0.0.0.0/0", :method=>"md5"}, {:type=>"host", :db=>"all", :user=>"all", :addr=>"::1/128", :method=>"md5"}, {:type=>"local", :db=>"communicator", :user=>"communicator", :addr=>" ", :method=>"trust"}]

Related

Is there a method to get the value of key in ruby and store it in a variable

I am trying to get the value of a key and trying to store it in an array
Below is the sample code,
require 'rubygems'
require 'json'
opt=[]
response_assigned = {
"poll_id": 1194
}
opt << [JSON.parse(response_assigned)['poll_id']]
By using ruby, I have tried to convert poll_id variable into string,
opt << [JSON.parse(response_assigned)['poll_id'].to_s,channel_id]
but it is throwing same error.
'convert_encoding': {:poll_id=>1194} is not like a string (TypeError)
response_assigned is already a Hash. You can access the values via :[]; there's no need to use JSON.parse here. (This method is used for converting JSON strings into hashes, like the object you already have!)
Also, a more subtle note: There are two distinct types of object in ruby: String and Symbol.
By defining your object like this: {"poll_id": 1194}, you have made the hash key a symbol. It's equivalent to writing this: {poll_id: 1194}, or this: {:poll_id => 1194}.
Therefore, in order to access the value, you can use:
opt << response_assigned[:poll_id]
If you want to make the hash key a String instead of a Symbol, you could write:
response_assigned = {
"poll_id" => 1194
}
opt << response_assigned["poll_id"]

Accessing a hash with symbol or integer

I have this hash:
#current_user.birthday = { "birthday" => "12/01/1978", "id" => "524626626" }
I would like to grab the date "12/01/1978".
I tried #current_user.birthday[:birthday], only to get no implicit conversion of Symbol into Integer
What's the proper way?
EDIT
I think the output is a string instead
puts #current_user.birthday gives me {"birthday"=>"09/21/1985", "id"=>"425495284312580"}
puts #current_user.birthday[0..3] gives me {"bi
I can possibly get it by #current_user.birthday[14..23] which puts out 12/01/1978. But is there a better way, in the event there are multiple birthdays
It should be
#current_user.birthday["birthday"]
Your birthday isn't a symbol but string.
So the problem is with the Hash printed as a String. I used eval to convert it into a Hash
#current_user.birthday = {"birthday"=>"09/21/1985", "id"=>"425495284312580"}
birthday = #current_user.birthday
hash = eval(birthday)
puts hash["birthday"] => 12/01/1978

Ruby hash defaults: where do nested values go? [duplicate]

This question already has answers here:
Strange, unexpected behavior (disappearing/changing values) when using Hash default value, e.g. Hash.new([])
(4 answers)
Closed 6 years ago.
I wanted to use Ruby's default hash values to allow me to more easily nest hashes without having to manually initialize them. I thought it'd be nice to be able to dig a level down for each key safely without having pre-set the key as a hash. However, I find that when I do this, the data gets stored somewhere, but is not visible by accessing the top-level hash. Where does it go, and how does this work?
top = Hash.new({}) #=> {}
top[:first][:thing] = "hello" #=> "hello"
top[:second] = {thing: "world"} #=> {:thing => "world"}
top #=> {:second => {:thing => "world"}}
top[:first] #=> {:thing => "hello"}
You want to know where your inserted hash is? Maybe you have heard about Schroedingers cat:
h = Hash.new({})
h[:box][:cat] = "Miau"
=> "Miau"
h
=> {}
The cat seem to be dead....
h[:schroedingers][:cat]
=> "Miau"
The cat seem still to be alive, but in a different reality....
Ok, if nothing helps, "Read The Fine Manual". For Hash.new, we read:
If obj is specified, this single object will be used for all default values.
So when you write h[:box], a object is returned, and this object is another hash, and it happen to empty.
Into this empty hash, you write an key-value.
Now this other hash is no longer empty, it has a key-value pair. And it is returned every time you search for a key is not found in your original hash.
You can access the default value via a variety of #default methods
http://ruby-doc.org/core-2.2.3/Hash.html#method-i-default
top.default
=> {:thing=>"hello"}
You can also tell it how you want it to act, example:
irb(main):058:0> top = Hash.new {|h,k| h[k] = {}; h[k]}
=> {}
irb(main):059:0> top[:first][:thing] = "hello"
=> "hello"
irb(main):060:0> top[:second] = {thing: "world"}
=> {:thing=>"world"}
irb(main):061:0> top
=> {:first=>{:thing=>"hello"}, :second=>{:thing=>"world"}}

I'm confused by Ruby notation [duplicate]

This question already has answers here:
Is there any difference between the `:key => "value"` and `key: "value"` hash notations?
(5 answers)
Closed 5 years ago.
When using Ruby, I keep getting mixed up with the :.
Can someone please explain when I'm supposed to use it before the variable name, like :name, and when I'm supposed to use it after the variable like name:?
An example would be sublime.
This has absolutely nothing to do with variables.
:foo is a Symbol literal, just like 'foo' is a String literal and 42 is an Integer literal.
foo: is used in three places:
as an alternative syntax for Symbol literals as the key of a Hash literal: { foo: 42 } # the same as { :foo => 42 }
in a parameter list for declaring a keyword parameter: def foo(bar:) end
in an argument list for passing a keyword argument: foo(bar: 42)
You are welcome for both, while creating Hash :
{:name => "foo"}
#or
{name: 'foo'} # This is allowed since Ruby 1.9
But basically :name is a Symbol object in Ruby.
From docs
Hashes allow an alternate syntax form when your keys are always symbols. Instead of
options = { :font_size => 10, :font_family => "Arial" }
You could write it as:
options = { font_size: 10, font_family: "Arial" }
:name is a symbol. name: "Bob" is a special short-hand syntax for defining a Hash with the symbol :name a key and the string "Bob" as a value, which would otherwise be written as { :name => "Bob" }.
You can use it after when you are creating a hash.
You use it before when you are wanting to reference a symbol.
In Arup's example, {name: 'foo'} you are creating a symbol, and using it as a key.
Later, if that hash is stored in a variable baz, you can reference the created key as a symbol:
baz[:name]

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

Resources