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"
Related
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
This question already has answers here:
Ruby local variable is undefined
(3 answers)
Closed 7 years ago.
I'm getting a NameError, which supposedly indicates that my array "pentagonals" is undefined. But I'm a monkey's uncle if I didn't define it in line 1. What am I forgetting/misunderstanding? The goal is to write a method that tells me whether a given number is pentagonal.
pentagonals = []
def pent?(num)
pentagonals.include?(num)
end
(1..1000).each {|i|
pentagonals << (i * (3 * i - 1) / 2)
}
puts pent?(1)
Global variables in Ruby are distinguished from every other program name (like regular variables, class names, methods names, module names, etc.) by an initial $, so you should change your program in this way:
$pentagonals = []
def pent?(num)
$pentagonals.include?(num)
end
(1..1000).each {|i|
$pentagonals << (i * (3 * i - 1) / 2)
}
puts pent?(1)
Note that global variables should be used sparingly, in fact they are dangerous because they can be written to from anywhere in your program.
Variables in a method are local to the method scope, unless they are passed in as parameters.
The method can also access global, class variables and other methods in the same scope.
class MyClass
# calling methods in the same class
def method1
method2
end
def method2
puts 'method2 was called'
end
# getter / setter method pair for class variables
# #class_variable is only accessible within this class
def print_class_variable
puts #class_variable
end
def set_class_variable(param)
#class_variable = param
end
# global variables can be accessed from anywhere
def print_global_var
puts $global_variable
end
def self.some_class_method
# cannot be directly accessed by the instance methods above
end
end
Note that global variables are not recommended as they can easily result in conflicts and ambiguity.
class Dog
$name = "Dog"
end
class Cat
$name = "Cat"
end
puts $name
# which one does $name refer to?
I'm currently learning Ruby and hit some behavior I don't quite understand. In the example code below, I have two until loops, each in their own method. When executing until_test it outputs an loop of 10 20 10 20 10 20 forever but when executing second_until_test it behaves as I expect it to, outputing only 10 20. It seems that for some reason the way the code is now, I'm not able to change variables passed as parameters. I know the answer to this is likely very simple but I've failed to figure it out or find the answer on here after searching a while. What is the proper way to pass parameters successfully as I'm trying to do in until_test?
Thanks in advance!
def until_test
num = 10
until num == 20
do_this(num)
end
end
def second_until_test
num = 10
until num == 20
puts num
num = 20
puts num
end
end
def do_this(variable)
puts variable
variable = 20
puts variable
end
Your problem here is namespace... In second_until_test, num is valid for the method, so it will be changed inside the untill loop.
In until_test, you are passing num as an argument to another method, which will not directly change the passed object, unless you assert num to the return value of the method:
def until_test
num = 10
until num == 20
num = do_this(num)
end
end
def do_this(variable)
puts 20
variable = 20
# this method has to return the value for num
variable # or return variable, or return 20...
end
TLDR: until_test's num does not change value, that's why it will loop forever.
I fixed you problem here:
def until_test
num = 10
until num == 20
num = do_this(num)
end
end
def second_until_test
num = 10
until num == 20
puts num
num = 20
puts num
end
end
def do_this(variable)
puts variable
variable = 20
puts variable
return variable
end
until_test
The other answers are correct. The reason you get a forever loop in until_test is because your do_this method doesn't return your modified variable. Calling puts doesn't return the value of the parameter passed, but nil, so that means, what you are assigning num is nil rather than the desired modified output :)
Anyways, just sharing an another way to kill the cat :)
In ruby, there is something that you call an instance variable. Any modification to the variable that is made within your script, be it other methods, will change the value of the variable. It can be simply declared by prepending an # to the variable. You can also use $ to make it global. Thanks Eric for pointing that out hahaha...
Implementing this on your code will look like this:
#num
def until_test
#num = 10
until #num == 20
do_this(#num)
end
end
def second_until_test
#num = 10
until #num == 20
puts #num
#num = 20
puts #num
end
end
def do_this(variable)
puts variable
variable = 20
#num = variable
puts variable
end
The selected answer is the best one, although Jason gives an alternative technique.
To clarify on Jason's answer, an instance variable is accessible to all the methods defined in an object's method, including nested methods.
class Dog
def dog_things
#dog = "Rex"
def bark
puts "#{#dog} goes 'bark, bark'"
end
bark
end
end
Dog.new.dog_things
=> "Rex goes 'bark, bark'"
And in Ruby, even if you haven't defined any classes or objects, you are nonetheless, always in an object. It's called main and it's an object of the Object class.
puts self
=> main
puts self.class
=> Object
The difference between instance and global variables is that if you define a class, the instance variable you set before creating the class is not available in the class (although it can have another instance variable with the same name).
Global variables, however, once defined, are accessible everywhere, in all classes and outside classes.
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 want to implement something like a sandbox which can
eval given string
execute given block in the same context with eval
return the result of block
The aim of the sandbox is to inspect the contents - functions, variables, e.t.c. - of vulnerable codes.
Here is my spec
it 'returns return value of given block' do
value = Sandbox.secure_eval('hoge = ["hoge", "fuga"]') do
hoge[0]
end
expect(value).to eq('hoge')
end
and, this is my implementation of sandbox
require 'timeout'
module Sandbox
def self.secure_eval(code, timeout: 5, safe_level: 2)
raise ArgumentError, 'please set call back by block' unless block_given?
proc = Proc.new do
Timeout::timeout timeout do
$SAFE = safe_level
eval code do
yield
end
end
end
proc.call
end
end
But #secure_eval returns the result of eval, in this case ["hoge", "fuga"], and cannot capture the return value of the block.
How can I make it?
You can return the result of eval to the block using yield. You just had to yield the value; thus I changed your yield to yield eval code. In the block you give to Sandbox.secure_eval you have to then bind this result to a block variable. The result of secure_eval will be the result of the block, like you wanted.
proc = Proc.new do
Timeout::timeout timeout do
$SAFE = safe_level
yield eval code # <= This line changed
end
end
Sandbox.secure_eval('hoge = ["hoge", "fuga"]') { |hoge| hoge[0] }
# => "hoge"
Sandbox.secure_eval('2 ** 4') { |result| result - 5 }
# => 11
In response to your comment; it turns out that with the aid of Kernel#Binding, we can get it to work more or less like you wanted. It feels rather like a hack so use it with caution.
I use the Binding to evaluate the code, which will have access to all defined variables. In addition, I define a method_missing for the Binding class so we can access the variables more easily. Without it, you would need to do eval('varname') rather than just varname. Per the comment of #hakcho who mentioned the monkey-patch solution that was in place is not ideal, I now use refinements which only locally changes the behavior of Binding (i.e., the method_missing implementation).
I have added an explicit block parameter to your method, which I use with instance_eval instead of yield. We can then access the variables directly in the block.
require 'timeout'
module Sandbox
refine Binding do
def method_missing(meth, *args, &block)
self.eval(meth.to_s)
end
end
def self.secure_eval(code, timeout: 5, safe_level: 2, &block)
raise ArgumentError, 'please set call back by block' unless block_given?
proc = Proc.new do
Timeout::timeout timeout do
$SAFE = safe_level
binding = binding()
binding.eval(code)
binding.instance_eval(&block)
end
end
proc.call
end
end
using Sandbox # Activate the refinement so we can use x, y, z directly
Sandbox.secure_eval('x = [1,2,3]; y = 0; z = { key: "Hello!" }') do
x[1] # => 2
y # => 0
z[:key] # => "Hello!"
end