Binding within a block - ruby

I'd like to use a block within method initialize. With this code:
class Settlement
attr_reader :url, :parameters
def initialize(&block)
instance_eval block.call
end
def parameters(x) ; #parameters = x; end
def url(x) ; #url = x; end
end
settlement = Settlement.new do
url "https://xxxxx"
parameters("ffff")
end
I got the error message below:
NoMethodError - undefined method parameters
Any ideas?

When you call instance_eval block.call block.call is evaluated before instance_eval is called. This means the block is called without the instance binding.
This should work:
def initialize(&block)
instance_eval &block
end

Related

What's the technique for calling back methods across objects using "send" in Ruby?

I'm doing some programming that involves asynchronous callbacks in Ruby, and need to pass the callback method to another object (which may or may not be statically called). The issue I have is the syntax of the callback to the instance - I know it's fairly complicated, but I'm not sure I can make it simpler. Here's what I have:
class OBJA
def self.staticMethod(text, returnCall)
puts "objA.staticMethod: #{text}"
OBJB.send(returnCall, "Call back from objA.staticMethod")
end
def instanceMethod(text, returnCall)
puts "objA.instanceMethod: #{text}"
OBJB.send(returnCall, "Call back from objA.instanceMethod")
end
end
class OBJB
def starterMethod
OBJA.staticMethod("foo", :returnedCall)
OBJA.new.instanceMethod("bar", :returnedCall)
end
def returnedCall(text)
puts text
end
end
You can execute it by doing the following:
b = OBJB.new
b.starterMethod
Thanks!
The issue I have is the syntax of the callback to the instance
You have to call an instance method with an instance. And if you call a class method on a class, e.g. OBJB.send(...), the class method has to be defined.
class OBJA
def self.staticMethod(text, methName)
puts "objA.staticMethod: #{text}"
OBJB.send(methName, "Call back from objA.staticMethod")
end
def instanceMethod(text, methName)
puts "objA.instanceMethod: #{text}"
OBJB.new.send(methName, "Call back from objA.instanceMethod")
end
end
class OBJB
def starterMethod
OBJA.staticMethod("foo", :returnedCall)
OBJA.new.instanceMethod("bar", :returnedCall)
end
def self.returnedCall(text)
puts text
end
def returnedCall(text)
puts text
end
end
b = OBJB.new
b.starterMethod
--output:--
objA.staticMethod: foo
Call back from objA.staticMethod
objA.instanceMethod: bar
Call back from objA.instanceMethod
You could also pass blocks to the OBJA methods:
class OBJA
def self.staticMethod(text, &block)
puts "objA.staticMethod: #{text}"
block.call("Call back from objA.staticMethod")
end
def instanceMethod(text, &block)
puts "objA.instanceMethod: #{text}"
block.call("Call back from objA.instanceMethod")
end
end
class OBJB
def starterMethod
OBJA.staticMethod("foo") {|str| puts str}
OBJA.new.instanceMethod("bar") {|str| puts str}
end
end
b = OBJB.new
b.starterMethod
--output:--
objA.staticMethod: foo
Call back from objA.staticMethod
objA.instanceMethod: bar
Call back from objA.instanceMethod
Or, more illustrative of the closure:
class OBJA
def self.staticMethod(text, &block)
puts "objA.staticMethod: #{text}"
block.call
end
def instanceMethod(text, &block)
puts "objA.instanceMethod: #{text}"
block.call
end
end
class OBJB
def initialize
#x = 1
#y = 2
end
def starterMethod
OBJA.staticMethod("foo") {puts instance_variable_get(:#x)}
OBJA.new.instanceMethod("bar") {puts instance_variable_get(:#y)}
end
end
b = OBJB.new
b.starterMethod
--output:--
objA.staticMethod: foo
1
objA.instanceMethod: bar
2
I don't know if this will help you or not, but this trick is used all over the Ruby frameworks. Ruby being the Wild West of programming languages, it will actually allow you to ignore the closure. That's useful when you want to accept a block from some code, but you don't want to execute the block in the context in which the block was defined--instead you want to execute the block in a context you create.
class OBJA
#x = 10 #Instance variables attach themselves to whatever object is
#y = 20 #self at the time they are created. Inside a class, but outside
#any defs, self is equal to the class, so these statements
#create what are known as 'class instance variables' (##variables
#aren't used in ruby because they don't behave 'correctly').
def self.staticMethod(text, &block)
puts "objA.staticMethod: #{text}"
instance_eval &block #See comment (1) below
end
def instanceMethod(text, &block)
puts "objA.instanceMethod: #{text}"
block.call
end
end
class OBJB
def initialize
#x = 1
#y = 2
end
def starterMethod
OBJA.staticMethod("foo") {puts instance_variable_get(:#x)}
OBJA.new.instanceMethod("bar") {puts instance_variable_get(:#y)}
end
end
b = OBJB.new
b.starterMethod
--output:--
objA.staticMethod: foo
10 #<--CHECK THIS OUT!!
objA.instanceMethod: bar
2
(1) When you call a method without a receiver, ruby uses self to call the method. Inside a class method, self is equal to the class, so the instance_eval() call is equivalent to:
OBJA.instance_eval &block
instance_eval() is used to change the value of the self variable to the receiver. But self was already equal to OBJA?? What instance_eval() succeeds in doing is changing the value of the self variable that it's block sees! By converting the block variable to become instance_eval's block, you actually change the blocks context, i.e. the variables that the block's code sees.
At the moment your callback OBJB.send is called on the class, but your returnedCall method is an instance method. There are two ways to fix this:
Call the callback on an instance instead of on the class by changing lines OBJB.send(... to
OBJB.new.send(...
Or by making the callback method a class method by changing def returnedCall(... to
def self.returnedCall(text)

How to intercept class method calls, not just instance method calls

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

Undefined method `lambda' in ruby code

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

Singleton method error from bind when called with the same metaclass

I'm writing aspect-ish code more or less lifted from the chosen solution for this question that looks like the following:
class Module
def add_logging klass, *method_names
method_names.each do |method_name|
original_method = instance_method method_name
define_method method_name do |*args, &blk|
log.debug("#{klass}.#{method_name} called")
original_method.bind(klass).call(*args, &blk)
end
end
end
end
The solution in the other post doesn't require the klass parameter, but it only works for instance methods, whereas I hope to call my code like this:
module MyModule
def MyModule.module_method
p "hello"
end
class << self
add_logging self, :module_method1
end
end
Unfortunately when I run this code I get in 'bind': singleton method called for a different object (TypeError). Seeing as I pass self in the context of the class << self block, I don't understand why the bind call in the above code thinks it isn't binding to the exact same metaclass.
This should work for you:
class Module
def add_logging(*method_names)
method_names.each do |method_name|
original_method = method(method_name).unbind
define_singleton_method(method_name) do |*args, &blk|
puts "#{self}.#{method_name} called"
original_method.bind(self).call(*args, &blk)
end
end
end
end
# class method example
module MyModule
def self.module_method1
puts "hello"
end
add_logging :module_method1
end
MyModule.module_method1
# output:
#
# MyModule.module_method1 called
# hello

How can I intercept method call in ruby?

I currently have a superclass which has a function that I want all the subclass to call within each of its function. The function is supposed to behave like a before_filter function in rails but I am not sure on how to go about implementing before_filter. Here is an example
class Superclass
def before_each_method
puts "Before Method" #this is supposed to be invoked by each extending class' method
end
end
class Subclass < Superclass
def my_method
#when this method is called, before_each_method method is supposed to get invoked
end
end
This is one way to do it:
class Superclass
def before_each_method name
p [:before_method, name]
end
def self.method_added name
return if #__last_methods_added && #__last_methods_added.include?(name)
with = :"#{name}_with_before_each_method"
without = :"#{name}_without_before_each_method"
#__last_methods_added = [name, with, without]
define_method with do |*args, &block|
before_each_method name
send without, *args, &block
end
alias_method without, name
alias_method name, with
#__last_methods_added = nil
end
end
class SubclassA < Superclass
def my_new_method
p :my_new_method
end
def my_new_other_method
p :my_new_other_method
end
end
SubclassA.new.my_new_method
SubclassA.new.my_new_other_method
This will create a wrapper method using the alias_method_chaining method as soon as the method you'd like to wrap is defined in the subclass.
This is my solution:
require 'active_support/all'
module BeforeEach
extend ActiveSupport::Concern
module InstanceMethods
def before_each
raise NotImplementedError('Please define before_each method')
end
end
module ClassMethods
def method_added(method)
method = method.to_s.gsub(/_with(out)?_before$/, '')
with_method, without_method = "#{method}_with_before", "#{method}_without_before"
return if method == 'before_each' or method_defined?(with_method)
define_method(with_method) do |*args, &block|
before_each
send(without_method, *args, &block)
end
alias_method_chain(method, :before)
end
end
end
To use it, just include BeforeEach into your class like so:
class Superclass
include BeforeEach
def before_each
puts "Before Method" #this is supposed to be invoked by each extending class' method
end
end
class Subclass < Superclass
def my_method
#when this method is called, before_each_method method is supposed to get invoked
end
end
Subclass.new.my_method
# => Before Method
Hopefully this will work for you!
class BalanceChart < BalanceFind
include ExecutionHooks
attr_reader :options
def initialize(options = {})
#options = options
#begin_at = #options[:begin_at]
end
def months_used
range.map{|date| I18n.l date, format: :month_year}.uniq!
end
before_hook :months_data, :months_used, :debits_amount
end
module ExecutionHooks
def self.included(base)
base.send :extend, ClassMethods
end
module ClassMethods
def before
#hooks.each do |name|
m = instance_method(name)
define_method(name) do |*args, &block|
return if #begin_at.blank? ## the code you can execute before methods
m.bind(self).(*args, &block) ## your old code in the method of the class
end
end
end
def before_hook(*method_name)
#hooks = method_name
before
end
def hooks
#hooks ||= []
end
end
end

Resources