Has #puts created a new hash? - ruby

While trying to answer another member's question, I happened across this strange behaviour:
puts :some_object => 'another_object'
Surprisingly, the output is this:
{:some_object=>"another_object"}
What is this new devilry? It looks as though I've created a hash using #puts, and without using the normal curly-bracket syntax.
I can't test this theory though, because this just generates an error:
puts (:some_object => 'another_object').class
# => syntax error, unexpected =>, expecting ')'
What's going on here?
Edit: Okay, thanks to bundacia's explanation, it's now easy for me to test and confirm that it's a hash (whereas I wasn't sure how to do that before):
def test(x)
puts x.class
end
test(:some_object => 'another_object')
# => Hash
Many thanks!

You are passing a hash to puts. In ruby, if the last argument you're passing to a function is a hash the curly braces are optional. So your example is equivalent to:
puts( {:some_object => 'another_object'} )

Related

ruby method arguments writen after method name and space leads to error [duplicate]

I get a syntax error with this code
render json: {
"what" => "created",
"whatCreated" => "thing",
"htmlOutput" => render_to_string (partial: "some_partial")
}
But with this code I don't:
render json: {
"what" => "created",
"whatCreated" => "thing",
"htmlOutput" => render_to_string(partial: "some_partial")
}
How come that space after render_to_string breaks my rails app?
the thing is, that method in ruby can be run with or without parentheses.
for example, you can run Array.new 1,2 and ruby knows that it receives the arguments after the space. and you can also run Array.new(1,2) and ruby knows the args are inside the parentheses.
but, when you run Array.new (1,2) , ruby thinks it will receive arguments after the space but actually it receives a tuple (1,2), and basicaly its exactly the same as Array.new((1,2))
so bottom line:
Array.new (1,2) == Array.new((1,2)) and thats a syntax error because (1, 2) literal is not a valid one
As a general Ruby style guide you should not put a space before the parameter list parentheses. this is not related to rails, but the Ruby language. try the followings to see:
Array.new(1,2) # => [2]
Array.new (1,2) # = > SyntaxError: unexpected ',', expecting ')'
Array.new(1) # => [nil]
Array.new (1) # => [nil]
As you can see in the second example the code broke, the interpreter was expecting to find a ) but found ,. However in the last example it didn't break.

`File.enum_for(:readlines, ...)` not enumerating

Why does this enumerator unexpectedly return an empty array:
> File.enum_for(:readlines, '/usr/share/dict/words').take(1)
=> []
while this one returns properly:
File.enum_for(:readlines, "/usr/share/dict/words").each{}.take(1)
=> ["A\n"]
For comparison, other enumerators will work without the each:
> "abc".enum_for(:each_byte).take(1)
=> [97]
What's really odd in the File.readlines case is that the body of the each doesn't actually get executed:
File.enum_for(:readlines, "/usr/share/dict/words").
each{|e| p e; raise "stop" }.take(2)
=> ["A\n", "a\n"]
This is on ruby 2.5.3. I tried the code both in pry and in a ruby -e one-liner, with the same results.
Apparently enum_for/to_enum only works with a method that yields. Credit goes to #Anthony for making me realize this.
This is the equivalent of what I was trying to do:
# whoops, `to_a` doesn't yield
[1,2,3].enum_for(:to_a).take(1)
=> []
# Works, but `enum_for` isn't really meant for this, and it's very possible this should be
# considered undefined behavior.
# In this case, as in `File.readlines`, the `each` block isn't really executed.
> [1,2,3].enum_for(:to_a).each{}.take(1)
=> [1]
Another interesting thing is that calling each{} on one of these "weird enumerators" seems to act as though the enum-ed method (e.g. to_a) were called directly. But of course this is pointless.
> arr = [1,2,3]
> arr.object_id
=> 70226978129700
> arr.to_a.object_id
=> 70226978129700
# same as calling `to_a` - doesn't create a new array!
> arr.enum_for(:to_a).each{}.object_id
=> 70226978129700
In the case of File.readlines, its implementation simply reads in the lines and returns them in a single array; it doesn't yield anything.

Ruby - escape a number leading variable name

I'm using create() to insert into a table that already exists, but some of the table fields and variable names start with a number. Currently in Ruby I'm getting a syntax error "unexpected tIDENTIFIER" when I try to do something like the following-
foo.each do |x|
Object.create(
3pm: x.3pm,
3pa: x.3pa
)
end
If I change it to '3pm' => x.3pm, it gives me a syntax error on the x.3pm portion.
How could I escape this to get it functioning?
The problem is that 3pm is not a valid identifier in Ruby. In Ruby, symbol literals declared with the :name syntax and method names must both be valid identifiers.
In the case of the keys, you can get around this by either using strings instead, as you discovered, or by using the :'name' syntax for symbol literals:
foo.each do |x|
Object.create(
:'3pm' => x.3pm,
:'3pa' => x.3pa
)
end
Unfortunately, this still leaves the problem of 3pm and 3pa not being valid method names. Normally, since those are not valid methods names, they couldn't even be methods on x in the first place. In this case though, x is likely using the either the define_method or method_missing features of Ruby to create or simulate the existance of a method named 3pm (even though that's normally not a valid method name in Ruby).
Thankfully, we can get around this by using Object#public_send to call the method:
foo.each do |x|
Object.create(
:'3pm' => x.public_send(:'3pm'),
:'3pa' => x.public_send(:'3pa')
)
end
That should resolve your problem.

Explain hash rocket in this context

I've just written this code, that works although I am not entirely sure why:
scope = scope.where(Sequel.qualify(:meeting_graphs, :id) => query.ids)
I am specifically talking about the hash rocket.
Previously the code was this, which makes perfect sense:
scope = scope.where(id: query.ids)
First thing I do not understand is why it does this not work when I replace the hash rocket with a colon which I thought was the preferred syntax:
scope = scope.where(Sequel.qualify(:meeting_graphs, :id): query.ids)
Sequel.qualify returns an object which also confuses me as I thought it would return a symbol.
Can anyone explain?
New hash syntax works only if key is a literal symbol.
Sequel.qualify returns qualifier object identifying column. It's possible since every object can be a hash key in Ruby.
that works although I am not entirely sure why
As long as Sequel.qualify(:meeting_graphs, :id) is valid, it can be a key of a hash. Any object can be a key of a hash. That is why.
why it does this not work when I replace the hash rocket with a colon
Even if Sequel.qualify(:meeting_graphs, :id) turns out to be a symbol, the colon notation will not work because it is part of a literal notation. It is not a method or a keyword that works on Ruby objects that are already made.
You are passing a keywords to the function and keywords use Hash syntax.
There are many ways to define hashes in ruby, and in the way you use a function one syntax does not work.
def return_one_symbol
'one'.to_sym
end
hash_syntax1 = {:one => '1'}
hash_syntax2 = {one: '1'}
p hash_syntax1 # => {:one=>"1"}
p hash_syntax2 # => {:one=>"1"}
hash_syntax1_function = {return_one_symbol => '1'}
hash_syntax2_function = {return_one_symbol: '1'}
p hash_syntax1_function # => {:one=>"1"}
p hash_syntax2_function # => {:return_one_symbol=>"1"}
see this post for more info:
Is there any difference between the `:key => "value"` and `key: "value"` hash notations?

Passing Hash values as parameters to methods in Ruby

I have a method met1 that takes hash values as parameters.
For example: met1('abc' => 'xyz')
What should be the syntax when I define the method? Can it be something like this?
def met1(options)
puts options
end
I know the above syntax works. But how can I access the individual hash key and value inside the met1? (where key is abc and value is xyz?) Thank you!
Thats easy
met1("abc" => "xyz")
def met1(options)
puts options
# with key
puts options["abc"]
end
I assume you know what the options might contain in terms of keys right? if not,
def met1(options)
puts options.keys # options is the hash you passed it, use it like one
end
Your syntax is correct. simply use options['key'] (in case 'key' is a string) in your method. It's customary to use symbols as keys, so in your example:
met1(:abc => 'xyz')
def met1(options)
puts options[:abc]
end

Resources