Is it possible in Ruby to get a reference to methods of an object ( I would like to know if this can be done without procs/lambdas ) , for example , consider the following code :
class X
def initialize
#map = {}
setup_map
end
private
def setup_map
# #map["a"] = get reference to a method
# #map["b"] = get reference to b method
# #map["c"] = get referebce to c method
end
public
def call(a)
#map["a"](a) if a > 10
#map["b"](a) if a > 20
#map["c"](a) if a > 30
end
def a(arg)
puts "a was called with #{arg}"
end
def b(arg)
puts "b was called with #{arg}"
end
def c(arg)
puts "c was called with #{arg}"
end
end
Is it possible to do such thing ? I would like to avoid procs/lambdas because I want to be able to change the behaviour of A,B,C by subclassing .
You want Object#method:
---------------------------------------------------------- Object#method
obj.method(sym) => method
------------------------------------------------------------------------
Looks up the named method as a receiver in obj, returning a Method
object (or raising NameError). The Method object acts as a closure
in obj's object instance, so instance variables and the value of
self remain available.
class Demo
def initialize(n)
#iv = n
end
def hello()
"Hello, #iv = #{#iv}"
end
end
k = Demo.new(99)
m = k.method(:hello)
m.call #=> "Hello, #iv = 99"
l = Demo.new('Fred')
m = l.method("hello")
m.call #=> "Hello, #iv = Fred"
Now your code becomes:
private
def setup_map
#map = {
'a' => method(:a),
'b' => method(:b),
'c' => method(:c)
}
# or, more succinctly
# #map = Hash.new { |_map,name| _map[name] = method(name.to_sym) }
end
public
def call(arg)
#map["a"][arg] if arg > 10
#map["b"][arg] if arg > 20
#map["c"][arg] if arg > 30
end
You can do this with lambdas while maintaining the ability to change behavior in subclasses:
class X
def initialize
#map = {}
setup_map
end
private
def setup_map
#map["a"] = lambda { |a| a(a) }
#map["b"] = lambda { |a| b(a) }
#map["c"] = lambda { |a| c(a) }
end
public
def call(a)
#map["a"].call(a) if a > 10
#map["b"].call(a) if a > 20
#map["c"].call(a) if a > 30
end
def a(arg)
puts "a was called with #{arg}"
end
def b(arg)
puts "b was called with #{arg}"
end
def c(arg)
puts "c was called with #{arg}"
end
end
Ruby methods aren't first-class objects; it implements OO with message passing.
class X
def call(a)
self.send(:a, a) if a > 10
self.send(:b, a) if a > 20
self.send(:c, a) if a > 30
end
def a(arg)
puts "a was called with #{arg}"
end
def b(arg)
puts "b was called with #{arg}"
end
def c(arg)
puts "c was called with #{arg}"
end
end
Or just call them directly:
def call(a)
self.a(a) if a > 10
self.b(a) if a > 20
self.c(a) if a > 30
end
You can get a reference to the method by object.method(:method_name).
Eg: To get a reference to system method.
m = self.method(:system)
m.call('ls')
Related
Is it possible to declare dynamic methods with define_method that does an instance_exec of a block with arguments ? Something like this :
class D
def self.adapt (method,*args,&impl)
define_method(method) do
instance_exec(args,impl)
end
end
end
D.adapt(:foo,a,b) { a + b }
puts D.new.foo(1,2)
Yes, you can:
class D < Struct.new(:c)
def self.adapt (method, &impl)
define_method(method) do |*args|
instance_exec(*args, &impl)
end
end
end
D.adapt(:foo) { |a, b| a + b + c }
puts D.new(3).foo(1, 2)
# => 6
So, I want to do a list of methods and after use that list in for-loop.
class Hello
def func
pass
end
def func1
pass
end
list = [func, func1]
def loop_func
for func_instance in list
func_instance
end
end
end
But this code does not work. What is wrong?
class Hello
def foo
p "Foo"
end
def bar
p "Bar"
end
def loop_methods
self.class.instance_methods(false)
.each{ |m| m == __method__ || self.send(m) }
end
end
Hello.new.loop_methods
Is it possible to do something like:
class A
def a(var)
puts "do something with #{var}"
end
end
class B < A
def a(var)
var = var + "some modification"
#this is what I want to do:
super.a(var)
end
end
Thanks!
Unless I'm misreading your question, you should be able to call super by itself; e.g.:
class A
def a(var)
puts "do something with #{var}"
end
end
class B < A
def a(var)
var = var + "some modification"
#this is what I want to do:
#super.a(var)
super
end
end
v = B.new
v.a("hey")
produces
$ ruby test.rb
do something with heysome modification
You cannot use a method name with the super call
If you use super on it's own then it will call the super class' implementation passing along the same arguments
class A
def a(var)
puts var
end
end
class B < A
def a(var)
super
end
end
B.new.a(1) #=> 1
If you want to modify the variable then you can either reassign the var arg or use a super call passing in arguments
class A
def a(var)
puts var
end
end
class B < A
def a(var)
var = 2
super
end
end
B.new.a(1) #=> 2
or
class A
def a(var)
puts var
end
end
class B < A
def a(var)
super(2)
end
end
B.new.a(1) #=> 2
try the below:
class A
def a(var)
puts "do something with #{var}"
end
end
class B < A
def a(var)
var = var + "some modification"
#this is what I want to do:
#super.a(var)
self.class.superclass.instance_method(:a).bind(self).call 12
end
end
v = B.new
v.a("hello")
Output:
do something with 12
Another one:
class A
def a(var)
puts "do something with #{var}"
end
end
class B < A
alias :old_a :a
def a(var)
var = var + "some modification"
#this is what I want to do:
#super.a(var)
old_a 12
end
end
v = B.new
v.a("hello")
Output:
do something with 12
not quite understanding factory method here...
here is the respec line:
Temperature.from_celsius(50).in_celsius.should == 50
Here is what I have now:
getting errors...not quite sure how to satisfy this. thanks
class Temperature
attr_accessor :f
attr_accessor :c
def initialize(args)
#f = args[:f]
#c = args[:c]
end
def in_fahrenheit
#f or
(#c*9.0/5.0)+32
end
def in_celsius
#c or
(#f-32)*(5.0/9.0)
end
def self.from_celsius(c)
new c
end
end
This should help
class Temperature
def initialize c
#c = c
end
def in_celsius
#c
end
def in_fahrenheit
#c *9.0 /5.0 +32
end
# factory pattern typically instantiates a new object
def self.from_celsius(c)
new c
end
end
puts Temperature.from_celsius(50).in_celsius #=> 50
puts Temperature.from_celsius(100).in_fahrenheit #=> 212
I would recommend against attr_accessor :c unless you want users to have public access to temp.c. Without it, users will be forced to use temp.in_celsius or temp.in_fahrenheit
You need to assign to :c in the initialize method. Then you need self.from_celsius to return a new instance of Temperature. You probably want something like this:
class Temperature
attr_accessor :c
def initialize c
#c = c
end
def in_celsius
#c
end
def in_fahrenheit
9/5 * #c + 32
end
def self.from_celsius(num)
Temperature.new(num)
end
def self.from_fahrenheit(num)
Temperature.new((num-32)*5/9)
end
end
Now rspec shows true
1.9.1p378 :047 > Temperature.from_celsius(50).in_celsius.should == 50
=> true
1.9.1p378 :131 > Temperature.from_fahrenheit(32).in_celsius.should == 0
=> true
The reason you're getting "error: Can't covert symbol to integer –" is because you're in your Temperature.from_celsius(50) you're passing it an integer when you're supposed to pass it a key & symbol for the options hash.
initialized
class Temperature
def initialize(opts = {})
#options = opts
end
class factory method
def self.from_celsius(x)
Temperature.new(:c => x)
end
instance method
def in_celsius
if #options[:c] == nil
return (#options[:f]-32) * (5/9.to_f)
else
return #options[:c]
end
end
Essentially I'm wondering if the following can be done in Ruby.
So for example:
def bar(symbol)
# magic code goes here, it outputs "a = 100"
end
def foo
a = 100
bar(:a)
end
You have to pass foo's context to bar:
def foo
a = 100
bar(:a, binding)
end
def bar(sym, b)
puts "#{sym} is #{eval(sym.to_s, b)}"
end
There is no built-in way to get a callers binding in Ruby in 1.8.X or 1.9.X.
You can use https://github.com/banister/binding_of_caller to work around.
In MRI 2.0 you can use RubyVM::DebugInspector, see: https://github.com/banister/binding_of_caller/blob/master/lib/binding_of_caller/mri2.rb
Working sample in MRI 2.0:
require 'debug_inspector'
def bar(symbol)
RubyVM::DebugInspector.open do |inspector|
val = eval(symbol.to_s, inspector.frame_binding(2))
puts "#{symbol}: #{val}"
end
end
def foo
a = 100
bar(:a)
end
foo
# a: 100
Here's a easier syntax hack, using a passed in block binding:
def loginfo &block
what = yield.to_s
evaled = eval(what, block.binding)
Rails.logger.info "#{what} = #{evaled.inspect}"
end
called like this:
x = 1
loginfo{ :x }
will log out:
x = 1
Just FYI, here's a "hacky way".
This is my (re-)implementation of well-known ppp.rb:
#!/usr/bin/ruby
#
# better ppp.rb
#
require 'continuation' if RUBY_VERSION >= '1.9.0'
def ppp(*sym)
cc = nil
ok = false
set_trace_func lambda {|event, file, lineno, id, binding, klass|
if ok
set_trace_func nil
cc.call(binding)
else
ok = event == "return"
end
}
return unless bb = callcc{|c| cc = c; nil }
sym.map{|s| v = eval(s.to_s, bb); puts "#{s.inspect} = #{v}"; v }
end
a = 1
s = "hello"
ppp :a, :s
exit 0
This currently fails with 1.9.[012] due to a bug in ruby's set_trace_func.
Check article out Variable Bindings in Ruby
class Reference
def initialize(var_name, vars)
#getter = eval "lambda { #{var_name} }", vars
#setter = eval "lambda { |v| #{var_name} = v }", vars
end
def value
#getter.call
end
def value=(new_value)
#setter.call(new_value)
end
end
def ref(&block)
Reference.new(block.call, block.binding)
end
def bar(ref)
# magic code goes here, it outputs "a = 100"
p ref.value
end
def foo
a = 100
bar(ref{:a})
end
foo