Why "puts {a: 1}" is a violation of Rubocop - ruby

The expression { a: 1 } is not a problem,
but Rubocop's point( Lint/Syntax: unexpected token tCOLON(Using Ruby 3.1 parser; configure using TargetRubyVersion parameter, under AllCops) ) appears in the expression
puts { a: 1 }.
Does anyone know why this statement is against the rules? If you have a reference, I would appreciate it if you could also give me the URL of it.
I use rubocop version 1.24.1 and set TargetRubyVersion in .rubocop.yml. ruby versions 2.7, 3.0 and 3.1 were all tested with the same result.

Braces in Ruby are used for two things: blocks and hashes. And the Ruby parser isn't going to perform infinite lookahead to determine which one it's looking at. If the current syntactic context accepts a block, Ruby assumes the thing you've given it with braces is a block.
puts { a: 1 }
First, we parse puts. It's an identifier, and it's not a local variable, so we'll assume it's a method on the current (main) object. Now we see {. It could be either a hash or a block. And we just parsed an identifier starting a function call, so a block would make sense here. Let's parse it as a block.
Next we see a. Okay, that's fine. A block with no arguments, and we've got an identifier called a. Again, it's not a local variable, so it must be a method on main. Then we see : and that doesn't work. There's no way a : can appear at this point in the syntax, so it's an error. Ruby does not go back and try the "hash" option; it's already committed completely to parsing a block, and that failed.
To call puts with a single hash argument, use parentheses to disambiguate.
puts({ a: 1 })

Related

ArgumentError when upgrading from Ruby 2.7 to 3.0 - related to separation of positional and keyword arguments?

I am trying to upgrade gems from ruby 2.7.0 to 3.0.0. I have tried to read and plan ahead for what roadblocks I'd hit, like the Separation of positional and keyword arguments.
I have updated two gems smoothly, but I have started to upgrade a third and, while testing the 3.0 update with RSpec, have run into the following error:
ArgumentError:
wrong number of arguments (given 1, expected 0; required keyword: children)
I am trying it instantiate a Class through a Factory. The stack trace is pointing back to the arguments of this function:
# rubocop:disable Style/KeywordParametersOrder
def initialize(parent_obj: nil, children:, **attributes)
#attributes = attributes
#parent = parent_obj
#children_json = children
end
# rubocop:enable Style/KeywordParametersOrder
Though I don't think it is significant, I have included the rubocop dis/enable lines just in case they are important.
The Factory is calling the class in this fashion:
data = {:some_data=>"foo", :some_name=>"bar", :children=>[]}
Long::Class::Name.new(data)
Now, when I pry into the code between data and Long::Class::Name.new(data) and instantiate my own Long::Class::Name like below:
Long::Class::Name.new(children: children, attributes: data)
it results in a successful creation without the ArgumentError.
I think to myself, "Cool, I'll just update the Factory's Class call to this new format and re-run the tests." After doing this, I am still getting the same ArgumentError as above.
I believe this is an issue with positional/keyword arguments like I linked above, but I am having trouble seeing how I can correct this. Besides the link above, I have also looked into Ruby 3 Keyword Arguments, as well as Hash and Keyword Coercion and Ruby 3 Changes. I believe I am facing the "Unforeseen Consequences" portion of that last link.
I have also looked at this Stack Overflow issue about ArgumentError after updating from Ruby 2.7 to Ruby 3.0 and tried to understand how I could use the first portion of the first answer to help me with my issue (disregarding the Update related to a PR).
Any thoughts on how can I dispel or work around this error? I have many gems I need to update and I am sure this will not be the last time I see this error. Any help would be greatly appreciated. Let me know if more information is needed.
When you do this…
data = {:some_data=>"foo", :some_name=>"bar", :children=>[]}
Long::Class::Name.new(data)
…you are calling the method with one positional argument (a hash containing the keys :some_data, :some_name, and :children) and no keyword argument.
If you instead were to call it like this…
Long::Class::Name.new(**data)
…you would be calling it with no positional argument and three keyword arguments.
This is called the "double splat" and was introduced for exactly your usecase, turning a hash into keyword arguments.

Ruby koan 124 - Are these symbols spontaneously changing type?

I'd like to get a categorical answer on how (and why) symbols appear to change type.
I had learned that symbols that end with a colon were an alternate syntax for declaring a symbol, usually used to populate hashes.
Koan number 124 has added more ambiguity to my (non)understanding by doing the following:
def method_with_keyword_arguments(one: 1, two: 'two')
[one, two]
end
This page:
http://chriszetter.com/blog/2012/11/02/keyword-arguments-in-ruby-2-dot-0/
says Keyword arguments in the method definition must be symbols given in the new-style hash syntax but then what appear to be symbols in the parameters of the method are then method variables in the returned Array.
Can symbols change 'type' without even being changed by a method? Or, in this case, is there a method I'm not seeing that's changing the type? And if the type doesn't matter, why does it appear to change? Or can symbols be referred to without a colon?
If I'm wrong about any of this, please feel free to correct me and let me know what actually IS going on! Thanks.
No, symbols cannot change type on their own, but when you're using one or two in your example, you are using variables with similar names, provided by Ruby based on your method definition, not the symbols themselves.
Consider a hash: when you have a hash a = { one: 1, two: '1234' } and you write a[:one] you don't get a symbol, but an appropriate value. So :one still is a symbol, but with [a[:one], a[:two]] you will get [1, '1234'] array, not [:one, :two].
There are no symbols here. one and two are parameters, parameters become local variable variables in the method body.
In
def foo(bar:, baz:)
bar and baz are no more symbols than they are in
def foo(bar, baz)
In the first case they are keyword parameters, in the second case they are positional parameters, other than that, there is no difference. In the parameter list, they are parameter placeholders, in the method body, they become local variables bound to arguments.

Ruby if vs end of the line if behave differently?

Why doesn't this code work?
b if b = true
Error: undefined local variable or method `b'
But this does:
if b = true
b
end
Shouldn't they be the same?
This is a very good question. It has to do with the scoping of variables in Ruby.
Here is a post by Matz on the Ruby bug tracker about this:
local variable scope determined up to down, left to right. So a local variable first assigned in the condition of if modifier is not effective in the left side if body. It's a spec.
In the first version as soon as k is hit, the parser pukes because it hasn't been seen yet.
In the second version, k is part of an assignment expression, and is parsed differently.
I don't know the reason but the problem that the interpreter tries to lookup the variable k before evaluating the condition.
If you write it like this, there won't be any error and works as you expected:
k = nil
h = {k: 1}
v = k if k = h.delete(:k)
you have put only one '='
Try with '=='
Then you will get error
In second example, you are assigning 'true' to b.
Because the Ruby interpreter creates a local variable when it sees an assignment
In the second case, it hasn't yet seen the assignment, so the variable doesn't exist when the expression is parsed.
To be more precise, a method is first parsed into an internal representation, and then, perhaps, the code will eventually be called and actually executed.
Local variables are created in that parsing pass. It's a matter of declaration, it just means that the interpreter becomes aware of them. They won't be created in the sense of being given space or a value until the surrounding method is called by someone.

What is the difference between these two method parameter definitions?

I'm starting to learn Ruby. I read that arguments where passed by reference to a method,
however I don't understand the difference between these two methods.
def print(text)
puts text
end
and
def print(*text)
puts text
end
Using a * means that we are passing a pointer like in C?
The *text is what's called the splat operator in Ruby. It basically means if you pass multiple arguments to the second print they will get slurped into the single text variable.
See The Splat Operator in Ruby
The * before a parameter name in a Ruby parameter list is used for variable length arguments, so they are similar to the ... in C/C++ for varargs.
def vlaFunc(*args)
puts args
end
vlaFunc(1,2,3)
# output is [1,2,3]
There are no pointers in Ruby, * in this context is generally referred to as the "splat" operator:
http://4loc.wordpress.com/2009/01/16/the-splat-operator-in-ruby/
http://theplana.wordpress.com/2007/03/03/ruby-idioms-the-splat-operator/
In this case the method can take an arbitrary number of arguments, which will be available in the array text.
First you have two nice methods started there. But I would say try to avoid using puts inside them. You don't need it anyway. A method will always yield the last statement evaluated. something = text would get the job done. And I don't need to answer now about the differences.
Your first two replies are very good there. But you may want to try something like this
j = *[] #=> nil in 1.8 but [] in 1.9
It's been the new kid on the block for a time now. Guess what it does?

How does "(1..4).inject(&:+)" work in Ruby

I find this code in Ruby to be pretty intriguing
(1..4).inject(&:+)
Ok, I know what inject does, and I know this code is basically equivalent to
(1..4).inject(0) {|a,n| a + n}
but how exactly does it work?
Why &:+ is the same as writing the block {|a,n| a + n}?
Why it doesn't need an initial value? I'm ok with the inicial value being 0, but (1..4).inject(&:*) also works, and there the initial value must be 1...
From Ruby documentation:
If you specify a symbol instead, then each element in the collection will be passed to the named method of memo
So, specifying a symbol is equivalent to passing the following block:
{|memo, a| memo.send(sym, a)}
If you do not explicitly specify an initial value for memo, then uses the first element of collection is used as the initial value of memo.
So, there is no magic, Ruby simply takes the first element as the initial value and starts injecting from the second element. You can check it by writing [].inject(:+): it returns nil as opposed to [].inject(0, :+) which returns 0.
Edit: I didn't notice the ampersand. You don't need it, inject will work with a symbol. But if you do write it, the symbol is converted to block, it can be useful with other methods

Resources