I realize that you can change the binding of a block using instance_eval
class Foo
def bar &block
instance_eval &block
end
end
Foo.new.bar { self } # returns the instance
But some built in methods accept blocks and in that case it doesn't seem possible to change the binding of the block without messing with the internals of the built in method.
class Foo
def enum &block
Enumerator.new &block
end
end
Foo.new.enum { self }.each {} # returns main!!!
Is there a way around this?
You can work around it this way:
class Foo
def enum &block
Enumerator.new do |*args|
instance_exec *args, &block
end
end
end
But I'm confident that you cannot change the binding of an existing Proc short of instance_eval/instance_exec-ing it.
Related
I am creating a helper module to initialize the object before calling its methods
module Initialized
extend ActiveSupport::Concern
class_methods do
def run(*args)
new(*args).run
end
def call(*args)
new(*args).call
end
def execute(*args)
new(*args).create
end
end
end
So instead of defining run, call, and execute in my helper module I need to receive any method name and check if it exists on the main class after initializing it, then call the requested instance method if exists in the main class or raise an error if not
I would say my targeted code would be something like this
module Initialized
extend ActiveSupport::Concern
class_methods do
def _(*args, methodname)
new(*args).try(:send, "#{methodname}") || raise 'Method not exist'
end
end
end
Sample usage would be
class MyClass
include Initialized
def initialize(param1)
#param1 = param1
end
def call
puts "call '#{#param1}'"
end
end
then calling
MyClass.call('method param')
I found these links but couldn't find my answer yet:
meta-dynamic-generic-programming-in-ruby
ruby-module-that-delegates-methods-to-an-object
template-methods-in-ruby
Despite the fact method_missing would do the job, I'd probably avoid it in this case in favor of a more explicit delegation. Simple and dirty example:
module InstanceDelegator
def delegate_to_instance(*methods)
methods.each do |method_name|
define_singleton_method method_name do |*args|
new(*args).public_send(method_name)
end
end
end
end
class Foo
extend InstanceDelegator
delegate_to_instance :bar # <- define explicitly which instance methods
# should be mirrored by the class ones
def bar
puts "bar is called"
end
def baz
puts "baz is called"
end
end
# and then
Foo.bar # => bar is called
Foo.baz # NoMethodError ...
# reopening works too
class Foo
delegate_to_instance :baz
end
Foo.baz # baz is called
Pros:
you don't need to redefine method_missing (less magic -> less pain when you debug the code)
you control precisely which instance methods to be wrapped with the class level "shorthand" (fewer chances to delegate something you don't want to - more robust code)
(minor) no need to raise NoMethodError explicitly - you can fully rely on the core dispatching as it is...
I found another solution instead of using a module,
I can use the class method self.method_missing
def self.method_missing(method_name, *args, &block)
obj = new(*args)
raise NoMethodError, "undefined method `#{method_name}' for #{self}:Class" unless obj.respond_to?(method_name)
obj.send(method_name, &block)
end
But the limitation is that I have to copy it into every class whenever I need to use this feature
I have a subclass that I would like to implement some default behavior for a set of methods, for example if I know I need to override the method, but just want to print a warning for now if the method is not yet supported on this particular subclass.
I can override each method in the subclass, like so, but I was wondering if there was a easier way, maybe using delegate or method_missing?
class Foo
def method_a
# do foo stuff
end
def method_b
# do foo stuff
end
def method_c
# do foo stuff
end
end
class Bar < Foo
def not_yet_supported
puts "warnnig not yet supported"
end
def method_b
not_yet_supported
end
end
Using alias or alias_method as suggested in the comments is an easy route, and would look like
alias method_b not_yet_supported
alias_method :method_c, :not_yet_supported
Note the syntactic differences, since alias is a keyword and alias_method is a method.
If you want to declare a whole list of unsupported methods, a little metaprogramming gets you there:
class Foo
def self.not_yet_supported(*method_names)
method_names.each do |method_name|
define_method method_name do
puts "#{method_name} is not yet supported on #{self.class.name}"
end
end
end
not_yet_supported :bar, :baz
end
A cleaner approach would be to use method_missing:
class Foo
UNIMPLEMENTED_METHODS = [:method_a, :method_b, :method_etc]
def method_missing(method_id)
UNIMPLEMENTED_METHODS.include?(method_id) ? warn("#{method_id} has not been implemented") : raise(NoMethodError)
end
end
A friendlier explanation of method_missing can also be found here
Can someone help me modify the answer provided for intercepting instance method calls so that it works with either class method calls, or both class and instance method calls? From my limited knowledge of metaprogramming with Ruby, I'd imagine it would have something to do with opening up the singleton class some place using class << self, but I've tried doing that in various places with this code and I can't seem to figure it out. Instead of a direct answer, though, could you provide me with a push in the right direction? I'm a big fan of figuring things out for myself unless I'm completely out of my depth. Thanks!
Here is my solution modified from the answer in the link you provided. I moved the hook logic from super class to a separate module so that when ever a class needs the hook, it just include or extend that module and call the hook method.
before_each_method type, &block - type can be :class or :instance, and the block is the code to be executed before each method. The block will be evaluated under certain environments, that is, for instance methods, self in the block is the instance; for class methods, self in the block is the class.
before_class_method &block - alias for before_each_method :class, &block
before_instance_method &block - alias for before_each_method :instance, &block
module MethodHooker
def self.included(base)
base.extend(ClassMethods)
end
def self.extended(base)
base.extend(ClassMethods)
end
module ClassMethods
def before_each_method type, &block
singleton = class << self; self; end
case type
when :instance
this = self
singleton.instance_eval do
define_method :method_added do |name|
last = instance_variable_get(:#__last_methods_added)
return if last and last.include?(name)
with = :"#{name}_with_before_each_method"
without = :"#{name}_without_before_each_method"
instance_variable_set(:#__last_methods_added, [name, with, without])
this.class_eval do
define_method with do |*args, &blk|
instance_exec(name, args, blk, &block)
send without, *args, &blk
end
alias_method without, name
alias_method name, with
end
instance_variable_set(:#__last_methods_added, nil)
end
end
when :class
this = self
singleton.instance_eval do
define_method :singleton_method_added do |name|
return if name == :singleton_method_added
last = instance_variable_get(:#__last_singleton_methods_added)
return if last and last.include?(name)
with = :"#{name}_with_before_each_method"
without = :"#{name}_without_before_each_method"
instance_variable_set(:#__last_singleton_methods_added, [name, with, without])
singleton.class_eval do
define_method with do |*args, &blk|
instance_exec(name, args, blk, &block)
send without, *args, &blk
end
alias_method without, name
alias_method name, with
end
instance_variable_set(:#__last_singleton_methods_added, nil)
end
end
end
end
def before_class_method &block
before_each_method :class, &block
end
def before_instance_method &block
before_each_method :instance, &block
end
end
end
class Test
extend MethodHooker
before_each_method :instance do |method, args, block|
p [method, args, block]
puts "before instance method(#{method}) #{#var}"
end
before_class_method do |method, args, block|
puts "before class method(#{method}) #{#class_instance_var}"
end
#class_instance_var = 'stackoverflow'
def initialize
#var = 1
end
def test(a, b, c)
puts "instance method test"
end
def self.test1
puts "class method test"
end
end
Test.new.test(1, "arg2", [3]) {|t| t}
Test.test1
The output will be something like:
[:initialize, [], nil]
before instance method(initialize)
[:test, [1, "arg2", [3]], #<Proc:0x00000001017d5eb8#/Users/test/before_method.rb:88>]
before instance method(test) 1
instance method test
before class method(test1) stackoverflow
class method test
I have a code
class A < BasicObject
def initialize var1, *args, &block
if var1 == :lambda
#var1 = lambda &block
end
end
end
a = A.new :lambda, 123 do |var|
puts "ha ha ha"
end
why does it cause an error?
undefined method `lambda' for #<A:0x00000001687968> (NoMethodError)
unlike this one (it doesn't cause it)
class A
def initialize var1, *args, &block
if var1 == :lambda
#var1 = lambda &block
end
end
end
The lambda method is defined in the Kernel module. Object includes Kernel. BasicObject does not. So if you want to use lambda from a BasicObject, you have to call it as ::Kernel.lambda.
Note that this is not specific to lambda - it applies to any other Kernel method (like e.g. puts) as well.
PS: Note that #var1 = lambda &block does the same thing as just writing #var1 = block, so the use of lambda isn't actually necessary here.
You are using BasicObject as base class which is an explicit Blank class and specifically does not include Kernel, so you need the qualifier ::Kernel when you access any Kernel method.
On a separate note -
Instead of passing an argument that you have a block you can use the Kernel method block_given?
So taking your example -
class A
def initialize *args, &block
if block_given?
#var1 = lambda &block
end
puts #var1.call
end
end
a = A.new 123 do |var|
puts "ha ha ha"
end
I'm playing with ruby's metaprogramming features, and I'm finding it a bit hairy. I'm trying to wrap a method call using a module. Currently, I'm doing this:
module Bar
module ClassMethods
def wrap(method)
class_eval do
old_method = "wrapped_#{method}".to_sym
unless respond_to? old_method
alias_method old_method, method
define_method method do |*args|
send old_method, *args
end
end
end
end
end
def self.included(base)
base.extend ClassMethods
end
end
class Foo
include Bar
def bar(arg = 'foo')
puts arg
end
wrap :bar
end
Three questions:
Is there any way to do this without renaming the method, so as to allow the use of super? Or something cleaner/shorter?
Is there a clean way to set the default values?
Is there a means to move the wrap :bar call further up?
1) Cleaner/shorter
module ClassMethods
def wrap(method)
old = "_#{method}".to_sym
alias_method old, method
define_method method do |*args|
send(old, *args)
end
end
end
class Foo
extend ClassMethods
def bar(arg = 'foo')
puts arg
end
wrap :bar
end
As far as I know there is no way to achieve this without renaming. You could try to call super inside the define_method block. But first of all, a call to super from within a define_method will only succeed if you specify arguments explicitly, otherwise you receive an error. But even if you call e.g. super(*args), self in that context would be an instance of Foo. So a call to bar would go to the super classes of Foo, not be found and ultimately result in an error.
2) Yes, like so
define_method method do |def_val='foo', *rest|
send(old, def_val, *rest)
end
However, in Ruby 1.8 it is not possible to use a block in define_method, but this has been fixed for 1.9. If you are using 1.9, you could also use this
define_method method do |def_val='foo', *rest, &block|
send(old, def_val, *rest, &block)
end
3) No, unfortunately. alias_method requires the existence of the methods that it takes as input. As Ruby methods come into existence as they are parsed, the wrap call must be placed after the definition of bar otherwise alias_method would raise an exception.