class Setter
attr_accessor :foo
def initialize
#foo = "It aint easy being cheesy!"
end
def set
self.instance_eval { yield if block_given? }
end
end
options = Setter.new
# Works
options.instance_eval do
p foo
end
# Fails
options.set do
p foo
end
Why does the 'set' method fail?
EDIT
Figured it out...
def set
self.instance_eval { yield if block_given? }
end
Needed to be:
def set(&blk)
instance_eval(&blk)
end
Yep - yield evaluates in the context within which it was defined.
Good write up here, but a simple example shows the problem:
>> foo = "wrong foo"
>> options.set do
?> p foo
>> end
"wrong foo"
Related
I have a parent class:
class Base
def my_method
block_method do
# EXECUTE WHATEVER'S IN THE CHILD VERSION OF my_method
# HOW TO DO?
end
end
def block_method
original_foo = 'foo'
foo = 'CUSTOM FOO'
yield
foo = original_foo
end
end
and some child classes--currently five and there will be more:
class ChildA < Base
def my_method
puts 'apple'
puts 'aardvark'
end
end
class ChildE < Base
def my_method
puts 'eel'
puts 'elephant'
end
end
I want to change what a variable refers to just for the duration of each Child's #my_method.
My thought is to do this by wrapping the functionality of each Child's #my_method in a block. But I'd rather do that in the parent class than have to wrap each child class's #my_method in the exact same block.
Any insights on how I can do this?
If there's some opposite of super, I guess that would be one way to accomplish what I want to do.
You could give the idea of what you are calling "some opposite of super" using a module and prepend like so:
module MethodThing
# added to remove binding from class since
# #my_method relies on the existence of #foo and #foo=
def self.prepended(base)
base.attr_reader(:foo) unless base.method_defined?(:foo)
base.attr_writer(:foo) unless base.method_defined?(:foo=)
end
def my_method
puts "Before: #{foo}"
original_foo = foo
self.foo= 'CUSTOM FOO'
begin
super
rescue NoMethodError
warn "(skipped) #{self.class}##{__method__} not defined"
end
self.foo = original_foo
puts "After: #{foo}"
end
end
Prepend the module on inheritance
class Base
def self.inherited(child)
child.prepend(MethodThing)
end
attr_accessor :foo
def initialize
#foo = 12
end
end
class ChildA < Base
def my_method
puts 'apple'
puts "During: #{foo}"
puts 'aardvark'
end
end
class ChildE < Base
end
Output:
ChildA.new.my_method
# Before: 12
# apple
# During: CUSTOM FOO
# aardvark
# After: 12
ChildE.new.my_method
# Before: 12
# (skipped) ChildE#my_method not defined
# After: 12
There are other strange ways to accomplish this with inheritance as well such as
class Base
class << self
attr_accessor :delegate_my_method
def method_added(method_name)
if method_name.to_s == "my_method" && self.name != "Base"
warn "#{self.name}#my_method has been overwritten use delegate_my_method instead"
end
end
end
attr_accessor :foo
def my_method
puts "Before: #{foo}"
original_foo = foo
self.foo= 'CUSTOM FOO'
begin
method(self.class.delegate_my_method.to_s).()
rescue NameError, TypeError
warn "(skipped) #{self.class} method delegation not defined"
end
self.foo = original_foo
puts "After: #{foo}"
end
end
class ChildA < Base
self.delegate_my_method = :delegation_method
def delegation_method
puts 'apple'
puts "During: #{foo}"
puts 'aardvark'
end
end
I could probably keep going with stranger and stranger ways to solve this problem but I think these will get you where you need to go.
One option would be to define a private or nodoc method which the parent class can call and is defined in each of the children.
class Parent
def my_method
block_method do
my_method_behavior
end
end
def block_method
original_foo = #foo
#foo = 'CUSTOM FOO'
yield
#foo = original_foo
end
end
class Child1 < Parent
def my_method_behavior
puts #foo
end
end
class Child2 < Parent
def my_method_behavior
puts #foo
end
end
How can I set some instance variables when extending a instance in the same way it can be done when creating it, with initialize.
In this example the variable set when extending is "lost":
module Mod
def self.extended(base)
#from_module = "Hello from Mod"
puts "Setting variable to: #{#from_module}"
end
def hello_from_module
return #from_module
end
end
class Klass
def initialize
#from_class = "Hello from Klass"
end
def hello_from_class
return #from_class
end
end
klass = Klass.new #=> #<Klass:0x00000000ed8618 #from_class="Hello from Klass">
klass.extend(Mod) #=> #<Klass:0x00000000ed8618 #from_class="Hello from Klass">
"Setting variable to: Hello from Mod"
klass.hello_from_class #=> "Hello from Klass"
klass.hello_from_module #=> nil (warning: instance variable #from_module not initialized)
There are a number of ways to do what you describe.
The most common one would be to use instance_variable_get and instance_variable_set:
module ModA
def self.extended(base)
base.instance_variable_set(:#from_module, "Hello from Mod A")
puts "Setting variable to: #{base.instance_variable_get(:#from_module)}"
end
def hello_from_module
return #from_module
end
end
Another common technique is to use any of the eval or exec methods. In this case instance_exec:
module ModB
def self.extended(base)
base.instance_exec { #from_module = "Hello from Mod B" }
puts "Setting variable to: #{base.instance_exec { #from_module }}"
end
def hello_from_module
return #from_module
end
end
Ruby has the File class that can be initialized using the normal new() method, or using open() and passing a block. How would I write a class that behaved like this?
File.open("myfile.txt","r") do |f|
...
end
This is a simple example of passing a block to new/open method
class Foo
def initialize(args, &block)
if block_given?
p block.call(args) # or do_something
else
#do_something else
end
end
def self.open(args, &block)
if block_given?
a = new(args, &block) # or do_something
else
#do_something else
end
ensure
a.close
end
def close
puts "closing"
end
end
Foo.new("foo") { |x| "This is #{x} in new" }
# >> "This is foo in new"
Foo.open("foo") { |x| "This is #{x} in open" }
# >> "This is foo in open"
# >> closing
The general outline of File.open is something like this:
def open(foo, bar)
f = do_opening_stuff(foo, bar)
begin
yield f
ensure
do_closing_stuff(f)
end
end
It is yield that invokes the block passed by the caller. Putting do_closing_stuff within an ensure guarantees that the file gets closed even if the block raises an exception.
More nitty-gritty on the various ways of calling blocks here: http://innig.net/software/ruby/closures-in-ruby
You can create a class method which creates an instance, yields it, and then then performs cleanup after the yield.
class MyResource
def self.open(thing, otherthing)
r = self.new(thing, otherthing)
yield r
r.close
end
def initialize(thing, otherthing)
#thing = thing
#otherthing = otherthing
end
def do_stuff
puts "Doing stuff with #{#thing} and #{#otherthing}"
end
def close
end
end
Now, you can either use it with a constructor:
r = MyResource.new(1, 2)
r.do_stuff
r.close
or using a block, which automatically closes the object:
MyResource.open(1, 2) do |r|
r.do_stuff
end
Can someone explain why the last yielder throws a no block given?
class Foo
def yielder
yield "hello"
end
end
class Mod
def initialize
##foo = Foo.new
end
def self.foo
##foo
end
end
worker = Mod.new
Mod.foo.yielder do |hello|
puts hello
end
Mod.foo.class.send(:define_method,:yielder) do
yield "new hello"
end
Mod.foo.yielder do |hello|
puts hello
end
Gives:
hello
test.rb:27:in `block in ': no block given (yield) (LocalJumpError)
from test.rb:30:in `'
A short introduction:
You don't need the Mod-instance, if you define ##foo outside initialize.
You don't need the Mod class to get the problem:
class Foo
def yielder
p 2
yield "hello"
end
end
foo = Foo.new
foo.yielder do |hello|
puts hello
end
foo.class.send(:define_method,:yielder) do
p 1
yield "new hello"
end
foo.yielder do |hello|
puts hello
end
You may shorten your example again:
class Foo
end
foo = Foo.new
foo.class.send(:define_method,:yielder) do
yield "new hello"
end
foo.yielder do |hello|
puts hello
end
This is the same as:
class Foo
define_method(:yielder) do
yield "new hello"
end
end
foo = Foo.new
foo.yielder do |hello|
puts hello
end
End of Introduction.
And now, I'm not sure if I understood correct what you want (and if I understand ruby correct ;) )
define_method accepts a block and use it as method body.
If the new method should receive a block on its own, you must define it in the interface of the definition and call it:
class Foo
define_method(:yielder) do | &prc |
prc.call("new hello")
end
end
foo = Foo.new
foo.yielder do |hello|
puts hello
end
Or the same logic in your example:
class Foo
def yielder
yield "hello"
end
end
class Mod
def initialize
##foo = Foo.new
end
def self.foo
##foo
end
end
worker = Mod.new
Mod.foo.yielder do |hello|
puts hello
end
Mod.foo.class.send(:define_method,:yielder) do | &prc |
prc.call "new hello"
end
Mod.foo.yielder do |hello|
puts hello
end
To make the code more robust, I would recommend some checks with block_given?.
Just for fun, again, but is it possible to take a block that contains method definitions and add those to an object, somehow? The following doesn't work (I never expected it to), but just so you get the idea of what I'm playing around with.
I do know that I can reopen a class with class << existing_object and add methods that way, but is there a way for code to pass that information in a block?
I guess I'm trying to borrow a little Java thinking here.
def new(cls)
obj = cls.new
class << obj
yield
end
obj
end
class Cat
def meow
puts "Meow"
end
end
cat = new(Cat) {
def purr
puts "Prrrr..."
end
}
cat.meow
# => Meow
# Not working
cat.purr
# => Prrrr...
EDIT | Here's the working version of the above, based on edgerunner's answer:
def new(cls, &block)
obj = cls.new
obj.instance_eval(&block)
obj
end
class Cat
def meow
puts "Meow"
end
end
cat = new(Cat) {
def purr
puts "Prrrr..."
end
}
cat.meow
# => Meow
cat.purr
# => Prrrr...
You can use class_eval(also aliased as module_eval) or instance_eval to evaluate a block in the context of a class/module or an object instance respectively.
class Cat
def meow
puts "Meow"
end
end
Cat.module_eval do
def purr
puts "Purr"
end
end
kitty = Cat.new
kitty.meow #=> Meow
kitty.purr #=> Purr
kitty.instance_eval do
def purr
puts "Purrrrrrrrrr!"
end
end
kitty.purr #=> Purrrrrrrrrr!
Yes
I suspect you thought of this and were looking for some other way, but just in case...
class A
def initialize
yield self
end
end
o = A.new do |o|
class << o
def purr
puts 'purr...'
end
end
end
o.purr
=> purr...
For the record, this isn't the usual way to dynamically add a method. Typically, a dynamic method starts life as a block itself, see, for example, *Module#define_method*.