Within a method at runtime, is there a way to know if that method has been called via super in a subclass? E.g.
module SuperDetector
def via_super?
# what goes here?
end
end
class Foo
include SuperDetector
def bar
via_super? ? 'super!' : 'nothing special'
end
end
class Fu < Foo
def bar
super
end
end
Foo.new.bar # => "nothing special"
Fu.new.bar # => "super!"
How could I write via_super?, or, if necessary, via_super?(:bar)?
There is probably a better way, but the general idea is that Object#instance_of? is restricted only to the current class, rather than the hierarchy:
module SuperDetector
def self.included(clazz)
clazz.send(:define_method, :via_super?) do
!self.instance_of?(clazz)
end
end
end
class Foo
include SuperDetector
def bar
via_super? ? 'super!' : 'nothing special'
end
end
class Fu < Foo
def bar
super
end
end
Foo.new.bar # => "nothing special"
Fu.new.bar # => "super!"
However, note that this doesn't require explicit super in the child. If the child has no such method and the parent's one is used, via_super? will still return true. I don't think there is a way to catch only the super case other than inspecting the stack trace or the code itself.
An addendum to an excellent #ndn approach:
module SuperDetector
def self.included(clazz)
clazz.send(:define_method, :via_super?) do
self.ancestors[1..-1].include?(clazz) &&
caller.take(2).map { |m| m[/(?<=`).*?(?=')/] }.reduce(&:==)
# or, as by #ndn: caller_locations.take(2).map(&:label).reduce(&:==)
end unless clazz.instance_methods.include? :via_super?
end
end
class Foo
include SuperDetector
def bar
via_super? ? 'super!' : 'nothing special'
end
end
class Fu < Foo
def bar
super
end
end
puts Foo.new.bar # => "nothing special"
puts Fu.new.bar # => "super!"
Here we use Kernel#caller to make sure that the name of the method called matches the name in super class. This approach likely requires some additional tuning in case of not direct descendant (caller(2) should be changed to more sophisticated analysis,) but you probably get the point.
UPD thanks to #Stefan’s comment to the other answer, updated with unless defined to make it to work when both Foo and Fu include SuperDetector.
UPD2 using ancestors to check for super instead of straight comparison.
Here's a simpler (almost trivial) approach, but you have to pass both, current class and method name: (I've also changed the method name from via_super? to called_via?)
module CallDetector
def called_via?(klass, sym)
klass == method(sym).owner
end
end
Example usage:
class A
include CallDetector
def foo
called_via?(A, :foo) ? 'nothing special' : 'super!'
end
end
class B < A
def foo
super
end
end
class C < A
end
A.new.foo # => "nothing special"
B.new.foo # => "super!"
C.new.foo # => "nothing special"
Edit Improved, following Stefan's suggestion.
module SuperDetector
def via_super?
m0, m1 = caller_locations[0].base_label, caller_locations[1]&.base_label
m0 == m1 and
(method(m0).owner rescue nil) == (method(m1).owner rescue nil)
end
end
The ultimate mix between my other, #mudasobwa's and #sawa's answers plus recursion support:
module SuperDetector
def self.included(clazz)
unless clazz.instance_methods.include?(:via_super?)
clazz.send(:define_method, :via_super?) do
first_caller_location = caller_locations.first
calling_method = first_caller_location.base_label
same_origin = ->(other_location) do
first_caller_location.lineno == other_location.lineno and
first_caller_location.absolute_path == other_location.absolute_path
end
location_changed = false
same_name_stack = caller_locations.take_while do |location|
should_take = location.base_label == calling_method and !location_changed
location_changed = !same_origin.call(location)
should_take
end
self.kind_of?(clazz) and !same_origin.call(same_name_stack.last)
end
end
end
end
The only case that wont work (AFAIK) is if you have indirect recursion in the base class, but I don't have ideas how to handle it with anything short of parsing the code.
Related
I want to know if a method on a class gets redefined.
use case: in version 1 of ruby-clock, defining a method on an object was part of the API. In version 2, doing so will break behavior, and a different API should be used.
what I ended up doing: https://github.com/jjb/ruby-clock/pull/28/files
The best would be if I could use some sort of metaprogramming to notice when it happens.:
# this is example code which does not work
class MyClass
def my_method
puts "hello"
end
# this is not real! it's an example of what i hope is somehow possible
def self.method_defined(m, *args, &block)
if :my_method == m
raise "you should not redefine my_method!"
end
end
end
Also acceptable would be if I could check back later and see if it has changed.
# this is example code which does not work
class MyClass
def my_method
puts "hello"
end
##my_method_object_id = get_method_object_id(:my_method)
end
# later, when app initialization is done, check if the user redefined the method
# this is not real! it's an example of what i hope is somehow possible
def self.method_defined(m, *args, &block)
if MyClass.get_method_object_id(:my_method) != MyClass.my_method_object_id
raise "you should not redefine my_method!"
end
end
n.b. that MyClass.method(:my_method) is a real thing in ruby, but it always produces a new object.
This sounds pretty much like XY problem. I don't think you should go this way - if you find yourself fighting with the Ruby object model, you most probably picked the wrong tool for the job.
But for the sake of completeness... There are a couple of callbacks in Module that can help with the ideas as crazy as this one. In particular, there is Module#method_added which is called each time some instance method is defined. So we can do smth. like this (very dirty example):
class C
#foo_counter = 0
def self.method_added(name)
if name == :foo
#foo_counter += 1
if #foo_counter > 1
raise "Don't redefine foo!"
end
end
end
def foo
"foo"
end
end
now if one tries to open C and redefine foo the following happens (pry session as an example):
pry(main)> class C
pry(main)* def foo
pry(main)* "bar"
pry(main)* end
pry(main)* end
RuntimeError: Don't redefine foo!
So you can do what you want to (to some extent - it's Ruby, so what stops one from redefining the annoying hook first? :)), but please don't. You'll get nothing but troubles...
You could use the callback BasicObject#singleton_method_added.
class C
#class_methods = []
def self.singleton_method_added(m)
puts "Method #{m} was just redefined" if #class_methods.include?(m)
#class_methods << m
end
def self.c
puts 'hi'
end
end
C.c
#=> "hi"
class C
def self.c
puts 'ho'
end
end
Method c was just redefined
C.c #=> "ho"
I try to write a metaprogramming for execute a method before 'master' method. Why ? Because, I have several class and it's ugly to repeat the call in the head of the method
Case :
class MyClass
include MySuperModule
before :method, call: before_method
def before_method
puts "Before.."
end
end
class SomeClass < MyClass
def method
puts "Method.."
end
end
module MySuperModule
# the awesome code
end
Output :
SomeClass.new.method => "Before.. Method.."
So, I try write a module with ClassMethodsor method_missingwithout success.
You don't need a gem for simple metaprogramming like this. What you can do is redefine the "after" method to call the "before" method and then the original "after" method.
This works even when using before multiple times on the same method or when creating a chain of before calls.
module MySuperModule
def before meth, opts
old_method = instance_method(meth)
define_method(meth) do
send opts[:call]
old_method.bind(self).call
end
end
end
class MyClass
extend MySuperModule
def foo
puts "foo"
end
def bar
puts "bar"
end
def baz
puts "baz"
end
before :foo, call: :bar
before :bar, call: :baz
end
MyClass.new.foo
# baz
# bar
# foo
If it is just for subclassing purposes you can take advantage of Module#prepend:
class Superclass
def self.inherited(subclass)
# subclass.send :prepend, Module.new { on Ruby < 2.1
subclass.prepend Module.new {
def method
before_method
super
end
}
end
def before_method
puts 'Before'
end
end
class Subclass < Superclass
def method
puts 'Method'
end
end
Subclass.new.method
#=> Before
#=> Method
What you are looking for is Aspect oriented programming support for ruby. There are several gems implementing this, like aquarium.
Another way to do this is to use the rcapture gem.
It is pretty awesome.
Eg:
require 'rcapture'
class A
# Makes the class intercept able
include RCapture::Interceptable
def first
puts 'first'
end
def second
puts 'second'
end
end
# injects methods to be called before each specified instance method.
A.capture_pre :methods => [:first, :second] do
puts "hello"
end
n = A.new
n.first
n.second
produces:
hello
first
hello
second
Maybe you can use a decorator. In ruby there is a nice gem called 'drapeer'. See Drapper Link
Every call in ruby runs through set_trace_func so you can hook into that and call exactly what you want. Not the prettiest solution and there are better ways but it does work. Another option is the Hooks gem, though I haven't tried it myself, it looks like it should give you the ability to do what you want.
module MySuperModule
# the awesome code
end
class MyClass
include MySuperModule
def before_method
puts "Before.."
end
end
class SomeClass < MyClass
def method
puts "Method.."
end
end
set_trace_func proc { |event, file, line, id, binding, class_name|
if event == "call" && class_name == SomeClass && id == :method
caller = binding.eval("self")
caller.send(:before_method)
end
}
SomeClass.new.method
#=> Before..
#=> Method..
Just for fun, again, but is it possible to take a block that contains method definitions and add those to an object, somehow? The following doesn't work (I never expected it to), but just so you get the idea of what I'm playing around with.
I do know that I can reopen a class with class << existing_object and add methods that way, but is there a way for code to pass that information in a block?
I guess I'm trying to borrow a little Java thinking here.
def new(cls)
obj = cls.new
class << obj
yield
end
obj
end
class Cat
def meow
puts "Meow"
end
end
cat = new(Cat) {
def purr
puts "Prrrr..."
end
}
cat.meow
# => Meow
# Not working
cat.purr
# => Prrrr...
EDIT | Here's the working version of the above, based on edgerunner's answer:
def new(cls, &block)
obj = cls.new
obj.instance_eval(&block)
obj
end
class Cat
def meow
puts "Meow"
end
end
cat = new(Cat) {
def purr
puts "Prrrr..."
end
}
cat.meow
# => Meow
cat.purr
# => Prrrr...
You can use class_eval(also aliased as module_eval) or instance_eval to evaluate a block in the context of a class/module or an object instance respectively.
class Cat
def meow
puts "Meow"
end
end
Cat.module_eval do
def purr
puts "Purr"
end
end
kitty = Cat.new
kitty.meow #=> Meow
kitty.purr #=> Purr
kitty.instance_eval do
def purr
puts "Purrrrrrrrrr!"
end
end
kitty.purr #=> Purrrrrrrrrr!
Yes
I suspect you thought of this and were looking for some other way, but just in case...
class A
def initialize
yield self
end
end
o = A.new do |o|
class << o
def purr
puts 'purr...'
end
end
end
o.purr
=> purr...
For the record, this isn't the usual way to dynamically add a method. Typically, a dynamic method starts life as a block itself, see, for example, *Module#define_method*.
I am trying to define some classes in Ruby that have an inheritance hierarchy, but I want to use one of the methods in the base class in the derived class. The twist is that I don't want to call the exact method I'm in, I want to call a different one. The following doesn't work, but it's what I want to do (basically).
class A
def foo
puts 'A::foo'
end
end
class B < A
def foo
puts 'B::foo'
end
def bar
super.foo
end
end
Probably, this is what you want?
class A
def foo
puts 'A::foo'
end
end
class B < A
alias bar :foo
def foo
puts 'B::foo'
end
end
B.new.foo # => B::foo
B.new.bar # => A::foo
A more general solution.
class A
def foo
puts "A::foo"
end
end
class B < A
def foo
puts "B::foo"
end
def bar
# slightly oddly ancestors includes the class itself
puts self.class.ancestors[1].instance_method(:foo).bind(self).call
end
end
B.new.foo # => B::foo
B.new.bar # => A::foo
Suppose I have a module with the methods : function1,function2,function3. I want to import function1 and function2 but not function3. Is there a way to do this in ruby?
Not sure if there is a clean way to just add the methods you want, but you can remove the methods that you don't want by using undef_method.
module Foo
def function1
end
def function2
end
def function3
end
end
module MiniFoo
include Foo
not_wanted_methods = Foo.instance_methods - %w(function1 function2)
not_wanted_methods.each {|m| undef_method m}
end
class Whatever
include MiniFoo
end
Assuming you control the source code to the module, I think the cleanest way would be to split the module in to more, uh, modular pieces.
If you only want some parts of a module, that's a pretty good sign that you could refactor that module into multiple modules that have less responsibility.
Similar solution but a tad more automatic. I have no idea what kind of weird things that can happen though.
module Foo
def m1
puts "Hello from m1"
end
def m2
puts "Hllo from m2"
end
end
class Module
alias :__include__ :include
def include(mod, *methods)
if methods.size > 0
tmp = mod.dup
new_mod = Object.const_set("Mod#{tmp.object_id}", tmp)
toremove = new_mod.instance_methods.reject { |m| methods.include? m.to_sym }
toremove.each { |m| new_mod.send(:undef_method, m) }
__include__(new_mod)
else
__include__(mod)
end
end
end
class Bar
include Foo
end
class Baz
include Foo, :m2
end
bar = Bar.new
baz = Baz.new
p bar.methods - Object.methods
p baz.methods - Object.methods
=>
["m1", "m2"]
["m2"]