Accessing variable defined inside block - ruby

I have this snippet:
class MyClass
def self.callWithBlock (&block)
print block.blockVar
end
end
MyClass::callWithBlock do
blockVar = 'Hello'
end
which gives me an error:
in `callWithBlock': undefined method `blockVar' for #<Proc:0x000000017ed168#./block-test.rb:9> (NoMethodError)
from ./block-test.rb:9:in `<main>'
How to access this blockVar?

If you add binding at the end of the block, that would become the result of call-ing the block, and you can eval whatever local variables assigned in that block within the context of the binding.
class MyClass
def self.callWithBlock (&block)
print block.call.eval('blockVar')
end
end
MyClass::callWithBlock do
blockVar = 'Hello'
binding
end
# => Hello

Related

Calling a class method with local variable as parameter

I want to pass a string in a variable from a method to another class. I have this code:
class A
def method_a
variable = "some string"
B.method_b(variable)
end
end
class B
def self.method_b(parameter)
puts parameter
end
end
This code generates the following error:
Undefined local variable or method `variable`
What am I doing wrong?
What you've defined here is an instance method, one that can only operate on an instance of B:
class B
def self.class_only(v)
puts "Class: #{v}"
end
def instance_only(v)
puts "Instance: #{v}"
end
end
The class_only method does not require an instance:
B.class_only(variable)
The instance_only method must operate on an instance:
b = B.new
b.instance_only(variable)
Now anything the B method is given via arguments is valid, and any local or instance variables on the A side are things you can supply to the call. There's no scope issues here because you're explicitly passing them over.
For example:
class A
def test
variable = SecureRandom.hex(6)
B.class_only(variable)
end
end
A.new.test

Ruby invoking attr method with array of attributes

Please explain me why i should define attr_list before attr? I can not understand why i should do that?
class Question
def self.attr_list
[:id, :name]
end
attr *self.attr_list
end
class Question
attr *self.attr_list
def self.attr_list
[:id, :name]
end
end
NoMethodError: undefined method `attr_list' for Question:Class
Unlike a def, a class is executed line by line immediately after you hit return to run your program:
class Dog
x = 10
puts x
end
--output:--
10
...
class Dog
puts x
x=10
end
--output:--
1.rb:2:in `<class:Dog>': undefined local variable or method `x'
In this line:
...
class Dog
def self.greet
puts 'hello'
end
greet
end
--output:--
hello
...
class Dog
greet
def self.greet
puts 'hello'
end
end
--output:--
1.rb:2:in `<class:Dog>': undefined local variable or method `greet'
Similarly, in this line:
attr *self.attr_list
you call self.attr_list(), yet the def comes after that line, so the method doesn't exist yet.
With a def, you can write this:
def do_math()
10/0
end
and you won't get an error until you call the method.
But by the time you create an instance of a class, all the code inside the class has already executed, creating the methods and constants that are defined inside the class.
By the way, you don't ever need to use attr because ruby 1.8.7+ has attr_accessor(reader and writer), attr_reader, and attr_writer.

Fix "no id given" message in method_missing

The following Ruby code raises the confusing error "no id given" shown at the end. How do I avoid this problem?
class Asset; end
class Proxy < Asset
def initialize(asset)
#asset
end
def method_missing(property,*args)
property = property.to_s
property.sub!(/=$/,'') if property.end_with?('=')
if #asset.respond_to?(property)
# irrelevant code here
else
super
end
end
end
Proxy.new(42).foobar
#=> /Users/phrogz/test.rb:13:in `method_missing': no id given (ArgumentError)
#=> from /Users/phrogz/test.rb:13:in `method_missing'
#=> from /Users/phrogz/test.rb:19:in `<main>'
The core of this problem can be shown with this simple test:
def method_missing(a,*b)
a = 17
super
end
foobar #=> `method_missing': no id given (ArgumentError)
This error arises when you call super inside method_missing after changing the value of the first parameter to something other than a symbol. The fix? Don't do that. For example, the method from the original question can be rewritten as:
def method_missing(property,*args)
name = property.to_s
name.sub!(/=$/,'') if name.end_with?('=')
if #asset.respond_to?(name)
# irrelevant code here
else
super
end
end
Alternatively, be sure to explicitly pass a symbol as the first parameter to super:
def method_missing(property,*args)
property = property.to_s
# ...
if #asset.respond_to?(property)
# ...
else
super( property.to_sym, *args )
end
end

Dynamic class creation, problem with function definition inside block: function body does not see local scope

I'm writing a function to dynamically create classes. I run into a problem with the *vars variable (below), where inside the block passed to Class::new, the "def initialize" method cannot see the value of *vars (and thus Ruby reports a unbound variable error on *vars).
What to do?
Thanks!
class MyParentClass
def do_something_with(*args)
end
def do_something_else_with(*vars)
end
end
def create_class(class_name,*vars)
new_class = Class::new(MyParentClass) do
def initialize(*args)
super
do_something_with(args)
do_something_else_with(vars)
end
end
Object::const_set(class_name.intern,new_class)
end
# Ruby: Error: *vars is unbound variable
# My notes: *vars is in scope inside the do..end block passed to Class::new, but cannot be seen inside def initialize (why?) . And, how to fix this?
I am not sure what are you trying to achieve with this contraption, but after some changes it works (well, depending on your definition of "to work"):
class MyParentClass
def do_something_with(*args)
puts "something #{args.inspect}"
end
def do_something_else_with(*vars)
puts "else #{vars.inspect}"
end
end
def create_class(class_name,*vars)
new_class = Class::new(MyParentClass) do
define_method :initialize do |*args|
super()
do_something_with(*args)
do_something_else_with(*vars)
end
end
Object::const_set(class_name.intern,new_class)
end
create_class :MyClass, 1, :foo, :bar
MyClass.new(2, :baz)
The trick to make vars visible is to define the constructor using a closure.

Closure doesn't work

If a block is a closure, why does this code does not work, and how to make it work?
def R(arg)
Class.new do
def foo
puts arg
end
end
end
class A < R("Hello!")
end
A.new.foo #throws undefined local variable or method `arg' for #<A:0x2840538>
Blocks are closures and arg is indeed available inside the Class.new block. It's just not available inside the foo method because def starts a new scope. If you replace def with define_method, which takes a block, you'll see the result you want:
def R(arg)
Class.new do
define_method(:foo) do
puts arg
end
end
end
class A < R("Hello!")
end
A.new.foo # Prints: Hello!
If you define the class dynamically, you can alter it as you like:
def R(arg)
c = Class.new
# Send the block through as a closure, not as an inline method
# definition which interprets variables always as local to the block.
c.send(:define_method, :foo) do
arg
end
c
end
class A < R("Hello!")
end
puts A.new.foo.inspect
# => "Hello!"

Resources