I'm not entirely sure how to Google this question, so I come here with an example. I'm looking at a selenium script that has an excerpt like the following:
def setup
#starting_url = "https://www.example.com"
#restricted_url = "https://www.example.com/restricted"
#user_email = "foo#bar.com"
#user_password = "notarealpassword"
#headless_mode = false
#page_timeout = 15 # seconds
#log_file = 'log/development.log'
#lineup_file = 'data/lineup1.csv'
... more code
end
My question is, why does every variable here get prefixed with an # symbol? This method is not part of a class. It is being written in the global scope. I understand the variables have significance in with an # symbol in the case of an explicit class, but what about here?
Those variables become instance variables in the scope of the main object. They will be available inside other methods also defined at the global scope. Local variables defined within methods at the global scope would go out of scope as soon as the method returned.
Illustration:
def foo
lvar = 1
#ivar = 2
end
def bar
puts #ivar # will print 2 after foo() is called
puts lvar # will throw NameError
end
#ivar # => nil
foo # initializes #ivar
#ivar # => 2
bar # prints 2, throws NameError
lvar # throws NameError
Related
I'm struggling with variable scope in ruby. I was under the impression that local variables were accessible by methods below them. Looking at the following code however I'm getting an undefined variable error.
a = Array.new
def testing()
a.push("test")
end
testing
Using global variables it works just fine, how can I avoid using global variables?
There isn't much to say here except that local variables in Ruby are only accessible in the scope in which they are defined and any blocks (closures) defined in that scope that capture them. Since in Ruby, unlike some other dynamic languages, method are not closures, they do not capture local variables.
If you tried, say, this:
irb(main):001:0> a = 3
=> 3
irb(main):002:0> define_method(:testing) do
irb(main):003:1* puts a
irb(main):004:1> end
=> :testing
irb(main):005:0> testing
3
It works, since the code is in a block instead of a method.
Defining a method in the top-level can be quite confusing. Let's wrap your code in a class instead:
class Foo
a = []
def testing
a << 'test'
end
end
(I've shortened Array.new to [] and a.push(...) to a << ...)
Foo#testing can be called via:
foo = Foo.new
foo.testing
#=> undefined local variable or method `a'
Apparently, this doesn't work. The first a is a local variable in the scope of the class body, whereas the second a is a local variable within an instance method.
Moving the variable initialization out of the class body into the initialize method doesn't work either, because local variables are not shared across methods:
class Foo
def initialize
a = [] # <- one 'a'
end
def testing
a << 'test' # <- another 'a'
end
end
To get this working, you have to use an instance variable:
class Foo
def initialize
#a = []
end
def testing
#a << 'test'
end
end
foo = Foo.new
foo.testing
#=> ["test"]
foo.testing
#=> ["test", "test"]
You could use instance variables. Any variable whose name begins with # is an instance variable and is available anywhere in the class or method in which it is defined. For example, the variable #A defined within class B will be available to any methods in B.
2.3.3 :007 > def testing()
2.3.3 :008?> [].push("test")
2.3.3 :009?> end
=> :testing
2.3.3 :010 > testing
=> ["test"]
You can't let local variables accessible by methods below them , you can use block like the answer by #Linuxios, or use the way that it easy work.
class Foo
def self.run(n,code)
foo = self.new(n)
#env = foo.instance_eval{ binding }
#env.eval(code)
end
def initialize(n)
#n = n
end
end
Foo.run( 42, "p #n, defined? foo" )
#=> 42
#=> "local-variable"
The sample program above is intended to evaluate arbitrary code within the scope of a Foo instance. It does that, but the binding is "polluted" with the local variables from the code method. I don't want foo, n, or code to be visible to the eval'd code. The desired output is:
#=> 42
#=> nil
How can I create a binding that is (a) in the scope of the object instance, but (b) devoid of any local variables?
The reason that I am creating a binding instead of just using instance_eval(code) is that in the real usage I need to keep the binding around for later usage, to preserve the local variables created in it.
so like this? or did i miss something important here?
class Foo
attr_reader :b
def initialize(n)
#n = n
#b = binding
end
def self.run(n, code)
foo = self.new(n)
foo.b.eval(code)
end
end
Foo.run(42, "p #n, defined?(foo)")
# 42
# nil
or move it further down to have even less context
class Foo
def initialize(n)
#n = n
end
def b
#b ||= binding
end
def self.run(n, code)
foo = self.new(n)
foo.b.eval(code)
end
end
Foo.run(42, "p #n, defined?(foo), defined?(n)")
# 42
# nil
# nil
Answer:
module BlankBinding
def self.for(object)
#object = object
create
end
def self.create
#object.instance_eval{ binding }
end
end
Description:
In order to get a binding with no local variables, you must call binding in a scope without any of them. Calling a method resets the local variables, so we need to do that. However, if we do something like this:
def blank_binding_for(obj)
obj.instance_eval{ binding }
end
…the resulting binding will have an obj local variable. You can hide this fact like so:
def blank_binding_for(_)
_.instance_eval{ binding }.tap{ |b| b.eval("_=nil") }
end
…but this only removes the value of the local variable. (There is no remove_local_variable method in Ruby currently.) This is sufficient if you are going to use the binding in a place like IRB or ripl where the _ variable is set after every evaluation, and thus will run over your shadow.
However, as shown in the answer at top, there's another way to pass a value to a method, and that's through an instance variable (or class variable, or global variable). Since we are using instance_eval to shift the self to our object, any instance variables we create in order to invoke the method will not be available in the binding.
I am trying to write a class that can capture a block of code and subsequently change the variables in the scope/closure.
class BlockCapture
attr_reader :block
def initialize(&block)
#block = block
#scope_additions = {}
end
def merge_into_scope(values = {})
#scope_additions = #scope_additions.merge(values)
end
def mutated_block
# some code that merges the scope additions into the binding
# and returns the context
end
end
captured_block = BlockCapture.new do
future_variable.upcase
end
captured_block.block.call # This should trigger a NameError, as `future_variable` is not in scope.
captured_block.merge_into_scope(future_variable: "I was added to the scope")
captured_block.mutated_block.call # This should work
=> "I WAS ADDED TO THE SCOPE"
I don't know how to go about merging the variables into the scope.
I have tried calling the binding method on the block variable, but it returns a new binding each time, so it appears to be a copy of rather than a reference to the binding of the original block.
irb(main):078:0> capture.block.binding
=> #<Binding:0x007fa38292e398>
irb(main):079:0> capture.block.binding
=> #<Binding:0x007fa382925f18>
irb(main):080:0> capture.block.binding
=> #<Binding:0x007fa38291d908>
Positing as an answer, even if I am not sure it would be helpful, because there is no space in the comment. Looks like you can run eval with the specific binding, for example with the current binding, created after the lambda was defined:
bind_lambda = -> (bind, x) { bind.eval x }
#=> #<Proc:0x007fec943b85e8#(pry):1 (lambda)>
First try ends with an error, as expected:
bind_lambda.call binding, 'future_var'
NameError: undefined local variable or method `future_var' for main:Object
from (pry):2:in `__pry__'
But after declaration of the local variable:
future_var = "here be dragons"
#=> "here be dragons"
We can reach it:
bind_lambda.call binding, 'future_var'
#=> "here be dragons"
This is because binding is the current environment.
I would like to deny creating instance variables in Ruby,to prevent unattended variables being created 'by mistake'.
My class:
class Test
def initialize
#a = 'Var A'
end
def make_new
#b = 'Var B' <-- I would like to deny creation of any variables that were not defined during the init
end
end
I don't claim this is a good idea, but just b/c it's kind of interesting, here is a solution that will throw an exception when a new ivar is created, but will also let you modify defined instance variables (unlike freezing the class). Just threw this together, there are undoubtably some issues w/ it, including the fact that it duplicates every method :)
module IvarBlocker
def method_added(method)
alias_name = "__#{method}_orig"
return if method == :initialize || method_defined?(alias_name) || method.match(/__.*_orig/)
alias_method alias_name, method
define_method(method) do |*args|
ivars_before = instance_variables.dup
send(alias_name, *args).tap { raise "New Ivar assigned" if !(instance_variables - ivars_before).empty? }
end
end
end
Usage
class Test
extend IvarBlocker
def initialize
#a = 1
end
def set_b
#b = 2
end
def set_a
#a = 6
end
end
t = Test.new #=> #<Test:0x007f87f13c41e8 #a=1>
t.set_b #=> RuntimeError: New Ivar assigned
t.set_a #=> 6
You can freeze object instances at the end of initialize method:
class Test
def initialize
#a = 'Var A'
freeze
end
def make_new
#b = 'Var B' # I would like to deny creation of any variables that were not defined during the init
end
end
t=Test.new
p t.instance_variable_get :#a
# "Var A"
t.make_new
#a.rb:24:in `make_new': can't modify frozen Test (RuntimeError)
# from a.rb:30:in `<main>'
t.instance_variable_set :#c, 'Var C'
# a.rb:31:in `instance_variable_set': can't modify frozen Test (RuntimeError)
# from a.rb:31:in `<main>'
class << t
#d = 'Var D'
end
#a.rb:33:in `singletonclass': can't modify frozen Class (RuntimeError)
# from a.rb:32:in `<main>'
p t.instance_variable_get :#d
There is a way - a hacky (but fun) way which is not meant for production (and is relatively slow). My sample implementation works for a single object only, but can be extended to support many objects.
Let's assume the following setup:
class Foo
def initialize
#a = :foo
end
def set_b; #b = 3; end
def set_c; #c = 7; end
end
def freeze_variables_of(obj)
frozen_variables = obj.instance_variables
set_trace_func lambda {|event, file, line, id, binding, classname|
if classname == obj.class
this = binding.eval 'self'
if this == obj
(this.instance_variables - frozen_variables).each {|var| this.remove_instance_variable var}
end
end
}
end
With the use of set_trace_func we can set a Proc which is called very often (usually more than once per statement). In that Proc we can check instance variables and remove unwanted variables.
Let's look at an example:
a = Foo.new
# => #<Foo:0x007f6f9db75cc8 #a=:foo>
a.set_b; a
# => #<Foo:0x007f6f9db75cc8 #a=:foo, #b=3>
freeze_variables_of a
a.set_c; a
# => #<Foo:0x007f6f9db75cc8 #a=:foo, #b=3>
We see that after doing the "freeze", set_c cannot set the instance variable (in fact the variable is removed at the very moment the set_c method returns).
In contrast to freezing the object (with a.freeze) (which I'd recommend for any real world application), this way allows you to modify all allowed instance variables while forbidding new ones.
This even works, if you directly assign instance variables (while Alex' method is probably faster, but relies on accessor methods).
There is no way to prevent creation of accidental instance variables defined that way. Why do you want to do this? What would you want such code to achieve?
How to pass an instance variable as an argument to a block?
This doesn't work in Ruby 1.9 (formal argument cannot be an instance variable).
awesome do |foo, #bar|
puts 'yay'
end
Ruby 1.9 makes block parameters local to the block. This also means that block parameters can no longer be global or instance variables. You can use closures for your purposes:
#foo = 10
1.times do |i|
puts #foo
end
# => "10"
UPDATE
Instead of using instance variables, local variable can help you:
foo = 10
1.times do |i|
puts foo
end
# => "10"
In such cases, you won't get problems related to code execution inside different context.
There are 3 cases here which could be relevant:
Instance variable of the definer of the block that references the instance variable (in the example the usage of awesome2.
Instance variable of the definer of the block that gives the instance variable as argument to the receiver (no chance to give it as argument to the block). This is usage of awesome.
Instance variable of the user of the block that gives the instance variable as argument to the block. This is usage of awesome3.
So lets try to implement both examples to see them. The example is lengthy, the relevant lines are:
awesome gets the instance variable as argument, which is then used by the receiver in the call of the block
awesome2 gets no instance variable, it is bound by Sender#use_in_block. No chance for the receiver to change that binding.
awesome3 gets the instance variable of the sender, but uses its own instance variable for calling the block
So depending on what you want to reach, one of the three is the better solution.
class Sender
attr_accessor :send_var
def call_a_block(var)
rec = Receiver.new(5)
#my_var = var
res = rec.awesome(#my_var) do |arg1|
arg1 + 3
end
res2 = rec.awesome3(#my_var) do |arg1|
arg1 + 3
end
p "Result of calling awesome with: 3 and #{#my_var} is #{res}"
p "Result of calling awesome3 with: 3 and #{#my_var} is #{res2}"
end
def use_in_block(var)
rec = Receiver.new(6)
#my_var = var
res = rec.awesome2 do
4 + #my_var
end
p "Result of calling awesome2 with: 4 and #{#my_var} is #{res}"
end
end
class Receiver
attr_accessor :rec_var
def initialize(var)
#rec_var = var
end
def awesome(arg1)
res = yield(arg1)
res * 2
end
def awesome3(arg1)
res = yield(#rec_var)
res * 2
end
def awesome2
res = yield
res * 2
end
end
s = Sender.new
s.call_a_block(7)
s.call_a_block(20)
s.use_in_block(7)
s.use_in_block(20)
Results are:
c:\Temp>ruby call.rb
"Result of calling awesome with: 3 and 7 is 20"
"Result of calling awesome3 with: 3 and 7 is 16"
"Result of calling awesome with: 3 and 20 is 46"
"Result of calling awesome3 with: 3 and 20 is 16"
"Result of calling awesome2 with: 4 and 7 is 22"
"Result of calling awesome2 with: 4 and 20 is 48"