class SomeClass
def initialize
yield
end
def test
puts 'test'
end
end
I want to execute the test function inside a block when I initialize a some SomeClass.
SomeClass.new { test() }
This gives me
NoMethodError: undefined method `test' for main:Object
You are looking for instance_eval:
class SomeClass
def initialize(&block)
instance_eval(&block) if block_given?
end
def test
puts 'test'
end
end
SomeClass.new { test() } #=> test
It is very easy, just pass the self:
class SomeClass
def initialize
yield self if block_given?
end
def test
puts 'test'
end
end
SomeClass.new { |ob| ob.test }
# >> test
Your one didn't work, because blocks are closure, and the self is set to main inside the block in your example. main is a instance of Object. You didn't defie the #test inside the Object, so main tries to call the #test and you got the genuine error.
Or you can use call, like this:
class SomeClass
def initialize(&block)
block.call(self) if block_given?
end
def test
puts 'test'
end
end
SomeClass.new {|s| s.test }
Two solutions:
1
class SomeClass
def initialize(&block)
instance_eval(&block)
end
def test
puts 'test'
end
end
SomeClass.new { test() } #=> test
2
class SomeClass
def initialize
yield self
end
def test
puts 'test'
end
end
SomeClass.new {|o| o.test() } #=> test
Some references hope to help you:
1.instance_eval no longer yielding self in ruby 1.9: https://www.ruby-forum.com/topic/189422
2.How do I build DSLs with yield and instance_eval?: http://rubylearning.com/blog/2010/11/30/how-do-i-build-dsls-with-yield-and-instance_eval/
Related
Let's say I have a class Test
class Test
def initialize()
puts "cool"
end
end
Is there a way to extend initialize class somehow and execute some method in it?
For example I want to:
class Test
def func()
puts "test"
end
end
test = Test.new()
Should output
cool
test
Thanks!
You can define a module containing your extension:
module TestExtension
def initialize
super
puts 'test'
end
end
and then prepend that module to Test:
class Test
def initialize
puts 'cool'
end
end
Test.prepend(TestExtension)
Test.new
# cool
# test
If the code for Test is not under your control, and you want to inject test:
Test.class_eval do
def test
puts "TEST"
end
alias initialize_without_test initialize
# This, if you want the return value of `test` to replace the original's
def initialize(*args, &block)
initialize_without_test(*args, &block)
test
end
# Or this, if you want to keep the return value of original `initialize`
def initialize(*args, &block)
initialize_without_test(*args, &block).tap do
test
end
end
end
I have a class that was built for subclassing.
class A
def initialize(name)
end
def some
# to define in subclass
end
end
# usage
p A.new('foo').some
#=> nil
In my use case, I don't want to create a subclass since I need just one instance. Therefore, I'll change the initialize method to support the following usage.
p A.new('foo') { 'YEAH' }.some
#=> YEAH
How could I support the usage above?
BTW: I found the following solutions for a Ruby 1.8.7 project, but they look awkward to me.
class A
def singleton_class
class << self; self; end
end
def initialize(name, &block)
#name = name
self.singleton_class.send(:define_method, :some) { block.call } if block_given?
end
def some
# to define in subclass
end
end
You can store the block argument in an instance variable and call it later on:
class A
def initialize(name, &block)
#name = name
#block = block
end
def some
#block.call
end
end
A.new('foo') { 'YEAH' }.some
#=> "YEAH"
You can also pass arguments into the block:
class A
# ...
def some
#block.call(#name)
end
end
A.new('foo') { |s| s.upcase }.some
#=> "FOO"
Or instance_exec the block in the context of the receiver:
class A
# ...
def some
instance_exec(&#block)
end
end
Which allows you to bypass encapsulation:
A.new('foo') { #name.upcase }.some
#=> "FOO"
I have a function which defines and returns a new class, with some pre-built methods. E.g.:
def define_class(name, options={}, &block)
klass = Class.new(Class) do
def say_hello
puts "Hello!"
end
def say_goodbye
puts "Adios!"
end
end
parent_class.const_set(form_class, klass)
klass
end
So, for example, this works:
define_class("testing").new.say_hello #=> "Hello!"
But I would like to be able to pass in custom methods through a block, which would then be added to my class, like so:
define_class "testing" do
# ... custom methods
end
Such that this would work:
klass = define_class "testing" do
def interject
puts "Excuse me?"
end
end
klass.new.interject #=> "Excuse me?"
I can't figure out how to make that work though; I've tried instance_eval, class_eval, and yield, and none are producing the desired result.
Try simply:
def define_class(name, options={}, &block)
klass = Class.new(&block)
parent_class.const_set(form_class, klass)
klass
end
If you want to call the block and your own block, you should use class_eval:
def define_class(name, options={}, &block)
klass = Class.new do
def say_hello
puts "Hello!"
end
def say_goodbye
puts "Adios!"
end
class_eval(&block)
end
parent_class.const_set(form_class, klass)
klass
end
I'm trying to create some kind of module or superclass that wraps one method call after each method of the subclass.
There are some constraints though: I wouldn't want the method to be run after initialize() is called nor after a few other methods of my choice is called.
Another constraint is that I would only want that method to be executed IF the flag #check_ec is set to true.
I have classes with more than 60 methods that I have hard-coded the same piece of code that ispasted all over the place.
Is there a way that I could make a wrapper that would automatically execute that method for my class methods?
So the idea is this:
class Abstract
def initialize(check_ec)
#check_ec = check_ec
end
def after(result) # this is the method that I'd like to be added to most methods
puts "ERROR CODE: #{result[EC]}"
end
def methods(method) # below each method it would execute after
result = method() # execute the given method normally
after(result) if #check_ec and method != :initialize and method != :has_valid_params
end
end
class MyClass < Abstract
def initialize(name, some_stuff, check_error_code)
# do some stuff...
#name = name
super(check_error_code)
end
def my_method_a() # execute after() after this method
return {EC: 0}
end
def my_method_b() # execute after() after this method
return {EC: 7}
end
def has_valid_params() # don't execute after() on this method
return true
end
end
This is trivially easy using method_missing, and composition instead of inheritance. You can build a very simple class which forwards method invocations, and then executes an after callback, except for specific method names:
class Abstract
def initialize(object)
#object = object
end
def method_missing(method, *arguments)
result = #object.send(method, *arguments)
after() unless method == "has_valid_params"
result
end
def after
# whatever
end
end
o = Abstract.new(MyClass.new)
A solution using singleton class.
class MyClass
def initialize(name, some_stuff)
# do some stuff...
#name = name
end
def my_method_a # execute after() after this method
return {EC: 0}
end
def my_method_b() # execute after() after this method
return {EC: 7}
end
def has_valid_params() # don't execute after() on this method
return true
end
end
module ErrorCodeChecker
def after(result) # this is the method that I'd like to be added to most methods
puts "ERROR CODE: #{result[:EC]}"
end
def addErrorCodeCheck(exclude = [])
methods = self.class.superclass.public_instance_methods(false) - exclude
class << self
self
end.class_exec {
methods.each {|method|
define_method(method) {|*p|
super(*p).tap {|res| after(res)}
}
}
}
end
end
class MyClassEC < MyClass
include ErrorCodeChecker
def initialize(name, some_stuff, check_error_code, exclude = [])
super name, some_stuff
addErrorCodeCheck(exclude) if check_error_code
end
end
'addErrorCodeCheck' opens up the singleton class of an instance of MyClassEC, and redefines instance methods of MyClass not in the exclude list. The redefined methods hide the original methods but call them via 'super' method inside before calling 'after'.
You can apply 'addErrorCodeCheck' repeatedly later if needed.
Execution example: (tested in Ruby 1.9.3)
my = MyClassEC.new('test', 'abc', true, [:has_valid_params])
my.my_method_a # => ERROR CODE: 0
my.my_method_b # => ERROR CODE: 7
my.has_valid_params # => (nothing)
What about this? It has a major drawback which is that your methods must be already defined before calling check_error_code, but it may suit your needs. You could look for inspiration for a better solution in Rails callbacks, or defer the redefinition of each method until that method is added using the method_added hook.
Include ErrorCodeChecker and call check_error_code in each class you want to check the error code (as in the last line of the snippet).
module ErrorCodeChecker
def self.included(base)
base.send(:extend, ClassMethods)
end
def after(result) # this is the method that I'd like to be added to most methods
puts "ERROR CODE: #{result[:ec]}"
end
module ClassMethods
def check_error_code(options = {})
check_on = instance_methods(false) - Array(options[:except])
check_on &= Array(options[:only]) if options[:only]
class_eval do
check_on.each do |method|
alias_method "#{ method }_without_ec", method
define_method(method) do |*args, &block|
send("#{ method }_without_ec", *args, &block).tap { |result| after(result) if #check_ec }
#if you want to actually return the return value of calling after:
#result = send("#{ method }_without_ec")
##check_ec ? after(result) : result
end
end
end
end
end
end
class Abstract
include ErrorCodeChecker
def initialize(check_ec)
#check_ec = check_ec
end
end
class MyClass < Abstract
def initialize(name, some_stuff, check_error_code)
# do some stuff...
#name = name
super(check_error_code)
end
def my_method_a # execute after() after this method
{ec: 0}
end
def my_method_b # execute after() after this method
{ec: 7}
end
def has_valid_params # don't execute after() on this method
true
end
check_error_code except: :has_valid_params
#or whitelisting:
#check_error_code only: [:my_method_a, :my_method_b]
#or both:
#check_error_code only: :my_method_a, except: [:has_valid_params, dont_check_this_one]
end
I'm trying to make a method similar to attr_reader but I can't seem to get the instance of the class that the method gets called in.
class Module
def modifiable_reader(*symbols)
# Right here is where it returns Klass instead of #<Klass:0x1df25e0 #readable="this">
mod = self
variables = symbols.collect { |sym| ("#" << sym.to_s).to_sym }
attr_reader *symbols
(class << ModifyMethods; self; end).instance_eval do
define_method(*symbols) do
mod.instance_variable_get(*variables)
end
end
end
end
class Object
module ModifyMethods; end
def modify(&block)
ModifyMethods.instance_eval(&block)
end
end
class Klass
modifiable_reader :readable
def initialize
#readable = "this"
end
end
my_klass = Klass.new
my_klass.modify do
puts "Readable: " << readable.to_s
end
I'm not sure what it is you're trying to do.
If it helps, the spell for attr_reader is something like this:
#!/usr/bin/ruby1.8
module Kernel
def my_attr_reader(symbol)
eval <<-EOS
def #{symbol}
##{symbol}
end
EOS
end
end
class Foo
my_attr_reader :foo
def initialize
#foo = 'foo'
end
end
p Foo.new.foo # => "foo"
What I can understand from your code is that you want to have the modify block to respond to the instance methods of Klass, that's as simple as:
class Klass
attr_reader :modifiable
alias_method :modify, :instance_eval
def initialize(m)
#modifiable = m
end
end
Klass.new('john').modify do
puts 'Readable %s' % modifiable
end
About this tidbit of code:
def modifiable_reader(*symbols)
# Right here is where it returns Klass instead of #<Klass:0x1df25e0 #readable="this">
mod = self
...
Probably this can give you a hint of what is going on:
Class.superclass # => Module
Klass.instance_of?(Class) # => true
Klass = Class.new do
def hello
'hello'
end
end
Klass.new.hello # => 'hello'
When you are adding methods to the Module class, you are also adding methods to the Class class, which will add an instance method to instances of Class (in this case your class Klass), at the end this means you are adding class methods on your Klass class