How can i treat functions like objects in ruby? - ruby

In my ruby project, i want to create a task manager class which will periodically iterate through an array of registered functions. I need to pass functions as argument to the register function, create a thread and when the thread timer fires, it needs to call each function in array.
class TaskMan
##func_array = Array.new
def self.register_func &arg_func
##func_array.push arg_func
end
def self.run_server
loop do
##func_array.each do |func|
func.call
end
sleep 60
end
end
end
class Callee
def self.func_0
puts "func_0 called."
end
def self.func_1
puts "func_1 called."
end
end
TaskMan.register_func Callee.func_0
TaskMan.register_func Callee.func_1
taskman_thread = Thread.new do
TaskMan.run_server
end
taskman_thread.join
Basically, this is what i want to do, but i'm not sure how to use to_proc and call, to push in the functions and call them.
Other solutions are also appreciated, but i don't want to avoid learning passing functions as arguments, putting them in array and calling them.
Thanks.

Use the method method to get the class methods as callable objects (Method instances in this case) and then apply & to those objects:
TaskMan.register_func &Callee.method(:func_0)
TaskMan.register_func &Callee.method(:func_1)
Now you can throw all sorts of things into the func_array:
TaskMan.register_func &SomeClass.method(:m) # class method
TaskMan.register_func &some_object.method(:m) # an instance method
TaskMan.register_func &some_lambda # a lambda function
TaskMan.register_func { puts "pancakes" } # a block
# etc.

You can instead pass the receiver and the function you intend calling as below:
class TaskMan
##func_array = Array.new
def self.register_func(receiver, func)
##func_array.push([receiver, func])
end
def self.run_server
loop do
##func_array.each do |(receiver, func)|
receiver.method(func).call
end
sleep 60
end
end
end
class Callee
def self.func_0
puts "func_0 called."
end
def self.func_1
puts "func_1 called."
end
end
TaskMan.register_func Callee, :func_0
TaskMan.register_func Callee, :func_1
taskman_thread = Thread.new do
TaskMan.run_server
end
taskman_thread.join

Let's say you have methods func_0 and func_1. To call them using array iteration you can do this
methods = [:func_0, :func_1]
methods.each do |m|
send(m)
end

Related

Before and after event handlers in Ruby

Is there a way to watch objects so that a block or lamba is run before and/or after calls on specific methods for that object? For example, something like this:
watch(lionel, :say_you, :before) do
puts '[before say_you]'
end
lionel.say_you()
# outputs [before say_you]
An important part of my requirement is that I don't want to monkey patch the object at all. There should be no changes to the object. I just want to watch it, not change it (Heisenberg would be proud).
I've written a module that sort of does what I describe. Unfortunately, it has some bad side-effects: it slows down the system and never cleans up its hash of object ids. So I wouldn't use it in production, but it shows the concept of watching an object without monkey patching it.
# watcher module
# (Adrian is one of The Watchmen)
module Adrian
#registry = {}
EVENT_IDS = {:before => :call, :after => :return}
# watch
def self.watch(obj, method, event_id, &handler)
# get event type
event = EVENT_IDS[event_id]
event or raise 'unknown-event: unknown event param'
# get object id
obj_id = obj.object_id
# store handler
#registry[obj_id] ||= {}
#registry[obj_id][method] ||= {}
#registry[obj_id][method][event] ||= []
#registry[obj_id][method][event].push(handler)
end
# trace
TracePoint.trace(:call, :return) do |tp|
# get watched object or return
if handlers = #registry[tp.binding.receiver.object_id]
handle tp, handlers
end
end
# handle
def self.handle(tp, handlers)
# $tm.hrm
# if this method is watched
callee = handlers[tp.callee_id]
callee or return
# get blocks
blocks = callee[tp.event]
blocks or return
# loop through series
blocks.each do |block|
block.call
end
end
end
# Lionel class
class Lionel
def say_you
puts 'you'
end
def say_me
puts 'me'
end
end
# instance
lionel = Lionel.new()
# before
Adrian.watch(lionel, :say_you, :before) do
puts '[before say_you]'
end
# after
Adrian.watch(lionel, :say_me, :after) do
puts '[after say_me]'
end
# call method
lionel.say_you
lionel.say_me
That outputs:
[before say_you]
you
me
[after say_me]
Here is a solution making use of the singleton class for the object you want to watch. I am not sure it's good solution, but it might be at least interesting.
The watch method takes, and object, a method name, and an order parameter along with a block. The order parameter specifies when you want to run the block, ie before, after or around the watched method.
The watch method saves the original instance method, then defines a singleton method with the same name as the original method on the object. The singleton method calls the passed block at the time or times specified by the order parameter. The singleton method also calls with the original instance method bound to the object at the appropriate time.
Perhaps there is a better way to call an instance method from with in a singleton method, but I have not found one. I am also not sure how this would work with various argument types, ie keyword args, etc.
Here is the watch method.
def watch(obj,method_name,order,&block)
inst_method = obj.class.instance_method(method_name)
obj.define_singleton_method(method_name) do |*args|
block.call if [:before,:around].include?(order)
inst_method.bind(self).call(*args)
block.call if [:after,:around].include?(order)
end
end
Usage examples:
class Foo
def initialize(type)
#type = type
end
def i_method(arg)
puts "#{#type} instance method called with arg = #{arg}"
end
end
watched_inst = Foo.new("watched")
unwatched_inst = Foo.new("unwatched")
watch(watched_inst,:i_method,:before) do
puts "do before"
end
watched_inst.i_method("foo")
unwatched_inst.i_method("bar")
watched_inst.i_method("foo_bar")
watch(watched_inst,:i_method,:after) do
puts "do after"
end
watched_inst.i_method("hello")
watch(watched_inst,:i_method,:around) do
puts "do around"
end
watched_inst.i_method("hello")
unwatched_inst.i_method("foo")
output:
do before
watched instance method called with arg = foo
unwatched instance method called with arg = bar
do before
watched instance method called with arg = foo_bar
watched instance method called with arg = hello
do after
do around
watched instance method called with arg = hello
do around
unwatched instance method called with arg = foo

Get all instance methods on top of the class definition

I'm trying to wrap all instance methods of TestClass to perform code before and after an instance method is called. So far, this code is working:
module Wrapper
def wrap(*methods)
prependable_module = Module.new do
methods.each do |m|
define_method(m) do |*args, &block|
p 1
super(*args, &block)
p 3
end
end
end
prepend prependable_module
end
end
class TestClass
extend Wrapper
wrap :instance_method1
def instance_method1
p 2
end
end
TestClass.new.instance_method1 # => 1, 2, 3
I can call wrap with all method names as arguments. If I try to wrap all methods without listing them individually, I need to call it using instance_methods(false) at the bottom of the class definition.
class TestClass
extend Wrapper
def instance_method1
p 2
end
wrap(*instance_methods(false))
end
In Rails, all callback methods like before_action or after_create are usually called on top of the class definition. My goal is to call wrap on top of the class definition as well (without listing all methods individually). In this case, I can't call instance_methods(false) on top of the class definition, because at this point no method has been defined.
Thanks for your help!
Update
Thanks to Kimmo Lehto's approach I can wrap every instance method using the method_added hook. I don't want to prepend a new module for every defined method, so I add all overridden methods to the same module.
module Wrapper
def method_added(method_name)
tmp_module = find_or_initialize_module
return if tmp_module.instance_methods(false).include?(method_name)
tmp_module.define_method(method_name) do |*args, &block|
p 1
super(*args, &block)
p 3
end
end
def find_or_initialize_module
module_name = "#{name}Wrapper"
module_idx = ancestors.map(&:to_s).index(module_name)
unless module_idx
prepend Object.const_set(module_name, Module.new)
return find_or_initialize_module
end
ancestors[module_idx]
end
end
class TestClass
extend Wrapper
def instance_method1
p 2
end
end
tc = TestClass.new
tc.instance_method1 # => 1, 2, 3
You could use the Module#method_added hook to automatically wrap any methods that are added.
You will need some magic to not get a stack overflow from an infinite loop.
Another option is to use TracePoint to trigger the wrapping once the class has been defined. You can use the Module#extended to set up the tracepoint. Something like:
module Finalizer
def self.extended(obj)
TracePoint.trace(:end) do |t|
if obj == t.self
obj.finalize
t.disable
end
end
end
def finalize
wrap(*instance_methods(false))
end
end
Classes are usually not exactly "closed" unless you explicitly .freeze them so it's a bit of a hacky solution and will not trigger if methods are added afterwards. method_added is probably your best bet.

Yielding inside a block

I've been given this class which includes the Enumerable module:
class Team
include Enumerable # LOTS of functionality
attr_accessor :name, :players
def initialize (name)
#name = name
#players = []
end
def add_players (*players) # splat
#players += players
end
def to_s
"#{#name} team: #{#players.join(", ")}"
end
def each
#players.each { |player| yield player }
end
end
I know that yield is used to be able to call a block inside a method when a block is passed as a method argument. What then does the yield do inside this block?
Proper functioning of the included Enumerable interface's methods requires that the class implement an each method that yields successive values. The implementation in your sample code effectively delegates this logic to that of Array's each method (since #players is an Array).
Replacing yield with return would result in no values being provided, and bypassing any code block that was passed in.
You could experiment with the each implementation to verify this.

How to pass a function as method argument

I am trying to pass a function as an argument to a method of class. I know I need to use proc, but I am not sure I am using the right syntax. This is my attempt.
module MyApp;end
module MyApp::Stats
def self.sum(a)
a.inject(0){ |accum, i| accum + i }
end
def self.mean(a)
sum(a) / a.length.to_f
end
# Many more functions....
def self.sum_proc
Proc.new{|x| sum(x) }
end
def self.mean_proc
Proc.new{|x| mean(x)}
end
# And many more procs
end
class MyData
attr_reader :values
attr_reader :aggregates
def initialize(array)
#values = array
#aggregates = {}
end
def aggregate(aggregator)
puts aggregator.call(#values)
#I would also like to create a hash of aggregator. Something like:
#aggregates["aggregator"] = aggregator.call(#values)
end
end
I can then do
ar = [1,2,3,4,5,6,7,8,9]
data = MyData.new(ar)
And call the aggregate method in various ways:
aggregator = Proc.new{|x| MyApp::Stats.sum(x)}
data.aggregate(aggregator)
data.aggregate(Proc.new{|x| MyApp::Stats.mean(x)} )
data.aggregate(Proc.new{|x| x.count{|y| y > 3.0} })
data.aggregate(MyApp::Stats.sum_proc)
data.aggregate(MyApp::Stats.mean_proc)
I have two issues with this code. First it seems redundant as I have to define the aggregator first and then the associated proc, e.g. sum and sum_proc. Second, I wonder how I could pass any of the standard enumerator methods without defining a proc for it: say count or first.
Finally, I would like to create a hash for the aggregators so that I could do:
puts data.aggregates["sum"]
puts data.aggregates["mean"]
Methods aren't objects in Ruby. You can't pass a method as an argument because you can only pass objects as arguments.
However, you can get a Method proxy object representing a method by calling the method method and passing the name of the method as an argument. Method proxy objects duck-type Proc, so they respond to arity, parameters, and most importantly to_proc and call.
The idiomatic way of taking a single first-class procedure as an argument, is to take a block like this:
def aggregate
yield #values
end
and pass a method using the unary prefix & operator:
data.aggregate(&MyApp::Stats.:sum)

Using define_method in a loop to create methods in Ruby (Through Selenium Webdriver)

I'm trying to create a method to dynamically do the following: (as I will have to implement this on about 30 different sets of sub-classes)
def t1
FooT1.new
end
def t2
FooT2.new
end
def t3
FooT3.new
end
Where there will be 2 variables in the method generation, the tab number(t1...tx) and the name of the class (Foo)
I tried the following, but I'm new to Ruby and can not get this working.
def method_generator(num_tabs, class_name)
1.upto(num_tabs) do |i|
define_method("t#{i}") do
"#{class_name}_t#{i}".new
end
end
end
Then call it in the sub-class like so:
method_generator(3, "Bar")
I'm aware I'm probably quite far off in implementing this, so any help is appreciated.
Just do as below :
def method_generator(num_tabs, class_name)
1.upto(num_tabs) do |i|
class_name.send(:define_method,"t#{i}") do
"#{class_name}_t#{i}".new
end
end
end
Module#define_method is a private method, thus you can't call it on the class_name like class_name.define_method(:name) do ..end, as private method call not allows explicit receiver. But to do so Object#send will help you, as this method is here for this kind of scenarios, where you can't call private method by explicit receiver.
Lets verify with an example, if this tricks works or not.
class Foo;end
def method_generator(num_tabs, class_name)
1.upto(num_tabs) do |i|
class_name.send(:define_method,"t#{i}") do
"#{class_name}_t#{i}".new
end
end
end
method_generator(3,Foo)
Foo.instance_methods(false)
# => [:t1, :t2, :t3] # see here 3 instance methods has been created of class Foo

Resources