Does declaring a hash with curly braces require assignment? - ruby

I thought I could declare a hash using either {} or Hash[], but curly braces doesn't seem to work unless I assign it to a variable (see below). I'm guessing irb gets confused about whether it's a hash or a block. Is that correct, or am I declaring the hash with curly braces incorrectly?
>> puts h = { :a=>1, :b=>2 }
{:a=>1, :b=>2}
>> puts { :a=>1, :b=>2 }
SyntaxError: (irb):58: syntax error, unexpected =>, expecting '}'
puts { :a=>1, :b=>2 }
^
from /usr/bin/irb:12:in `<main>'
>> puts Hash[ :a=>1, :b=>2 ]
{:a=>1, :b=>2}
>>

You can fix this by adding parentheses:
puts({ :a => 1, :b => 2 })
# {:a=>1, :b=>2}
If you leave off the parentheses, Ruby will interpret the curly braces as a block:
puts { :a => 1, :b => 2 }
# SyntaxError: unexpected ':', expecting '}'
# semantically the same as
puts do
:a => 1, :b => 2
end
# SyntaxError: unexpected =>, expecting keyword_end
But you can also omit both parentheses and Ruby will treat it as a Hash:
puts :a => 1, :b => 2
# {:a=>1, :b=>2}
# same as
puts(:a => 1, :b => 2)
# {:a=>1, :b=>2}
# same as
puts({:a => 1, :b => 2})
# {:a=>1, :b=>2}
And you can even use short notation for symbol keys ;-)
puts a: 1, b: 2
# {:a=>1, :b=>2}

It just requires unambiguous syntax, e.g.,
irb> puts({ :a => 1, :b => 2 })
{:a=>1, :b=>2}

Your puts { ... } looks to ruby as though you are passing a block of code. Since puts is a method that can take arguments and/or a block you need to specify that the stuff inside the curly brackets is intended to be an argument and not a block. Try:
puts( { a: 1, b: 2 })

Related

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

What is the proper way to pass a hash as a parameter to function call?

I have a function:
def a p
p
end
and a hash of symbols :a => :b. How should I pass it as a's parameter? What are possible ways?
I can use parentheses or the shorter form:
a(:a => :b) # => {:a=>:b}
a(a: :b) # => {:a=>:b}
Is there any way to pass this hash as a parameter without setting a variable and using parens? Just omitting them doesn't work.
a :a => :b # => SyntaxError: (irb):103: syntax error, unexpected ':', expecting end-of-input
a a: :b # => SyntaxError: (irb):105: syntax error, unexpected ':', expecting end-of-input

Ruby - method parameters

My method:
def my_method=(attributes, some_option = true, another_option = true)
puts hello
end
When i try to call this, i get such error:
my_method=({:one => 'one', :two => 'two'}, 1, 1)
#you_code.rb:4: syntax error, unexpected ',', expecting ')'
#my_method=({:one => 'one', :two => 'two'}, 1, 1)
^
What's the problem?
Method with suffix punctuation = can have only one argument.
Otherwise, you must use send to invoke with multiple parameters.
send :'my_method=', {:a => 1}, 1, 1
Don't use parenthesis when invoking a method using the = syntactic sugar.
Invoke it like this:
mymethod= {:one => 'one', :two => 'two'}, 1, 1

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]

Ruby: provide an argument while turning proc to a block

We can easily define a method and turn it into block with unary ampersand.
def my_method(arg)
puts arg*2
end
['foo', 'bar'].each(&method(:my_method))
# foofoo
# barbar
# or
my_method = ->(arg) { puts arg*2 }
['foo', 'bar'].each(&my_method)
# same output
As we see the first argument is passed automatically when we work with aggregates. But what if we need to pass 2 or even more arguments?
my_method = ->(arg,num) { puts arg*num }
['foo', 'bar'].each(&my_method)
# ArgumentError: wrong number of arguments (1 for 2)
['foo', 'bar'].each(&my_method(3))
# NoMethodError: undefined method `foo' for main:Object
['foo','bar'].each do |i, &my_method|
yield i, 3
end
# LocalJumpError: no block given (yield)
Is that possible to pass additional arguments while turning proc to a block?
#sawa is right. You can do that with curry.
Proc version:
mult = proc {|a, b| a * b} # => #<Proc:0x00000002af1098#(irb):32>
[1, 2].map(&mult.curry[2]) # => [2, 4]
Method version:
def mult(a, b)
a*b
end
[1, 2].map(&method(:mult).to_proc.curry[2]) # => [2, 4]
Regarding your comment:
Strange, but it swaps arguments during the performance
Actually, the argument order is preserved.
curry returns a new proc that effectively collects arguments until there are enough arguments to invoke the original method / proc (based on its arity). This is achieved by returning intermediate procs:
def foo(a, b, c)
{ a: a, b: b, c: c }
end
curried_proc = foo.curry #=> #<Proc:0x007fd09b84e018 (lambda)>
curried_proc[1] #=> #<Proc:0x007fd09b83e320 (lambda)>
curried_proc[1][2] #=> #<Proc:0x007fd09b82cfd0 (lambda)>
curried_proc[1][2][3] #=> {:a=>1, :b=>2, :c=>3}
You can pass any number of arguments at once to a curried proc:
curried_proc[1][2][3] #=> {:a=>1, :b=>2, :c=>3}
curried_proc[1, 2][3] #=> {:a=>1, :b=>2, :c=>3}
curried_proc[1][2, 3] #=> {:a=>1, :b=>2, :c=>3}
curried_proc[1, 2, 3] #=> {:a=>1, :b=>2, :c=>3}
Empty arguments are ignored:
curried_proc[1][][2][][3] #=> {:a=>1, :b=>2, :c=>3}
However, you obviously can't alter the argument order.
An alternative to currying is partial application which returns a new proc with lower arity by fixing one or more arguments. Unlike curry, there's no built-in method for partial application, but you can easily write your own:
my_proc = -> (arg, num) { arg * num }
def fix_first(proc, arg)
-> (*args) { proc[arg, *args] }
end
fixed_proc = fix_first(my_proc, 'foo') #=> #<Proc:0x007fa31c2070d0 (lambda)>
fixed_proc[2] #=> "foofoo"
fixed_proc[3] #=> "foofoofoo"
[2, 3].map(&fixed_proc) #=> ["foofoo", "foofoofoo"]
Or fixing the last argument:
def fix_last(proc, arg)
-> (*args) { proc[*args, arg] }
end
fixed_proc = fix_last(my_proc, 2) #=> #<Proc:0x007fa31c2070d0 (lambda)>
fixed_proc['foo'] #=> "foofoo"
fixed_proc['bar'] #=> "barbar"
['foo', 'bar'].map(&fixed_proc) #=> ["foofoo", "barbar"]
Of course, you are not limited to fixing single arguments. You could for example return a proc that takes an array and converts it to an argument list:
def splat_args(proc)
-> (array) { proc[*array] }
end
splatting_proc = splat_args(my_proc)
[['foo', 1], ['bar', 2], ['baz', 3]].map(&splatting_proc)
#=> ["foo", "barbar", "bazbazbaz"]

Resources