Hash declaration syntax error in irb - ruby

1. { :a => 10 } #=> no error
2. { a: 10 } #=> no error
3. { :"str" => 10 } #=> no error
4. { "str": 10 } #=> syntax error, unexpected ':', expecting =>
Isn't 4. same as 2? Why 2 is working and 4 throws syntax error?

My understanding is that {"key": value} is not a valid syntax as it is not clear whether it means {:"key" => value} or {"key" => value}
There is a discussion on this here. Quote from Matz in the discussion
| Iff {'key': 'value'} means {:key => 'value'} I have no objection.
| Won't that be misleading? I think the OP wants {'key': 'value'} to mean {'key' => 'value}
But considering the fact that {key: "value"}
is a shorthand for {:key => "value"}, {"key": "value"} should be a
shorthand for {:"key" => "value"}. Besides that, since it reminds me
JSON so much, making a: and "a": different could cause more confusion
than the above misleading.
matz.

Hash: Hashes allow an alternate syntax form when your keys are always symbols.
options = { :font_size => 10, :font_family => "Arial" }
You could write it as:
options = { font_size: 10, font_family: "Arial" }
In your first 3 cases all are symbols in key position,but the fourth is a string instance,not the symbol instance as key.That's the reason 4th case is invalid Ruby syntax.
{ :a => 10 }.keys[0].class # => Symbol
{ a: 10 }.keys[0].class # => Symbol
{ :"str" => 10 }.keys[0].class # => Symbol

No. (1) is standard symbol, (2) is shorthand 1.9 syntax for symbol-key hashes, (3) is shorthand for "str".to_sym, (4) does not exist and you should use the hashrocket.

Related

Ruby: what's the difference using property with colons and quotes

I'm a newbie on Ruby, just doing a fast upgrade in a existent project and I'm wondering what's the difference between object record and to_validate.
puts to_validated.class
# Hash
puts to_validated
# {"data"=>{"name"=>"david"}, "metadata"=>{"body_size"=>"16", "collector_ip"=>"172.22.0.1", "collector_timestamp"=>1579608324863, "event"=>"whatever", "version"=>"1.0"}}
puts record.class
# Hash
puts record
# {:data=>{"name"=>"david"}, :metadata=>{"body_size"=>"16", "collector_ip"=>"172.22.0.1", "collector_timestamp"=>1579610268940, "event"=>"default", "version"=>"1.0.0"}}
The only difference on those objects is colons on data and metadata. Is possible to convert colons into quotes?
I know it's a dumb question but I'm applying a fix in this project and using a third party library that is failing using record object.
You can use Hash.transform_keys which was introduced to Ruby in version 2.5 to change symbolized keys into strings:
record.transform_keys { |k| k.to_s }
In Ruby hashes are a key value data structure. The keys can actually be a mix of any kind of objects:
hash_with_numerical_keys = {
1 => 'A',
2 => 'B',
3 => 'C'
3.5 => 'D'
}
hash_with_string_keys = {
'a' => 1,
'b' => 2,
'c' => 3
}
hash_with_symbols = {
:a => 1,
:b => 2,
:c => 3
}
# can also be declared as
hash_with_symbols = {
a: 1,
b: 2,
c: 3
}
hash_with_singletons = {
nil => 0,
true => 1,
false => 2
}
Symbols are commonly used as only one instance of a symbol ever exists. They are thus really effective to compare. String keys mostly come into play when you're dealing with JSON or some kind of external data.
The hash you are looking at contains a mixture of string and symbol keys:
{:data=>{"name"=>"david"}, :metadata=>{"body_size"=>"16", "collector_ip"=>"172.22.0.1", "collector_timestamp"=>1579610268940, "event"=>"default", "version"=>"1.0.0"}}
In Ruby 2.5 you can use hash#transform_keys to change the keys into strings:
hash.transform_keys(&:to_s)
In earlier versions you can do:
hash.each_with_object({}) do |(k,v), new_hash|
new_hash[k.to_s] = v
end
If you are using Rails or just ActiveSupport you can use Hash#stringify_keys, Hash#deep_stringify_keys or HashWithIndifferentAccess

JSON with symbols and strings not readable

I have the following JSON:
{ :a => 1, "b" => "test" }
jsonObject[:b] does not give me any data, whereas for a JSON with all keys as strings,
{ "a" => 1, "b" => "test" }
it works fine:
jsonObject[:b] # => "test"
Is there a constraint against using a symbol and key in the same JSON object?
I suggest to parse a JSON to a Hash before using, like
require 'json'
JSON.parse("{...}")
and convert a hash to a JSON string by
hash.to_json
all keys of symbols and strings are converted into strings.
require 'json'
a = {:a => '12', 'b' => '23'}
p aa = a.to_json #=> "{\"a\":\"12\",\"b\":\"23\"}"
p JSON.parse(aa) #=> {"a"=>"12", "b"=>"23"}
It might be possible that you are sometimes dealing with a simple Hash and sometimes with a HashWithIndifferentAccess. The Rails' params for example allow indifferent access by default. This might explain your confusion:
hash = { :a => 1, 'b' => 2 }
hash[:a]
#=> 1
hash['b']
#=> 2
hash[:b]
#=> nil
But with a HashWithIndifferentAccess:
hash = hash.with_indifferent_access
hash[:a]
#=> 1
hash['b']
#=> 2
hash[:b]
#=> 2

How does this definition for a hash work? [duplicate]

This question already has answers here:
Is there any difference between the `:key => "value"` and `key: "value"` hash notations?
(5 answers)
Closed 8 years ago.
I found code with hash assignment such as follows:
#defeat = {r: :s, p: :r, s: :p}
# => {:r=>:s, :p=>:r, :s=>:p}
Why are the keys for this hash generated as symbols? Is this a short form of doing this?
defeat[:r] = :s
defeat[:p] = :r
defeat[:s] = :p
Is there a name for this style of Hash?
A Hash can be easily created by using its implicit form:
grades = { "Jane Doe" => 10, "Jim Doe" => 6 }
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" }
Now in your example #defeat = {r: :s, p: :r, s: :p}, all keys are symbol. That's why your example Hash is a valid construct, which has been introduced since 1.9.
When you use the hash style {key: value} you're actually declaring a symbol for the key. Like Arup's example, {:key => value} is the same thing with the implicit form. So anytime you use : instead of => in a hash, you are creating a symbol as the key.
In your example, you're creating symbols for both your key and your value.
{key: :value } # both are symbols

How to pass the second parameter to Mongo::Collection#find?

This is a newbie question. I find the method definition in the YARD Rdoc:
(Object) find(selector = {}, opts = {})
Options Hash (opts):
:fields (Array, Hash)
then I try this coll.find('English' => 'fulcrum',{English:1,Chinese:1}), want the result 'English' field is fulcrum ,and only return English and Chinese field, but Ruby punished me with the this
irb(main):018:0> coll.find('English' => 'fulcrum',{English:1,Chinese:1})
SyntaxError: (irb):18: syntax error, unexpected ')', expecting tASSOC
from /usr/local/bin/irb:12:in `<main>'
irb(main):019:0>
I want to know why, thanks
after correct the syntax problem by the suggestion by #mu, I got Unknown options error:
irb(main):013:0> coll.find({English:'fulcrum'},{English:1, :Chinese => 1})RuntimeError: Unknown options [{:English=>1, :Chinese=>1}]
from /usr/local/lib/ruby/gems/1.9.1/gems/mongo-1.5.2/lib/mongo/collection.rb:234:in `find'
from (irb):13
from /usr/local/bin/irb:12:in `<main>'
irb(main):014:0>
When Ruby sees an unwrapped Hash in argument list:
o.m(k => v, ...)
it assumes that you really mean this:
o.m({ k => v, ... })
So, when you say this:
coll.find('English' => 'fulcrum', {English: 1, Chinese: 1})
Ruby sees this:
coll.find({ 'English' => 'fulcrum', {English: 1, Chinese: 1} })
A Hash is a perfectly valid key so Ruby expects it to be followed by a => value:
coll.find('English' => 'fulcrum', {English: 1, Chinese: 1} => some_value)
and that's where the error message comes from:
syntax error, unexpected ')', expecting tASSOC
If you want to pass two hashes, you need to wrap the first one in braces:
coll.find({'English' => 'fulcrum'}, {English: 1, Chinese: 1})
The second argument to [find](
http://api.mongodb.org/ruby/current/Mongo/Collection.html#find-instance_method) should be an options Hash and it looks like you want the :fields option and you can give that an array of names instead of a noisy Hash:
coll.find({'English' => 'fulcrum'}, :fields => %w[English Chinese])

Ruby on Rails 3, syntax error while creating a new object

I am still learning Ruby, and still copy pasting from my manual. But I run on a problem, that I dont know how to explain and what am I doing wrong. So here it is:
I want to create a new object with this:
second_page = Page.new ( :name=>"Second page", :position=>1, :permalink => "second" )
and I got a error:
Loading development environment (Rails 3.0.10)
ruby-1.9.2-p290 :001 > second_page = Page.new ( :name=>"Second page", :position=>1, :permalink => "second" )
SyntaxError: (irb):1: syntax error, unexpected tASSOC, expecting ')'
...econd_page = Page.new ( :name=>"Second page", :position=>1, ...
... ^
(irb):1: syntax error, unexpected ',', expecting $end
...age.new ( :name=>"Second page", :position=>1, :permalink => ...
... ^
from /usr/local/rvm/gems/ruby-1.9.2-p290/gems/railties-3.0.10/lib/rails/commands/console.rb:44:in `start'
from /usr/local/rvm/gems/ruby-1.9.2-p290/gems/railties-3.0.10/lib/rails/commands/console.rb:8:in `start'
from /usr/local/rvm/gems/ruby-1.9.2-p290/gems/railties-3.0.10/lib/rails/commands.rb:23:in `<top (required)>'
from script/rails:6:in `require'
from script/rails:6:in `<main>'
but, if I do this:
ruby-1.9.2-p290 :002 > second_page = Page.new :name=>"Second page", :position=>1, :permalink => "second"
=> #<Page id: nil, subject_id: nil, name: "Second page", permalink: "second", position: 1, visible: false, created_at: nil, updated_at: nil>
that seems to work.
I got example from manual, and I am wondering what is going on?
And without () I dont know how can I do stuff with that object?
Thank you
Ruby doesn't allow you to put spaces before round brackets if you choose to place them. This should work:
second_page = Page.new( :name=>"Second page", :position=>1, :permalink => "second" )
What you have here is the parser trying to resolve syntax ambiguities. Page.new accepts a single argument: a hash containing attributes which should be set on the newly created active record object.
If you now call the method without any parentheses, it is not initially clear what the arguments are. Thus the parser is smart enough to figure out it should be a hash in this case.
If you actually write the parentheses, you have to be a bit more specific and have to actually write down the hash braces too. Thus the following statements are equivalent:
first_page = Page.new :foo => "Bar"
second_page = Page.new({:foo => "Bar"})
third_page = Page.new ({:foo => "Bar"})
In most cases, parentheses are optional in method calls in Ruby. But only if there aren't any ambiguities. If in doubt, always specify the parentheses. Note that Ruby 1.9 changed the syntax here and is thus a bit more strict.
In ruby parenthesis to the method arguments are not necessary. So,
object.method()
# is same as
object.method
object.method(param1, param2)
# is same as
object.method param1, param2
There is another popular syntax for passing arbitrary number of parameters:
def print_a(*params)
puts params.inspect
end
print_a "a"
#prints: ["a"]
print_a "a", "b"
#prints: ["a", "b"]
print_a "a", "b", 2, :four => 4
#prints: ["a", "b", 2, {:four=>4}]
print_a "a", "b", 3, :four => 4, :five => 5
#prints: ["a", "b", 3, {:four=>4, :five=>5}]
As you may have noticed in the last example ruby is smart enough to detect hashes and aggregate the key value pairs in a single hash argument. But it only works if the hash is last argument.
print_a("a", "b", :four => 4, :five => 5, 3)
# gives error: syntax error, unexpected '\n', expecting tASSOC
# converting the hash to an explicit hash works again
print_a "a", "b", {:four => 4, :five => 5}, 3
# ["a", "b", {:four=>4, :five=>5}, 3]

Resources