If you run the code below you get an error.
class C
def self.filter_clause param_1
puts param_1
yield # context of this param is class B
end
def hi
"hello"
end
end
class B
def self.filter(value, lambda)
code = lambda { filter_clause(value, &lambda) }
C.instance_exec(&code)
end
filter(:name, ->{ hi })
end
The error is
NameError: undefined local variable or method `hi' for B:Class
from (pry):17:in `block in <class:B>'
From my understanding the reason for this is the lambda is running under the context of class B. So it can't find the method def hi. I can't figure out how to get it to run under the context of class C.
Essentially I want to be able to inject a method being called in another class that accepts an argument and a block.
For example:
filter_clause("value", ->{ hi })
Is this possible?
Not sure if I am making sense.
You're close. If you want the lambda(/block, really) to execute in the context of an instance of C (since hi is an instance method on C), then you'll need to instantiate it and then instance_exec the block on that new instance:
class C
def self.filter_clause param_1, &block
puts new.instance_exec &block
end
def hi
"hello"
end
end
class B
def self.filter(value, lambda)
code = lambda { filter_clause(value, &lambda) }
C.instance_exec(&code)
end
filter(:name, ->{ hi })
end
# => hello
Are you opposed to passing the context into the block? If not, something like this would work:
class C
def self.filter_clause param_1
puts param_1
yield self
end
def hi
"hello"
end
end
class B
def self.filter(value, lambda)
code = lambda { filter_clause(value, &lambda) }
C.instance_exec(&code)
end
filter(:name, ->(klass){ klass.new.hi }) # "hello"
end
Related
I have a class "C". I'd like C to run a method A that takes in a block. I'd then like the block to have the context of the class provided.
C.a do
b # runs main.b instead of C.b
end
Currently, the method b is running in the context of main. I'd like for it to run in the context of the class C
How can this be done?
class C
class << self
def a(&block)
block.bind self # NOPE!
block.binding = self # NOPE!
yield # NOPE!
end
def b
end
end
end
PS. This is the same pattern as Rails routes.
You need to eval the block in the context of the Class:
class C
class << self
def a(&block)
self.instance_eval(&block)
end
def b
puts "hello"
end
end
end
C.a do
b
end
=> "hello"
There is a pretty good documentation of the current implementation of refinements in ruby here:
http://ruby-doc.org//core-2.2.0/doc/syntax/refinements_rdoc.html,
but there are some strange corner cases.
First, include module is orthogonal to using module (one include the instance method of module while the other activates the refinement). But there is a trick to include a refinement module itself, see
Better way to turn a ruby class into a module than using refinements?.
def to_module(klass)
Module.new do
#note that we return the refinement module itself here
return refine(klass) {
yield if block_given?
}
end
end
class Base
def foo
"foo"
end
end
class Receiver
include to_module(Base) {
def foo
"refined " + super
end
}
end
Receiver.new.foo #=> "refined foo"
Strangely this refinement module can't be used with using!
m=to_module(Base) {}
m.class #=> Module
using m
#=>TypeError: wrong argument type Class (expected Module)
So using only work on the enclosing module of the refinement modules.
Secondly I wanted to use the above yield trick to be able to pass a Proc to refine (even through it only accepts a block), without resorting to converting the Proc back to source as in
https://www.new-bamboo.co.uk/blog/2014/02/05/refinements-under-the-knife/.
But using yield as in the include example does not work:
def ref_module1(klass)
Module.new do
refine(klass) {
yield
}
end
end
class Receiver1
using ref_module1(Base) {
def foo
"refined " + super
end
}
def bar
Base.new.foo
end
end
Receiver1.new.bar #=> NoMethodError: super: no superclass method `foo'
We see that Receiver1 still use Bar#foo and not the refined method.
Howewer we can use module_eval instead:
def ref_module2(klass,&b)
Module.new do
refine(klass) {
module_eval(&b)
}
end
end
class Receiver2
using ref_module2(Base) {
def foo
"refined " + super
end
}
def bar
Base.new.foo
end
end
Receiver2.new.bar #=> "refined foo"
I don't quite understand why module_eval works here and not the yield method. Inside the refinement block, the 'default_definee' is the refinement module, so module_eval which puts the 'default_definee' to self='the refinement module' should not affect it. And indeed in the 'include' example at the beginning, I get the same result when I use module_eval or a direct yield.
Can anyone explain this behavior?
Context (or binding) is the reason why module_eval works and yield doesn't in your last set of examples. It actually has nothing to do with refinements, as demonstrated below.
Starting with module_eval:
class Foo
def run(&block)
self.class.module_eval(&block)
end
end
foo = Foo.new
foo.run {
def hello
"hello"
end
}
puts foo.hello # => "hello"
puts hello => # '<main>': undefined method 'hello' for main:Object (NameError)
In Foo#run we call module_eval on Foo. This switches the context (self) to be Foo. The result is much like we had simple defined hello inside of class Foo originally.
Now let's take a look at yield:
class Foo
def run
yield
end
end
foo = Foo.new
foo.run {
def hello
"hello"
end
}
puts hello # => "hello"
puts foo.hello # => '<main>': private method 'hello' called for ...
yield simply invokes the block in its original context, which in this example would be <main>. When the block is invoked, the end result is exactly the same as if the method were defined at the top level normally:
class Foo
def run
yield
end
end
foo = Foo.new
def hello
"hello"
end
puts hello # => "hello"
puts foo.hello # => '<main>': private method 'hello' called for ...
You might notice that foo seems to have the hello method in the yield examples. This is a side effect of defining hello as a method at the top level. It turns out that <main> is just an instance of Object, and defining top level methods is really just defining private methods on Object which nearly everything else ends up inheriting. You can see this by opening up irb and running the following:
self # => main
self.class # => Object
def some_method
end
"string".method(:some_method) # => #<Method: String(Object)#some_method>
Now back to your examples.
Here's what happens in the yield example:
def ref_module1(klass)
Module.new do
refine(klass) {
yield
}
end
end
class Receiver1
# like my yield example, this block is going to
# end up being invoked in its original context
using ref_module1(Base) {
def foo
"I'm defined on Receiver1"
end
}
def bar
# calling foo here will simply call the original
# Base#foo method
Base.new.foo
end
end
# as expected, if we call Receiver1#bar
# we get the original Base#foo method
Receiver1.new.bar # => "foo"
# since the block is executed in its original context
# the method gets defined in Receiver1 -- its original context
Receiver1.new.foo # => "I'm defined on Receiver1"
As for module_eval, it works in your examples because it causes the block to be run in the context of the new module, rather than on the Receiver1 class.
I have the following code:
class A
def self.scope
yield
end
def self.method_added method
self.instance_eval %{
# do something involving the added method
}
end
end
class B < A
scope do
def foo
end
end
end
When the method_added hook is fired, will the code inside instance_eval run within the same scope as the method that was added? Or, will it run outside of it?
What are the caveats and gotchas involved within this?
Your scope method is basically a no-op. When you pass a block to a method that yields, the block is evaluated in the current scope. Observe:
class A
def self.scope
yield
end
end
A.scope { p self }
# main
Since nothing is yielded to the block, and nothing is done with the return value of yield, any code run in the block will have the same effect run outside the scope block.
This isn't the case with instance_eval, however. When instance_eval runs a block, self in the block is set to the receiver (rather than whatever self is in the block's scope). Like this:
class A
end
A.instance_eval { p self }
# A
But note that this means that self.instance_eval { ... } is also a fancy no-op, because you're changing the block's self to the same self outside the block.
So your code is equivalent to this:
class A
def self.method_added method
# do something involving the added method
end
end
class B < A
def foo
end
end
Let's find out!
class A
def self.scope
yield
end
def self.method_added method
puts "In method_added, method = #{method}, self = #{self}"
instance_eval 'puts "In instance_eval, method = #{method}, self = #{self}"'
end
end
class B < A
scope do
puts "In scope's block, self = #{self}"
def foo
end
end
end
# In scope's block, self = B
# In method_added, method = foo, self = B
# In instance_eval, method = foo, self = B
Notice that you don't need self. in self.instance_eval.
Trying to use define_method inside initialize but getting undefined_method define_method. What am I doing wrong?
class C
def initialize(n)
define_method ("#{n}") { puts "some method #{n}" }
end
end
C.new("abc") #=> NoMethodError: undefined method `define_method' for #<C:0x2efae80>
I suspect that you're looking for define_singleton_method:
define_singleton_method(symbol, method) → new_method
define_singleton_method(symbol) { block } → proc
Defines a singleton method in the receiver. The method parameter can be a Proc, a Method or an UnboundMethod object. If a block is specified, it is used as the method body.
If you use define_method on self.class, you'll create the new method as an instance method on the whole class so it will be available as a method on all instances of the class.
You'd use define_singleton_method like this:
class C
def initialize(s)
define_singleton_method(s) { puts "some method #{s}" }
end
end
And then:
a = C.new('a')
b = C.new('b')
a.a # puts 'some method a'
a.b # NoMethodError
b.a # NoMethodError
b.b # puts 'some method b'
If your initialize did:
self.class.send(:define_method,n) { puts "some method #{n}" }
then you'd get:
a.a # puts 'some method a'
a.b # puts 'some method b'
b.a # puts 'some method a'
b.b # puts 'some method b'
and that's probably not what you're looking for. Creating a new instance and having the entire class change as a result is rather odd.
Do as below :
class C
def initialize(n)
self.class.send(:define_method,n) { puts "some method #{n}" }
end
end
ob = C.new("abc")
ob.abc
# >> some method abc
Module#define_method is a private method and also a class method.Your one didn't work,as you tried to call it on the instance of C.You have to call it on C,using #send in your case.
You were almost there. Just point to the class with self.class, don't even need to use :send:
class C
def initialize(n)
self.class.define_method ("#{n}") { puts "some method #{n}" }
end
end
ob = C.new('new_method')
ob2 = C.new('new_method2')
# Here ob and ob2 will have access to new_method and new_method2 methods
You can also use it with :method_missing to teach your class new methods like this:
class Apprentice
def method_missing(new_method)
puts "I don't know this method... let me learn it :)"
self.class.define_method(new_method) do
return "This is a method I already learned from you: #{new_method}"
end
end
end
ap = Apprentice.new
ap.read
=> "I don't know this method... let me learn it :)"
ap.read
=> "This is a method I already learned from you: read"
I want to call instance_eval on this class:
class A
attr_reader :att
end
passing this method b:
class B
def b(*args)
att
end
end
but this is happening:
a = A.new
bb = B.new
a.instance_eval(&bb.method(:b)) # NameError: undefined local variable or method `att' for #<B:0x007fb39ad0d568>
When b is a block it works, but b as a method isn't working. How can I make it work?
It's not clear exactly what you goal is. You can easily share methods between classes by defining them in a module and including the module in each class
module ABCommon
def a
'a'
end
end
class A
include ABCommon
end
Anything = Hash
class B < Anything
include ABCommon
def b(*args)
a
end
def run
puts b
end
end
This answer does not use a real method as asked, but I didn't need to return a Proc or change A. This is a DSL, def_b should have a meaningful name to the domain, like configure, and it is more likely to be defined in a module or base class.
class B
class << self
def def_b(&block)
(#b_blocks ||= []) << block
end
def run
return if #b_blocks.nil?
a = A.new
#b_blocks.each { |block| a.instance_eval(&block) }
end
end
def_b do
a
end
end
And it accepts multiple definitions. It could be made accept only a single definition like this:
class B
class << self
def def_b(&block)
raise "b defined twice!" unless #b_block.nil?
#b_block = block
end
def run
A.new.instance_eval(&#b_block) unless #b_block.nil?
end
end
def_b do
a
end
end