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
Related
I am trying to dynamically define methods and pass a parameter to it. This is what I have so far:
class Commands
end
#commands = Commands.new
def route(name, &block)
Commands.send(:define_method, name, &block)
end
route 'foo' do
puts 'oh hai'
puts data
end
When route is called, this is what I expect to be executed:
data = {name: 'foo', optional: 'bar'}
#commands.send(data['name'].to_sym, data)
This fails because data isn't defined unless I do something like:
route 'foo' do |data|
puts 'oh hai'
puts data
end
Is there a way to pass a default parameter? Seems like it's sinatrarb does it, but I haven't been able to make head or tails from the code yet.
Your question isn't very clear, but I suspect that the functionality you're looking for is the sort usually achieved with instance_eval and friends. instance_eval evaluates the given block in the context of its callee, so that the callee's instance methods and instance variables are available inside the block.
Here's a really elementary example:
"foo".instance_eval do
puts size
puts upcase
end
# -> 3
# -> FOO
size and upcase aren't parameters we've given to the block, they're methods that are already available in the context of the String object.
With that in mind, you might start with something like this:
class Route
attr_reader :data
def initialize(name)
#data = { name: name, optional: "bar" }
end
end
rt = Route.new("foo")
rt.instance_eval do
puts 'oh hai'
puts data
end
# -> oh hai
# -> {:name=>"foo", :optional=>"bar"}
Here we've defined a Route class whose instances can serve as a context in which to evaluate blocks given to the route method:
def route(name, &block)
rt = Route.new(name)
Commands.send(:define_method, name) do
rt.instance_eval(&block)
end
end
route 'foo' do
puts 'oh hai'
puts data
end
route 'qux' do
puts "My name is %{name} and optional is %{optional}" % data
end
#commands = Commands.new
#commands.foo
# -> oh hai
# -> {:name=>"foo", :optional=>"bar"}
#commands.qux
# -> My name is qux and optional is bar
The block is what you are passing to route method; you are free to pass whatever you want, hence ruby won’t complain against any number of parameters (till the #commands.foo is actually called.)
One might assign a default value to block parameter:
λ = lambda do |data = 'hello'|
puts data
end
and pass this lambda to route:
route 'foo', &λ
#⇒ "hello"
AFAIK, there is no way to explicitly assign local variable in foreign binding using some kind of hack like:
block.binding.local_variable_set(:data, 'hello')
How can I create an opbjet that's totally lazy by itself? I have a block, and I want to pass around (as a dependency) the "current value" (at call time) of the block instead of the value at dependency injection time.
I can't actually pass around a lambda because all the services expect an actual object, so they won't send :call to them, just access them.
This (oversimplified) example might clarify the situation:
class Timer
def initialize(current_time)
#current_time = current_time
end
def print_current_time
print #current_time
end
end
class Injector
def current_time
# a lazy object that when accessed actually calls the lambda below
# every single time.
end
def current_time_lazy
-> { Time.now }
end
def instantiate(class_name)
# search for the class, look at the constructor and
# create an instance with the dependencies injected by
# name
# but to be simple
if class_name == "Timer"
Timer.new(current_time)
end
end
end
timer = Injector.new.instantiate("Timer")
timer.print_current_time # => some time
sleep 2
timer.print_current_time # => some *different* time
The actual situation implies passing around the current_user but depending on the situation the current user might change after those values are injected.
I would really appreciate any suggestion (even if for now I will carefully sort the dependency injection code so this doesn't happen, but I think it's pretty fragile)
This should help :
class Timer
def initialize(current_time)
#current_time = current_time
end
def print_current_time
puts #current_time
end
end
class LazyMaker < BasicObject
def self.instantiate(class_name, lambada)
if class_name == 'Timer'
::Timer.new(new(class_name, lambada))
end
end
def initialize(class_name, lambada)
#lambada = lambada
end
def method_missing(method, *args)
#lambada.call.send(method, *args)
end
end
timer = LazyMaker.instantiate('Timer', -> { Time.now })
timer.print_current_time # some time
sleep 2
timer.print_current_time # some other time
I'm trying to use delegation to implement it, so that I can call the block first, get a new object and redirect the method call to it. Why this way ? Because basically, accessing an object to do something means to call a method on it. For instance, in print #current_time, it sends #current_time.to_s.
But since almost all objects will have a few methods inherited from standard base classes in Ruby like Object, LazyMaker also has methods like to_s. So I thought of making just the LazyMaker inherit from BasicObject, which is a blank class. So almost all of the methods get delegated.
But yeah, there might be another way to do this.
I want to build an API client that has an interface similar to rails active record. I want the consumers to be able to chain methods and after the last method is chained, the client requests a url based on the methods called. So it's method chaining with some lazy evaluation. I looked into Active Record but this is very complicated (spawning proceses, etc).
Here is a toy example of the sort of thing I am talking about. You can chain as many 'bar' methods together as you like before calling 'get', like this:
puts Foo.bar.bar.get # => 'bar,bar'
puts Foo.bar.bar.bar.get # => 'bar,bar,bar'
I have successfully implemented this, but I would rather not need to call the 'get' method. So what I want is this:
puts Foo.bar.bar # => 'bar,bar'
But my current implementation does this:
puts Foo.bar.bar #=> [:bar, :bar]
I have thought of overriding array methods like each and to_s but I am sure there is a better solution.
How would I chain the methods and know which was the last one so I could return something like the string returned in the get method?
Here is my current implementation:
#!/usr/bin/env ruby
class Bar
def get(args)
# does a request to an API and returns things but this will do for now.
args.join(',')
end
end
class Foo < Array
def self.bar
#q = new
#q << :bar
#q
end
def bar
self << :bar
self
end
def get
Bar.new.get(self)
end
end
Also see: Ruby Challenge - Method chaining and Lazy Evaluation
How it works with activerecord is that the relation is a wrapper around the array, delegating any undefined method to this internal array (called target). So what you need is to start with a BasicObject instead of Object:
class Foo < BasicObject
then you need to create internal variable, to which you will delegate all the methods:
def method_missing(*args, &block)
reload! unless loaded?
#target.send(*args, &block)
end
def reload!
# your logic to populate target, e.g:
#target = #counter
#loaded = true
end
def loaded?
!!#loaded
end
To chain methods, your methods need to return new instance of your class, e.g:
def initialize(counter=0)
#counter = counter
end
def bar
_class.new(#counter + 1)
end
private
# BasicObject does not define class method. If you want to wrap your target
# completely (like ActiveRecord does before rails 4), you want to delegate it
# to #target as well. Still you need to access the instance class to create
# new instances. That's the way (if there are any suggestion how to improve it,
# please comment!)
def _class
(class << self; self end).superclass
end
Now you can check it in action:
p Foo.new.bar.bar.bar #=> 3
(f = Foo.new) && nil # '&& nil' added to prevent execution of inspect
# object in the console , as it will force #target
# to be loaded
f.loaded? #=> false
puts f #=> 0
f.loaded? #=> true
A (very simple, maybe simplistic) option would be to implement the to_s method - as it is used to "coerce" to string (for instance in a puts), you could have your specific "this is the end of the chain" code there.
I need to load a YAML file (I'm experimenting with SettingsLogic) and I'd like the instance to load the YAML with the same name as it. Briefly:
class MySettings < SettingsLogic
source "whatever_the_instance_is_called.yml"
# Do some other stuff here
end
basic_config = MySettings.new # loads & parses basic_config.yml
advanced_cfg = MySettings.new # loads & parses advanced_cfg.yml
...and so on...
The reason for this I don't yet know what configuration files I'll have to load, and typing:
my_config = MySettings.new("my_config.yml")
or
my_config = MySettings.new(:MyConfig)
just seems to be repeating myself.
I took a look around both Google and Stackoverflow, and the closest I came to an answer is either "Get Instance Name" or a discussion about how meaningless an instance name is! (I'm probably getting the query wrong, however.)
I have tried instance#class, and instance#name; I also tried instance#_id2ref(self).
What am I missing?!
Thanks in advance!
O.K., so with local variable assignment, there are snags, such as that assignment might occur slightly later than local variable symbol addition to the local variable list. But here is my module ConstMagicErsatz that I used to implement something similar to out-of-the box Ruby constant magic:
a = Class.new
a.name #=> nil - anonymous
ABC = a # constant magic at work
a.name #=> "ABC"
The advantage here is that you don't have to write ABC = Class.new( name: "ABC" ), name gets assigned 'magically'. This also works with Struct class:
Koko = Struct.new
Koko.name #=> "Koko"
but with no other classes. So here goes my ConstMagicErsatz that allows you to do
class MySettings < SettingsLogic
include ConstMagicErsatz
end
ABC = MySettings.new
ABC.name #=> "ABC"
As well as
a = MySettings.new name: "ABC"
a.name #=> "ABC"
Here it goes:
module ConstMagicErsatz
def self.included receiver
receiver.class_variable_set :##instances, Hash.new
receiver.class_variable_set :##nameless_instances, Array.new
receiver.extend ConstMagicClassMethods
end
# The receiver class will obtain #name pseudo getter method.
def name
self.class.const_magic
name_string = self.class.instances[ self ].to_s
name_string.nil? ? nil : name_string.demodulize
end
# The receiver class will obtain #name setter method
def name= ɴ
self.class.const_magic
self.class.instances[ self ] = ɴ.to_s
end
module ConstMagicClassMethods
# #new method will consume either:
# 1. any parameter named :name or :ɴ from among the named parameters,
# or,
# 2. the first parameter from among the ordered parameters,
# and invoke #new of the receiver class with the remaining arguments.
def new( *args, &block )
oo = args.extract_options!
# consume :name named argument if it was supplied
ɴς = if oo[:name] then oo.delete( :name ).to_s
elsif oo[:ɴ] then oo.delete( :ɴ ).to_s
else nil end
# but do not consume the first ordered argument
# and call #new method of the receiver class with the remaining args:
instance = super *args, oo, &block
# having obtained the instance, attach the name to it
instances.merge!( instance => ɴς )
return instance
end
# The method will search the namespace for constants to which the objects
# of the receiver class, that are so far nameless, are assigned, and name
# them by the first such constant found. The method returns the number of
# remaining nameless instances.
def const_magic
self.nameless_instances =
class_variable_get( :##instances ).select{ |key, val| val.null? }.keys
return 0 if nameless_instances.size == 0
catch :no_nameless_instances do search_namespace_and_subspaces Object end
return nameless_instances.size
end # def const_magic
# ##instances getter and setter for the target class
def instances; const_magic; class_variable_get :##instances end
def instances= val; class_variable_set :##instances, val end
# ##nameless_instances getter for the target class
def nameless_instances; class_variable_get :##nameless_instances end
def nameless_instances= val; class_variable_set :##nameless_instances, val end
private
# Checks all the constants in some module's namespace, recursivy
def search_namespace_and_subspaces( ɱodule, occupied = [] )
occupied << ɱodule.object_id # mark the module "occupied"
# Get all the constants of ɱodule namespace (in reverse - more effic.)
const_symbols = ɱodule.constants( false ).reverse
# check contents of these constant for wanted objects
const_symbols.each do |sym|
# puts "#{ɱodule}::#{sym}" # DEBUG
# get the constant contents
obj = ɱodule.const_get( sym ) rescue nil
# is it a wanted object?
if nameless_instances.map( &:object_id ).include? obj.object_id then
class_variable_get( :##instances )[ obj ] = ɱodule.name + "::#{sym}"
nameless_instances.delete obj
# and stop working in case there are no more unnamed instances
throw :no_nameless_instances if nameless_instances.empty?
end
end
# and recursively descend into the subspaces
const_symbols.each do |sym|
obj = ɱodule.const_get sym rescue nil # get the const value
search_namespace_and_subspaces( obj, occupied ) unless
occupied.include? obj.object_id if obj.kind_of? Module
end
end
end # module ConstMagicClassMethods
end # module ConstMagicErsatz
The above code implements automatic searching of whole Ruby namespace with the aim of finding which constant refers to the given instance, whenever #name method is called.
The only constraint using constants gives you, is that you have to capitalize it. Of course, what you want would be modifying the metaclass of the object after it is already born and assigned to a constant. Since, again, there is no hook, you have to finde the occasion to do this, such as when the new object is first used for its purpose. So, having
ABC = MySettings.new
and then, when the first use of your MySettings instance occurs, before doing anything else, to patch its metaclass:
class MySettings
def do_something_useful
# before doing it
instance_name = self.name
singleton_class.class_exec { source "#{instance_name}.yml" }
end
# do other useful things
end
Shouldn't you be able to do either
File.open(File.join(File.expand_path(File.dir_name(__FILE__)), foo.class), "r")
or
require foo.class
The first one need not be that complicated necessarily. But if I'm understanding you correctly, you can just use foo.class directly in a require or file load statement.
Adjust as necessary for YAML loading, but #class returns a plain old string.
Well if you have tons of variables to instantiate, I'd personally just create a Hash to hold them, it's cleaner this way. Now to instantiate all of this, you could do a loop other all your yaml files :
my_settings = {}
[:basic_config, :advanced_cfg, :some_yaml, :some_yaml2].each do |yaml_to_parse|
my_settings[yaml_to_parse] = MySettings.new(yaml_to_parse)
end
Make sure your initialize method in MySettings deals with the symbol you give it!
Then get your variables like this :
my_settings[:advanced_cfg]
Unfortunately, Ruby has no hooks for variable assignment, but this can be worked around. The strategy outline is as follows: First, you will need to get your MySettings.new method to eval code in the caller's binding. Then, you will find the list of local variable symbols in the caller's binding by calling local_variables method there. Afterwards, you will iterate over them to find which one refers to the instance returned by super call in your custom MySettings.new method. And you will pass its symbol to source method call.
EDIT: I slightly changed the spec, to better match what I imagined this to do.
Well, I don't really want to fake C# attributes, I want to one-up-them and support AOP as well.
Given the program:
class Object
def Object.profile
# magic code here
end
end
class Foo
# This is the fake attribute, it profiles a single method.
profile
def bar(b)
puts b
end
def barbar(b)
puts(b)
end
comment("this really should be fixed")
def snafu(b)
end
end
Foo.new.bar("test")
Foo.new.barbar("test")
puts Foo.get_comment(:snafu)
Desired output:
Foo.bar was called with param: b = "test"
test
Foo.bar call finished, duration was 1ms
test
This really should be fixed
Is there any way to achieve this?
I have a somewhat different approach:
class Object
def self.profile(method_name)
return_value = nil
time = Benchmark.measure do
return_value = yield
end
puts "#{method_name} finished in #{time.real}"
return_value
end
end
require "benchmark"
module Profiler
def method_added(name)
profile_method(name) if #method_profiled
super
end
def profile_method(method_name)
#method_profiled = nil
alias_method "unprofiled_#{method_name}", method_name
class_eval <<-ruby_eval
def #{method_name}(*args, &blk)
name = "\#{self.class}##{method_name}"
msg = "\#{name} was called with \#{args.inspect}"
msg << " and a block" if block_given?
puts msg
Object.profile(name) { unprofiled_#{method_name}(*args, &blk) }
end
ruby_eval
end
def profile
#method_profiled = true
end
end
module Comment
def method_added(name)
comment_method(name) if #method_commented
super
end
def comment_method(method_name)
comment = #method_commented
#method_commented = nil
alias_method "uncommented_#{method_name}", method_name
class_eval <<-ruby_eval
def #{method_name}(*args, &blk)
puts #{comment.inspect}
uncommented_#{method_name}(*args, &blk)
end
ruby_eval
end
def comment(text)
#method_commented = text
end
end
class Foo
extend Profiler
extend Comment
# This is the fake attribute, it profiles a single method.
profile
def bar(b)
puts b
end
def barbar(b)
puts(b)
end
comment("this really should be fixed")
def snafu(b)
end
end
A few points about this solution:
I provided the additional methods via modules which could be extended into new classes as needed. This avoids polluting the global namespace for all modules.
I avoided using alias_method, since module includes allow AOP-style extensions (in this case, for method_added) without the need for aliasing.
I chose to use class_eval rather than define_method to define the new method in order to be able to support methods that take blocks. This also necessitated the use of alias_method.
Because I chose to support blocks, I also added a bit of text to the output in case the method takes a block.
There are ways to get the actual parameter names, which would be closer to your original output, but they don't really fit in a response here. You can check out merb-action-args, where we wrote some code that required getting the actual parameter names. It works in JRuby, Ruby 1.8.x, Ruby 1.9.1 (with a gem), and Ruby 1.9 trunk (natively).
The basic technique here is to store a class instance variable when profile or comment is called, which is then applied when a method is added. As in the previous solution, the method_added hook is used to track when the new method is added, but instead of removing the hook each time, the hook checks for an instance variable. The instance variable is removed after the AOP is applied, so it only applies once. If this same technique was used multiple time, it could be further abstracted.
In general, I tried to stick as close to your "spec" as possible, which is why I included the Object.profile snippet instead of implementing it inline.
Great question. This is my quick attempt at an implementation (I did not try to optimise the code). I took the liberty of adding the profile method to the
Module class. In this way it will be available in every class and module definition. It would be even better
to extract it into a module and mix it into the class Module whenever you need it.
I also didn't know if the point was to make the profile method behave like Ruby's public/protected/private keywords,
but I implemented it like that anyway. All methods defined after calling profile are profiled, until noprofile is called.
class Module
def profile
require "benchmark"
#profiled_methods ||= []
class << self
# Save any original method_added callback.
alias_method :__unprofiling_method_added, :method_added
# Create new callback.
def method_added(method)
# Possible infinite loop if we do not check if we already replaced this method.
unless #profiled_methods.include?(method)
#profiled_methods << method
unbound_method = instance_method(method)
define_method(method) do |*args|
puts "#{self.class}##{method} was called with params #{args.join(", ")}"
bench = Benchmark.measure do
unbound_method.bind(self).call(*args)
end
puts "#{self.class}##{method} finished in %.5fs" % bench.real
end
# Call the original callback too.
__unprofiling_method_added(method)
end
end
end
end
def noprofile # What's the opposite of profile?
class << self
# Remove profiling callback and restore previous one.
alias_method :method_added, :__unprofiling_method_added
end
end
end
You can now use it as follows:
class Foo
def self.method_added(method) # This still works.
puts "Method '#{method}' has been added to '#{self}'."
end
profile
def foo(arg1, arg2, arg3 = nil)
puts "> body of foo"
sleep 1
end
def bar(arg)
puts "> body of bar"
end
noprofile
def baz(arg)
puts "> body of baz"
end
end
Call the methods as you would normally:
foo = Foo.new
foo.foo(1, 2, 3)
foo.bar(2)
foo.baz(3)
And get benchmarked output (and the result of the original method_added callback just to show that it still works):
Method 'foo' has been added to 'Foo'.
Method 'bar' has been added to 'Foo'.
Method 'baz' has been added to 'Foo'.
Foo#foo was called with params 1, 2, 3
> body of foo
Foo#foo finished in 1.00018s
Foo#bar was called with params 2
> body of bar
Foo#bar finished in 0.00016s
> body of baz
One thing to note is that it is impossible to dynamically get the name of the arguments with Ruby meta-programming.
You'd have to parse the original Ruby file, which is certainly possible but a little more complex. See the parse_tree and ruby_parser
gems for details.
A fun improvement would be to be able to define this kind of behaviour with a class method in the Module class. It would be cool to be able to do something like:
class Module
method_wrapper :profile do |*arguments|
# Do something before calling method.
yield *arguments # Call original method.
# Do something afterwards.
end
end
I'll leave this meta-meta-programming exercise for another time. :-)