How to use define_method in an included module to create methods? - ruby

I want a class method, step, which dynamically creates instance methods.
step(:start) do |payload|
puts payload
end
This would create the equivalent of:
def start(payload)
payload
end
The trouble I am having is getting a block passed to the class method evaluate in the context of the instance.
The problem seems to be that the block, because it is a closure, is evaluated in the context of where is was defined, the class.
module Steps
def step(name, &block)
define_method(name) do |payload|
self # => Foo instance
block.call(payload)
end
end
end
class Foo
extend Steps
step(:start) do |payload|
self # => Foo class
payload # => 1
self.data # => undefined method `data' for Foo:Class (NoMethodError)
end
def data
'DATA'
end
end
puts Foo.new.start(1)
Is it possible to have the block evaluated in the context of the instance?
The closest I have come is using instance_eval however I can't figure out how to pass the argument payload to the block:
define_method(name) do |payload|
instance_eval(&block)
end
When I do the above self in the passed block is an instance of Foo so I can call data, but how to access payload?

instance_exec
Executes the given block within the context of the receiver (obj). In
order to set the context, the variable self is set to obj while the
code is executing, giving the code access to obj’s instance variables.
Arguments are passed as block parameters.
define_method(name) do |payload|
instance_exec(payload, &block)
end
ref: Change Block Binding Without Eval?

Related

Ruby variable from outer scope undefined within a block - why?

This part of code dynamically creates several classes:
(1..MAX_ACT).each do |act_id|
klass = Class.new(ActB) do
def initialize(trg)
super(trg, act_id)
end
end
Object.const_set("Act#{act_id}", klass)
end
In this case, the common base class (ActB) has a constructor with two parameters, while the child classes have a constructor with one parameter.
Running this code works well, but when I later try to instantiate one of these classes, for example
Act3.new(4)
I get the error message
NameError: undefined local variable or method `act_id' for #<Act3:0x00000006008b7990>
The error message must refer to the line
super(trg, act_id)
because this is the only place in my program where I am using this variable. However, this variable is defined a few lines above, when it says
(1..MAX_ACT).each do |act_id|
I had expected, that the do...end block creates a closure for the constructor, where act_id is bound. However, this doesn't seem to be the case.
Why does my example not work? How do I have to do it correctly?
def (and class and module) creates a fresh local scope, which doesn't inherit any locals from outside.
So you're right that the Class.new do .. end creates a closure... but the inner def doesn't share it.
If you need standard block behaviour, you can use define_method instead:
(1..MAX_ACT).each do |act_id|
klass = Class.new(ActB) do
define_method :initialize do |trg|
super(trg, act_id)
end
end
Object.const_set("Act#{act_id}", klass)
end
Just out of curiosity, there is a hack, allowing to fool scoping and still use def initialize :)
class ActB
def initialize(trg, act_id)
puts "ActID: #{act_id}"
end
end
(1..MAX_ACT).each do |act_id|
klass = Class.new(ActB) do
#act_id = act_id
def initialize(trg)
super(trg, self.class.instance_variable_get(:#act_id))
end
end
Object.const_set("Act#{act_id}", klass)
end
Act1.new :foo
#⇒ ActID: 1
Act2.new :foo
#⇒ ActID: 2
The problem here is that the block passed to Class.new is executed in the context of that class. In the context of that class, act_id is not defined. So, to fix this, you can move the method definition outside of the class initialization, like so:
(1..MAX_ACT).each do |act_id|
klass = Class.new(ActB)
klass.define_method(:initialize) do |trg|
super(trg, act_id)
end
Object.const_set("Act#{act_id}", klass)
end

Don't pass block when calling super

How can I set a block to nil for the super call?
class A
def foo
if block_given?
result = yield
# do stuff with the yield result
end
# some more code
end
end
class B < A
def foo
block_result = yield
# process block results and store it
# ...
super
end
end
B.new.foo { puts "block called" }
# => block called
# => block called
I don't want to yield the block twice. Is it somehow possible that block_given? in class A returns false?
Background is that I don't own the A class and I can't change it's foo method but I want to avoid calling my block twice. I also don't want to pass a dummy / empty block to super, because the behaviour of A's foo method changes when a block is given.
It's actually not that obvious. From the documentation we know that:
calling super passes all arguments
calling super() passes no arguments
What the documentation doesn't say is that this only applies to positional and keyword arguments. super() still passes a given block!
You have to explicitly unset the block by calling:
super(&nil)
How does this work?
You probably know that you can define a method with an explicit block argument:
def foo(&block)
# ...
end
and that you can pass it as a block to another method: (that other method might be super)
def foo(&block)
super(&block)
end
Now, if you pass a block when calling the method, the corresponding block variable will be an instance of Proc:
def foo(&block)
p block_given: block_given?, block: block
end
foo {}
#=> {:block_given=>true, :block=>#<Proc:0x00007ff4990d0030>}
If you call it without passing block, the block variable will just be nil:
foo
#=> {:block_given=>false, :block=>nil}
So if no block is given, block is nil and
super(&block)
becomes:
super(&nil)

Default method for Ruby class

Is there a way to specify a class method such that when the object is used as if it were a function, that method is called? Something like this:
class MyClass
def some_magic_method(*args)
# stuff happens
end
end
# create object
myob = MyClass.new
# implicitly call some_magic_method
myob 'x'
You could write a command class and make use of a ruby shortcut
class MyClass
def self.call(text)
puts text
end
end
MyClass.('x')
Here MyClass.() defaults to the call class method.
As mentioned by #CarySwoveland in the comments you can use method_missing. A basic example is as follows:
class MyClass
def method_missing(method_name, *args)
if method_name.match?(/[xyz]/)
send(:magic_method, args.first)
else
super
end
end
def magic_method(a)
a = 'none' if a.nil?
"xyz-magic method; argument(s): #{a}"
end
end
myob = MyClass.new
myob.x #=> "xyz-magic method; argument(s): none"
myob.x(1) #=> "xyz-magic method; argument(s): 1"
myob.y #=> "xyz-magic method; argument(s): none"
myob.z #=> "xyz-magic method; argument(s): none"
This captures all methods named x, y or z. Our else branch sends all other undefined methods to the original method_missing:
myob.v #=> test.rb:7:in `method_missing': undefined method `v' for
#<MyClass:0x000000021914f8> (NoMethodError)
#from test.rb:25:in `<main>'
What methods you capture is up to you and is determined by the regex /[xyz]/ in this case.
Key methods: BasicObject#method_missing, Object#send. For further info check out this question, read Eloquent Ruby by Russ Olsen (from which this answer references)
You meant to invoke some class' instance method when the object is invoked as a function. This is already supported: instance method call gets called when you "invoke" an object via the functional invocation method () (for more details, see here How do I reference a function in Ruby?).
class C
def call(x)
puts "Called with #{x}"
end
end
obj = C.new
obj.(88) # Called with 88 => nil
obj (88) # NoMethodError: undefined method `obj' for main:Object
If you do want the latter syntax, a horrible trick is the following one (but works only at the top-level, unless you carry along the bindings):
module Kernel
def method_missing(name,*args)
obj = begin
TOPLEVEL_BINDING.local_variable_get(name)
rescue
nil
end
return super if obj.nil?
obj.send :call, *args
end
end
obj = C.new
obj 88 # Called with OK => nil
This example also wants to communicate that you should always keep in mind
who is the receiver of your method calls, and what syntaxes are available for calling methods (especially when you leave out dots and parentheses).
class D
def obj; C.new end
def f
#(obj) 88 # BAD
(obj).(88)
#obj() 88 # BAD
obj().(88)
end
end
The point is that you do not actually have functions, but methods that get called on objects. If you omit the receiver of a method call, the receiver defaults to self, the current object. But in your example, myob does not appear as an explicit receiver (since there is not following dot as in myob.), hence the current object is looked for a method myob.

Binding method to instance

Is there a way to bind an existing method to an existing instance of an object if both the method and the instance are passed as symbols into a method that does that if the instance is not a symbol?
For example:
def some_method
#do something
end
some_instance = Klass.new(something)
def method_that_binds(:some_method, to: :some_instance)
#how do I do that?
end
Your requirements are a little unusual, but it is possible to do this mostly as you say:
class Person; end
harry = Person.new
barry = Person.new
def test
puts 'It works!'
end
define_method :method_that_binds do |a_method, to|
eval(to[:to].to_s).singleton_class.send(:define_method, a_method, &Object.new.method(a_method))
end
method_that_binds :test, to: :harry
harry.test
# It works! will be sent to STDOUT
barry.test
# undefined method 'test'
This doesn't actually use a named parameter, but accepts a hash with a to key, but you can see you can call it in the way you want. It also assumes that the methods you are defining are defined globally on Object.
The API you want doesn't easily work, because you have to know from which scope you want to access the local variable. It's not quite clear to me why you want to pass the name of the local variable instead of passing the content of the local variable … after all, the local variable is present at the call site.
Anyway, if you pass in the scope in addition to the name, this can be accomplished rather easily:
def some_method(*args)
puts args
puts "I can access some_instance's ivar: ##private_instance_var"
end
class Foo; def initialize; #private_instance_var = :foo end end
some_instance = Foo.new
def method_that_binds(meth, to:, within:, with: [])
self.class.instance_method(meth).bind(within.local_variable_get(to)).(*with)
end
method_that_binds(:some_method, to: :some_instance, within: binding, with: ['arg1', 'arg2'])
# arg1
# arg2
# I can access some_instance's ivar: foo
As you can see, I also added a way to pass arguments to the method. Without that extension, it becomes even simpler:
def method_that_binds(meth, to:, within:)
self.class.instance_method(meth).bind(within.local_variable_get(to)).()
end
But you have to pass the scope (Binding) into the method.
If you'd like to add a method just to some_instance i.e. it's not available on other instances of Klass then this can be done using define_singleton_method (documentation here.)
some_instance.define_singleton_method(:some_method, method(:some_method))
Here the first use of the symbol :some_method is the name you'd like the method to have on some_instance and the second use as a parameter to method is creating a Method object from your existing method.
If you'd like to use the same name as the existing method you could wrap this in your own method like:
def add_method(obj, name)
obj.define_singleton_method(name, method(name))
end
Let's say we have a class A with a method a and a local variable c.
class A
def a; 10 end
end
c = '5'
And we want to add the method A#a to c.
This is how it can be done
c.singleton_class.send :define_method, :b, &A.new.method(:a)
p c.b # => 10
Explanations.
One way to add a method to an object instance and not to its class is to define it in its singleton class (which every ruby object has).
We can get the c's singleton class by calling the corresponding method c.signleton_class.
Next we need to dynamically define a method in its class and this can usually be accomplished by using the define_method which takes a method name as its first argument (in our case :b) and a block. Now, converting the method into a block might look a bit tricky but the idea is relatively simple: we first transform the method into a Method instance by calling the Object#method and then by putting the & before A.new.method(:a) we tell the interpreter to call the to_proc method on our object (as our returned object is an instance of the Method, the Method#to_proc will be called) and after that the returned proc will be translated into a block that the define_method expects as its second argument.

How to dynamically declare subclass in Ruby?

This question pretty much sums up the simple case for dynamically extending the class hierarchy in Ruby.
The problem I'm having is that I want to define this subclass with a DSL, and I think I'm a victim of my own complicated scope.
I have working code which uses a base class:
module Command
class Command
...
end
end
And then each command is implemented as a subclass:
module Command
class Command_quit < Command
def initialize
name = "quit"
exec do
#user.client.should_terminate = true
end
end
end
end
There is a lot of rote and repetition here, and I have envisioned a DSL which cleans this up significantly:
module Command
define :quit do
exec do # this is global.rb:7 from the error below
#user.client.should_terminate = true
end
end
end
As you can see, I want to DRY out the boilerplate as I am only concerned with the contents of #initialize, which sets some metadata (such as name) and defines the exec block (which is the important part).
I have gotten stuck with the following module method:
module Command
def self.define(cmd_name, &init_block)
class_name = "Command_#{cmd_name.to_s}"
class_definition = Class.new(Command)
class_initializer = Proc.new do
name = cmd_name
init_block.call
end
::Command.const_set class_name, class_definition
::Command.const_get(class_name).send(:define_method, :initialize, class_initializer)
end
end
This code yields lib/commands/global.rb:7:in 'exec': wrong number of arguments (0 for 1+) (ArgumentError)
And suppose I have some metadata (foo) which I want to set in my DSL:
module Command
define :quit do
foo "bar" # this becomes global.rb:7
exec do
#user.client.should_terminate = true
end
end
end
I see lib/commands/global.rb:7:in block in <module:Command>': undefined method 'foo' for Command:Module (NoMethodError)
I think I've got my Proc/block/lambda-fu wrong here, but I'm struggling to get to the bottom of the confusion. How should I write Command::define to get the desired result? It seems like although Ruby creates Command::Command_help as a subclass of Command::Command, it's not actually inheriting any of the properties.
When you refer to something in Ruby, it first look up something in local bindings, if it fails, it then look up self.something. self represents a context of the evaluation, and this context changes on class definition class C; self; end, method definition class C; def m; self; end; end, however, it won't change on block definition. The block captures the current self at the point of block definition.
module Command
define :quit do
foo "bar" # self is Command, calls Command.foo by default
end
end
If you want to modify the self context inside a block, you can use BasicObject.instance_eval (or instance_exec, class_eval, class_exec).
For your example, the block passed to define should be evaluated under the self context of an instance of the concrete command.
Here is an example. I added some mock method definition in class Command::Command:
module Command
class Command
# remove this accessor if you want to make `name` readonly
attr_accessor :name
def exec(&block)
#exec = block
end
def foo(msg)
puts "FOO => #{msg}"
end
def run
#exec.call if #exec
end
end
def self.define(name, &block)
klass = Class.new(Command) do
define_method(:initialize) do
method(:name=).call(name) # it would be better to make it readonly
instance_eval(&block)
end
# readonly
# define_method(:name) { name }
end
::Command.const_set("Command_#{name}", klass)
end
define :quit do
foo "bar"
exec do
puts "EXEC => #{name}"
end
end
end
quit = Command::Command_quit.new #=> FOO => bar
quit.run #=> EXEC => quit
puts quit.class #=> Command::Command_quit
Your problem is that blocks preserve the value of self (among other things) - when you call init_block.call and execution jumps to the block passed to define, self is the module Command and not the instance of Command_quit
You should be ok if you change your initialize method to
class_initializer = Proc.new do
self.name = cmd_name # I assume you didn't just want to set a local variable
instance_eval(&init_block)
end
instance_eval executes the block, but with the receiver (in this case your instance of Command_quit as the subclass.
An exception to the "blocks preserve self" behaviour is define_method: in that case self will always be object on which the method is called, much like with a normal method.

Resources