What does "[]" represent in the following context? - ruby

Could someone please explain the difference between:
&.[](:key)
.try(:[],:key)
.try(:key)
Specially what the "[]" represents in the first and second?

.[] is a method on the Hash object.
x.[](:key) calls the [] method with the :key argument and is equivalent to x[:key]
& is the safe navigation operator. x&.[](:key) would return nil if x is nil while x.[](:key) would cause an error.
x = {key: 123}
x[:key]
# => 123
x.[](:key)
# => 123
x = nil
x.[](:key)
# NoMethodError: undefined method `[]' for nil:NilClass
x&.[](:key)
# => nil
As far as the differences go, I don't believe there are any between the first and second, however x.try(:key) would try to call a key method on x and that would error out because it doesn't exist.
x.try(:[], :key) calls the .[] method with :key as an argument and is equivalent to what we saw above. As with the safe navigation operator, try returns nil if x is nil.

For better understanding, [] is a special method similar to fetch on Hash:
hash = {a: 1}
hash.[](:a) # => 1
hash.fetch(:a) # => 1
try is from Rails, could be used with syntax Hash#try(:method, argument):
hash.try(:[], :a) # => 1
hash.try(:fetch, :a) # => 1

Related

Handling a method with an optional argument followed by a list of keyword arguments

I want to pass an optional argument (any datatype, even a Hash) followed by a list of keyword arguments (which can be empty) to a method.
This is what I got:
def my_method(subject = nil, **options)
[subject, options]
end
The method output is as expected in the following cases:
nothing
my_method()
# => [nil, {}]
a subject
my_method('foo')
# => ["foo", {}]
a literal subject with options
my_method('foo', bar: 'bar')
# => ["foo", {:bar=>"bar"}]
a Hash as subject with options
my_method({foo: 'foo'}, bar: 'bar')
# => [{:foo=>"foo"}, {:bar=>"bar"}]
no subject, only options
my_method(bar: 'bar')
# => [nil, {:bar=>"bar"}]
When passing a Hash as subject and no options, the desired outcome is:
my_method({foo: 'foo'})
# => [{:foo=>"foo"}, {}]
But I get the following; I don't get the correct subject:
my_method({foo: 'foo'})
# => [nil, {:foo=>"foo"}]
Is my_method(foo: 'foo') equivalent to my_method({foo: 'foo'})? Any ideas on how I could get the desired outcome?
See, you have **options as an argument which do not have any default value & first argument have default value. So understand following single argument case,
Whenever single argument is passed it is tried to assign to second argument (as first one is holding default nil) & if it fails due to type mismatch then it assign to first argument. That's how my_method(4) works.
Now Suppose you have single argument passed as hash, which match to assign to 2nd argument then of course it get assigned to second argument & first is set default nil.
If you want to make it work, then you can do following,
> my_method({sd: 4}, {})
=> [{:sd=>4}, {}]
Or you can provide argument name while passing,
> my_method(subject: {sd: 4})
=> [{:sd=>4}, {}]
As far as I know this is a limitation of how Ruby passes arguments. It can't tell the difference between my_method({foo: 'foo'}) and my_method(foo: 'foo')
Why not hack around it with this?
if subject.is_a?(Hash) && options.empty?
subject, options = nil, subject
end
This assumes that subject shouldn't be a hash though.
Maybe not the best answer but you can try with keyword argument :
def my_method(subject: nil, **options)
[ subject, options ]
end
Then
my_method(subject: {foo: 'foo'})
=> [{:foo=>"foo"}, {}]
Alternative :
You can use
my_method({foo: 'foo'}, {})

Double splat on `nil`

My understanding is that a single splat on a non-array object calls to_a and then dissociates the elements apart. And since nil.to_a is defined to be [], the following conversion happens:
[:foo, *nil, :bar]
# => [:foo, *nil.to_a, :bar]
# => [:foo, *[], :bar]
# => [:foo, :bar]
By analogy, I thought that a double splat on a non-hash object calls to_h and then dissociates the key-value pairs apart. And since nil.to_h is defined to be {}, I expected the following conversion to happen:
{"foo" => 1, **nil, "bar" => 2}
# => {"foo" => 1, **nil.to_h, "bar" => 2}
# => {"foo" => 1, **{}, "bar" => 2}
# => {"foo" => 1, "bar" => 2}
But actually, it raises an error: no implicit conversion of nil into Hash. Why does it behave like that?
Edit I am not asking about the reasoning behind the design. I am asking where my thinking is wrong regarding double splat.
Well it's our human being super power to recognize patterns and predict things. However it's not always true. This is one example. Ruby is not consistent in splat and double splat. Your way of thinking is a good way to "remember" but it's not exactly the way Ruby works on splats.
See this bug report for more detail. In this bug report, Ruby's author Matz rather to remove the feature of being able to splat nil than add double splat to nil.
The reason *nil works is because the splat operator works on anything that responds to to_a, and nil.to_a returns []. The reason **nil doesn't work is that nil doesn't respond to to_hash, which is to_a's double-splat counterpart.
If you wanted this behavior, you could monkey-patch NilClass:
class NilClass
def to_hash
{}
end
end
{ "foo" => 1, **nil, "bar" => 2 }
# => { "foo" => 1, "bar" => 2 }

Fetch key values directly from a custom hash format

I have a hash as shown below:
hash_ex = {:"p, q," => "1, 2,"}
And hash_ex[:p] returns nil:
hash_ex[:p]
# => nil
Instead, it should be 1. How would I be able to get this key value?
There is no defined key :p in hash_ex. Therefore nil is expected result.
hash_ex = {:"p, q,"=>"1, 2,"}
hash_ex.keys
# => [:"p, q,"]
hash_ex[:"p, q,"]
# => "1, 2,"
The above example shows that you have declared "p, q," as a key. Also "p, q," here is being treated as a Symbol and not a String. Therefore hash_ex["p, q,"] will also return nil.
But hash_ex[:p] returns nil instead it should be 1
Ruby is not an AI, it can't figure out how to parse your "custom Hash format".
How I would be able to get this key value?
Well, you have to convert your format into a structure that Ruby understands:
key_string = "p, q,"
value_string = "1, 2,"
keys = key_string.split(/,\s*/).map(&:to_sym) #=> [:p, :q]
values = value_string.split(/,\s*/).map(&:to_i) #=> [1, 2]
hash_ex = keys.zip(values).to_h
hash_ex[:p] #=> 1

Hash inspection in IRB?

I have a hash:
hash = { test: 'Test' }
If I am in an irb session and I enter hash, it outputs the content of the hash:
{
:test => 'Test'
}
What method is being invoked on the variable hash when I do that?
The method is Hash#inspect.
hash = { test: 'Test' }
# => {:test=>"Test"}
hash.inspect
# => "{:test=>\"Test\"}"
Object.inspect
The method is usually used for printing object structure.
IRB calls #inspect method on your expressions and prints its result.
When your hash contains a lot of data, it can be painful to read its content in one line.
I like to use y that print the hash in yaml.
h = {:a => 1, :b => 2}
y h
# ---
# :b: 2
# :a: 1
# => nil
IRB will call Hash#inspect.
hash.inspect

Add nil object as an element of an array in Ruby

Can I have an array which has nil as a value in it?, e.g., [1, 3, nil, 23].
I have an array in which I assign nil like this array = nil, and then I want to iterate through it but I can't. The .each method fails saying nil class.
Is it possible to do this?
Use:
a = [nil]
Example:
> a = nil
=> nil
> a.each{|x|puts x}
NoMethodError: undefined method `each' for nil:NilClass
from (irb):3
from :0
> a= [nil]
=> [nil]
> a.each{|x|puts x}
nil
=> [nil]
I believe your problem lies in when you "assign nil" to the array
arr = []
arr = nil
Is this something like what you tried doing? In this case you do not assign nil to the array you assign nil to the variable arr, hence arr is now nil giving you errors concerning a "nil class"
Sure you can. You are probably trying to do something with the nil object without checking to see if it's nil? first.
C:\work>irb
irb(main):001:0> a = [1,2,nil,3]
=> [1, 2, nil, 3]
irb(main):003:0> a.each{|i|
irb(main):004:1* if i.nil? then
irb(main):005:2* puts ">NADA>"
irb(main):006:2> else
irb(main):007:2* puts i
irb(main):008:2> end
irb(main):009:1> }
1
2
>NADA>
3
=> [1, 2, nil, 3]
irb(main):010:0>
I think you're confusing adding an item to an array with assigning a value of nil to a variable.
Add an item to (the end of) an array (two ways):
array.push(item)
# or if you prefer
array << item
# works great with nil, too
array << nil
I'm assuming that the array already exists. If it doesn't, you can create it with array = [] or array = Array.new.
On the other hand, array = nil assigns nil to a variable that happens to be (misleadingly) named 'array'. If that variable previously pointed to an array, that connection is now broken.
You may be thinking of assignment with an index position, but array[4] = nil is very different from array = nil.
There is a problem with how you're storing the nil value in the array. Here is an example to show how arrays work:
a = nil
b = [nil]
c = []
c.push(nil)
# a is nil
# b is [nil]
# c is now [nil]
So, 'b' and 'c' both created an array with a nil attribute while 'a' just set a variable to nil (NOT an array).
With either 'b' or 'c', you should now be able to run .each without any problem
use Enumerable#map
ruby-1.8.7-p249 > [1,2,nil,4].map{|item| puts item}
1
2
nil
4
=> [nil, nil, nil, nil]
note that even though the return is nil for each item in the array, the original array is as it was. if you do something to operate on each item in the array, it will return the value of each operation. you can get rid of nils buy compacting it.
ruby-1.8.7-p249 > [1,2,nil,4].map{|item| item + 1 unless item.nil? }
=> [2, 3, nil, 5]
ruby-1.8.7-p249 > [1,2,nil,4].map{|item| item + 1 unless item.nil? }.compact
=> [2, 3, 5]

Resources