What does shadowing mean in Ruby?
Shadowing is when you have two different local variables with the same name. It is said that the variable defined in the inner scope "shadows" the one in the outer scope (because the outer variable is now no longer accessible as long as the inner variable is in scope, even though it would otherwise be in scope).
So in your case, you can't access the outer x
variable in your block, because you have an inner variable with the same name.
Behaviours of a Ruby local variable shadowing an instance method
What looks like inconsistent return values for name
during runtime and while debugging doesn't seem to related to Pry, but more about binding
itself encapsulating the entire execution context of a method, versus the progressive change in what shadowed variables reference at runtime. To build on the example method with some more debugging code:
def say_name
puts "--- Before assignment of name: ---"
puts "defined?(name) : #{defined?(name).inspect}"
puts "binding.local_variable_defined?(:name) : #{binding.local_variable_defined?(:name).inspect}"
puts "local_variables : #{local_variables.inspect}"
puts "binding.local_variables : #{binding.local_variables.inspect}"
puts "name : #{name.inspect}"
puts "binding.eval('name') : #{binding.eval('name').inspect}"
if name.nil?
name = "Unknown"
end
puts "--- After assignment of name: ---"
puts "defined?(name) : #{defined?(name).inspect}"
puts "binding.local_variable_defined?(:name) : #{binding.local_variable_defined?(:name).inspect}"
puts "local_variables : #{local_variables.inspect}"
puts "binding.local_variables : #{binding.local_variables.inspect}"
puts "name : #{name.inspect}"
puts "binding.eval('name') : #{binding.eval('name').inspect}"
puts "My name is #{name.inspect}"
end
Now, running Person.new("Paul").say_name
outputs:
--- Before assignment of name: ---
defined?(name) : "method"
binding.local_variable_defined?(:name) : true
local_variables : [:name]
binding.local_variables : [:name]
name : "Paul"
binding.eval('name') : nil
--- After assignment of name: ---
defined?(name) : "local-variable"
binding.local_variable_defined?(:name) : true
local_variables : [:name]
binding.local_variables : [:name]
name : nil
binding.eval('name') : nil
My name is nil
which shows that binding
never references the method call of name
and only ever the eventually-assigned name
variable.
What is Shadowing outer local variable in rubocop and how do I fix this?
This means that the user
provided as a block arguments will overwrite the user
variable defined here user = User.where(email: auth.info.email).first
To overcome it you'll need to change the name of one of the variables.
Either something like:
result = User.where...
return result if result
Or:
where(provider: auth.provider, uid: auth.uid).first_or_create do |u|
u.fullname = auth.info.name
...
end
Some more info: https://github.com/bbatsov/ruby-style-guide#no-shadowing
What is Shadowing outer local variable in rubocop and how do I fix this?
This means that the user
provided as a block arguments will overwrite the user
variable defined here user = User.where(email: auth.info.email).first
To overcome it you'll need to change the name of one of the variables.
Either something like:
result = User.where...
return result if result
Or:
where(provider: auth.provider, uid: auth.uid).first_or_create do |u|
u.fullname = auth.info.name
...
end
Some more info: https://github.com/bbatsov/ruby-style-guide#no-shadowing
Ruby way of avoiding shadowing for finds
I would prefer making the variable on the left hand side more descriptive:
blue_box = boxes.find { |box| box.color == BLUE }
Even something more generic like found_foo
I would consider to be more descriptive.
Shadowing a top-level constant within a binding
ERB#result
takes an optional binding:
require 'erb'
class Foo
ENV = { 'RUBY_VERSION' => '1.2.3' }
def get_binding
binding
end
end
template = "Ruby version: <%= ENV['RUBY_VERSION'] %>"
ERB.new(template).result
#=> "Ruby version: 2.1.3"
b = Foo.new.get_binding
ERB.new(template).result b
#=> "Ruby version: 1.2.3"
Why doesn't assignment to variable y work in a Ruby block?
The source of confusion here is so-called shadowing. You have two local variables called x
here, but only one called y
. The second x
variable is shadowing the first one - meaning that any attempt to access or assign to the first variable is doomed to fail.
When you do ary.each do |x|
, you are creating new variable, which has nothing (except for the name) to do with the variable x
you have created in the outer scope.
y
on the other hand is not shadowed and the loop binding is accessing (and assigning to) the variable defined in the parent scope. Unfortunately there is no way (at least that I am aware of) of explicit local variable creation like var
/let
/const
in javascript.
In short, your code executes as follow:
x = 1
y = 1
ary = [1, 2, 3]
ary.each do |x2|
y = x2
end
p [x, y]
So in fact, assignment to y works just as expected. You assign it value of 1 before the block, and then you assign 3 more times within the block. The last assignment is y=3
so that's its final value.
BEWARE, THE HACK:
Well there actually is a way of forcing the local variable creation, and it is to add an extra yielding argument to each block. This should never be done in the actual code, and I only present it for completeness. You should actually avoid any variable shadowing whenever possible.
x = y = 1
ary = [2,3,4]
ary.each do |x, y| # this creates new local variables x and y, y is always set to nil for each iteration.
y = x # y here is a shadowing variable, not the y of the parent scope
puts [x,y].inspect
end
puts [x,y].inspect
# OUTPUTS:
[2, 2]
[3, 3]
[4, 4]
[1, 1]
Avoid temporary variables by using name shadowing
This style is very common:
let nums = another
. bunch
. of_
. functions
. a
. bunch
. of_
. functions
$ [1..10]
It clearly delineates the code; while .
serves the place of the temporary variable name.
And it avoids the dangerous issues that can happen when you start shadowing variable names -- accidentally referring to the wrong x
will get you into trouble sooner or later.
Related Topics
How to Create Wizard Forms in Ruby on Rails
How to Disable Db:Schema:Dump for Migrations
Xpath Expression for Regex-Like Matching
Windows/Ruby/Rails Install --- .Cannot Load Such File -- SQLite3/Sqlite3_Native Windows
How to Declare a String with Both Single and Double Quotes in Yaml
How to Escape #{ from String Interpolation
Uninitialized Constant "Controller Name"
Remove Duplicate Elements from Array in Ruby
What Is Recursion and How Does It Work
How to Robustly Parse Malformed CSV
Elasticsearch & Tire: Using Mapping and To_Indexed_JSON
Rails 4 Nested Attributes Not Saving
In Ruby Why Does Nil.Id Return 4
Why Does Ruby String#Split Not Treat Consecutive Trailing Delimiters as Separate Entities
Why Does Ruby's String#To_I Sometimes Return 0 When the String Contains a Number