Reputation: 343
I'm playing Ruby game and got stuck at this :
Implement class
Squirrel
in a way below API will be supported.squirrel = Squirrel.new squirrel.fight do jump kick punch jump end squirrel.actions #=> ['jump', 'kick', 'punch', 'jump']
I was experimenting with def find(&block)
and saving it as Proc later, but it probably shouldn't be done this way. I'll appreciate any hints.
Upvotes: 0
Views: 78
Reputation: 369633
This is a rather standard metaprogramming technique that was popularized many years ago by the BlankSlate
and XmlBase
classes in Jim Weirich's builder
Gem.
The main idea is to create a "blank slate" object, i.e. an object that has no methods, and then record all method calls on this object.
Here is one possible implementation:
class Squirrel
class Recorder < BasicObject
(instance_methods + private_instance_methods).each(&method(:undef_method))
def method_missing(method) @actions << method.to_s end
end
attr_reader :actions, :recorder
private
attr_writer :actions, :recorder
def initialize
self.actions = []
self.recorder = Recorder.allocate
Object.public_instance_method(:instance_variable_set).bind(recorder).(:@actions, actions)
end
public def fight(&block)
BasicObject.public_instance_method(:instance_eval).bind(recorder).(&block)
end
end
require 'minitest/autorun'
describe Squirrel do
before do @squirrel = Squirrel.new end
describe '#fight' do
it 'should record its actions' do
@squirrel.fight do jump; kick; punch; jump end
@squirrel.actions.must_equal %w[jump kick punch jump]
end
end
end
Upvotes: 1
Reputation: 19143
Try instance_eval
. And/or read this this:
Change the context/binding inside a block in ruby
Upvotes: 0
Reputation: 230561
I'll appreciate any hints
Sure. fight
should accept a block and instance_eval
it. jump
, kick
and others should be methods on the same class.
Upvotes: 2