How can I use a block to change the execution context in ruby?
It looks like you are sending methods to a class/module, so your example may be simply rewritten with use of Module#class_eval method:
name { Faker::Hacker.class_eval { "#{ingverb} #{adjective} #{noun}" } }
would invoke methods in the block passed to class_eval
on Faker::Hacker
class.
Changing the context/binding/scope of a ruby block (Rails-specific form_for)
The correct way to do this was to call the block like so: block.call(f)
and use concat for the other fields.
form_for(record, *(args << options.merge(:builder => builder))) do |f|
block.call(f)
concat hidden_field_tag 'key', "#{key}/${filename}"
concat hidden_field_tag 'AWSAccessKeyId', "#{access_key}"
concat hidden_field_tag 'acl', "#{acl}"
concat hidden_field_tag 'success_action_redirect', "#{redirect}"
concat hidden_field_tag 'policy', "#{policy}"
concat hidden_field_tag 'signature', "#{signature}"
end
How can I call a Proc that takes a block in a different context?
To solve this, you need to re-bind the Proc to the new class.
Here's your solution, leveraging some good code from Rails core_ext:
require 'rspec'
# Same as original post
class SomeClass
def instance_method(x)
"Hello #{x}"
end
end
# Same as original post
class AnotherClass
def instance_method(x)
"Goodbye #{x}"
end
def make_proc
Proc.new do |x, &block|
instance_method(block.call(x))
end
end
end
### SOLUTION ###
# From activesupport lib/active_support/core_ext/kernel/singleton_class.rb
module Kernel
# Returns the object's singleton class.
def singleton_class
class << self
self
end
end unless respond_to?(:singleton_class) # exists in 1.9.2
# class_eval on an object acts like singleton_class.class_eval.
def class_eval(*args, &block)
singleton_class.class_eval(*args, &block)
end
end
# From activesupport lib/active_support/core_ext/proc.rb
class Proc #:nodoc:
def bind(object)
block, time = self, Time.now
object.class_eval do
method_name = "__bind_#{time.to_i}_#{time.usec}"
define_method(method_name, &block)
method = instance_method(method_name)
remove_method(method_name)
method
end.bind(object)
end
end
# Here's the method you requested
def change_scope_of_proc(new_self, proc)
return proc.bind(new_self)
end
# Same as original post
describe "change_scope_of_proc" do
it "should change the instance method that is called" do
some_class = SomeClass.new
another_class = AnotherClass.new
proc = another_class.make_proc
fixed_proc = change_scope_of_proc(some_class, proc)
result = fixed_proc.call("Wor") do |x|
"#{x}ld"
end
result.should == "Hello World"
end
end
Change the binding of a Proc in Ruby
You can try the following hack:
class Proc
def call_with_vars(vars, *args)
Struct.new(*vars.keys).new(*vars.values).instance_exec(*args, &self)
end
end
To be used like this:
irb(main):001:0* lambda { foo }.call_with_vars(:foo => 3)
=> 3
irb(main):002:0> lambda { |a| foo + a }.call_with_vars({:foo => 3}, 1)
=> 4
This is not a very general solution, though. It would be better if we could give it Binding
instance instead of a Hash and do the following:
l = lambda { |a| foo + a }
foo = 3
l.call_with_binding(binding, 1) # => 4
Using the following, more complex hack, this exact behaviour can be achieved:
class LookupStack
def initialize(bindings = [])
@bindings = bindings
end
def method_missing(m, *args)
@bindings.reverse_each do |bind|
begin
method = eval("method(%s)" % m.inspect, bind)
rescue NameError
else
return method.call(*args)
end
begin
value = eval(m.to_s, bind)
return value
rescue NameError
end
end
raise NoMethodError
end
def push_binding(bind)
@bindings.push bind
end
def push_instance(obj)
@bindings.push obj.instance_eval { binding }
end
def push_hash(vars)
push_instance Struct.new(*vars.keys).new(*vars.values)
end
def run_proc(p, *args)
instance_exec(*args, &p)
end
end
class Proc
def call_with_binding(bind, *args)
LookupStack.new([bind]).run_proc(self, *args)
end
end
Basically we define ourselves a manual name lookup stack and instance_exec
our proc against it. This is a very flexible mechanism. It not only enables the implementation of call_with_binding
, it can also be used to build up much more complex lookup chains:
l = lambda { |a| local + func(2) + some_method(1) + var + a }
local = 1
def func(x) x end
class Foo < Struct.new(:add)
def some_method(x) x + add end
end
stack = LookupStack.new
stack.push_binding(binding)
stack.push_instance(Foo.new(2))
stack.push_hash(:var => 4)
p stack.run_proc(l, 5)
This prints 15, as expected :)
UPDATE: Code is now also available at Github. I use this for one my projects too now.
How to change self in a block like instance_eval method do?
You can write a method that accepts a proc argument, and then pass that as a proc argument to instance_eval.
class Foo
def bar(&b)
# Do something here first.
instance_eval &b
# Do something else here afterward, call it again, etc.
end
end
Foo.new.bar { puts self }
Yields
#<Foo:0x100329f00>
Changing the scope of a captured block in Ruby
After some research in the direction @bioneuralnet suggested, it's possible to create a new Proc
doing a new instance_eval
to restore the context. The binding
of the initial block is used to get the initial self
. So here is a (somewhat ugly) solution:
def capture_b(&block)
instance_eval(&block)
the_desired_self = block.binding.eval("self")
bk = @block
@block = Proc.new{ the_desired_self.instance_eval(&bk) }
self
end
It's not perfect, as it will be slower and since the original block won't be ==
to the resulting block; maybe there's a better solution?
Related Topics
What's the Best Way to Talk to a Database While Using Sinatra
Can't Get Rack-Cors Working in Rails Application
Bundle Can't Install Rmagick Gem on MAC Osx 10.7
Is There a Literal Notation for an Array of Symbols
How to Run Rails Console in the Test Environment and Load Test_Helper.Rb
How to Chop a String into Chunks of a Given Length in Ruby
Access Instance Variable from Outside the Class
One Liner in Ruby for Displaying a Prompt, Getting Input, and Assigning to a Variable
Render an Erb Template with Values from a Hash
Convert an Array of Integers into an Array of Strings in Ruby
Undefined Instance Method "Respond_To" in Rails 5 API Controller
Aws Elastic Beanstalk - How to Upgrade Existing Environment from Ruby 2.1 to Ruby 2.2
Rspec: How to Stub an Instance Method Called by Constructor
Rails: Copying Attributes from an Object to Another Using the "Attributes" Method
How to Use Xmlns Declarations with Xpath in Nokogiri
How to Get Attributes That Were Defined Through Attr_Reader or Attr_Accessor