I have a subclass that I would like to implement some default behavior for a set of methods, for example if I know I need to override the method, but just want to print a warning for now if the method is not yet supported on this particular subclass.
I can override each method in the subclass, like so, but I was wondering if there was a easier way, maybe using delegate or method_missing?
class Foo
def method_a
# do foo stuff
end
def method_b
# do foo stuff
end
def method_c
# do foo stuff
end
end
class Bar < Foo
def not_yet_supported
puts "warnnig not yet supported"
end
def method_b
not_yet_supported
end
end
Using alias or alias_method as suggested in the comments is an easy route, and would look like
alias method_b not_yet_supported
alias_method :method_c, :not_yet_supported
Note the syntactic differences, since alias is a keyword and alias_method is a method.
If you want to declare a whole list of unsupported methods, a little metaprogramming gets you there:
class Foo
def self.not_yet_supported(*method_names)
method_names.each do |method_name|
define_method method_name do
puts "#{method_name} is not yet supported on #{self.class.name}"
end
end
end
not_yet_supported :bar, :baz
end
A cleaner approach would be to use method_missing:
class Foo
UNIMPLEMENTED_METHODS = [:method_a, :method_b, :method_etc]
def method_missing(method_id)
UNIMPLEMENTED_METHODS.include?(method_id) ? warn("#{method_id} has not been implemented") : raise(NoMethodError)
end
end
A friendlier explanation of method_missing can also be found here
Related
I am creating a helper module to initialize the object before calling its methods
module Initialized
extend ActiveSupport::Concern
class_methods do
def run(*args)
new(*args).run
end
def call(*args)
new(*args).call
end
def execute(*args)
new(*args).create
end
end
end
So instead of defining run, call, and execute in my helper module I need to receive any method name and check if it exists on the main class after initializing it, then call the requested instance method if exists in the main class or raise an error if not
I would say my targeted code would be something like this
module Initialized
extend ActiveSupport::Concern
class_methods do
def _(*args, methodname)
new(*args).try(:send, "#{methodname}") || raise 'Method not exist'
end
end
end
Sample usage would be
class MyClass
include Initialized
def initialize(param1)
#param1 = param1
end
def call
puts "call '#{#param1}'"
end
end
then calling
MyClass.call('method param')
I found these links but couldn't find my answer yet:
meta-dynamic-generic-programming-in-ruby
ruby-module-that-delegates-methods-to-an-object
template-methods-in-ruby
Despite the fact method_missing would do the job, I'd probably avoid it in this case in favor of a more explicit delegation. Simple and dirty example:
module InstanceDelegator
def delegate_to_instance(*methods)
methods.each do |method_name|
define_singleton_method method_name do |*args|
new(*args).public_send(method_name)
end
end
end
end
class Foo
extend InstanceDelegator
delegate_to_instance :bar # <- define explicitly which instance methods
# should be mirrored by the class ones
def bar
puts "bar is called"
end
def baz
puts "baz is called"
end
end
# and then
Foo.bar # => bar is called
Foo.baz # NoMethodError ...
# reopening works too
class Foo
delegate_to_instance :baz
end
Foo.baz # baz is called
Pros:
you don't need to redefine method_missing (less magic -> less pain when you debug the code)
you control precisely which instance methods to be wrapped with the class level "shorthand" (fewer chances to delegate something you don't want to - more robust code)
(minor) no need to raise NoMethodError explicitly - you can fully rely on the core dispatching as it is...
I found another solution instead of using a module,
I can use the class method self.method_missing
def self.method_missing(method_name, *args, &block)
obj = new(*args)
raise NoMethodError, "undefined method `#{method_name}' for #{self}:Class" unless obj.respond_to?(method_name)
obj.send(method_name, &block)
end
But the limitation is that I have to copy it into every class whenever I need to use this feature
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..
Is there a way to avoid (raise an error when it is attempted) definition of a method with a certain name, for example Foo#bar? (A usecase would be when Foo#bar is already defined, and I want to avoid that method being overridden, but that is irrelevant to the question.) I am assuming something like:
class Foo
prohibit_definition :bar
end
...
# Later in some code
class Foo
def bar
...
end
end
# => Error: `Foo#bar' cannot be defined
class Class
def method_added(method_name)
raise "So sad, you can't add" if method_name == :bad
end
end
class Foo
def bad
puts "Oh yeah!"
end
end
#irb> RuntimeError: So sad, you can't add
# from (irb):3:in `method_added'
# from (irb):7
Maybe you can catch the callback in method_added() of Module class and check the method name and delete the added method if it does not meet your criteria. Then you can raise an error.
Not you want exactly but close enough I think.
class Class
def method_added(method_name)
if method_name == :bar
remove_method :bar
puts "#{method_name} cannot be added to #{self}"
end
end
I like Ruby's singleton but I would like to make usage of it better so here is example
require 'singleton'
class Foo
include Singleton
def initialize
# code to setup singleton here
end
def self.get_bar
Foo.instance.get_bar
end
def get_bar
end
def get_nar
end
end
Usage
Foo.instance.get_bar (default) or Foo.get_bar (due to static self.get_bar method I made)
Is there elegant way to make all methods accessible without me having to write static wrapper for each method? Just seems redundant to have to write for each method .instance
UPDATE
Ruby 1.8.7
You could mix this module:
module DelegateToSingleton
def respond_to_missing?(method)
super || instance.respond_to?(method)
end
def method_missing(method, *args)
instance.send(method, *args)
end
end
into your singleton:
class Foo
extend DelegateToSingleton
include Singleton
def foo
'foo'
end
def bar
'bar'
end
end
with these results:
p Foo.foo # => "foo"
p Foo.bar # => "bar"
DelegateToSingleton::method_missing is what makes it work: Whenever Foo receives a method it doesn't know about, it just forwards it to its instance.
DelegateToSingleton::respond_to_missing? is not strictly needed, but having it is good manners whenever playing tricks with method_missing.
For Ruby earlier than 1.9.2: Override respond_to? instead of respond_to_missing?
Just separate the class from the instance:
class Foo
def initialize
end
def get_bar
end
def get_nar
end
end
MyFoo = Foo.new
MyFoo.get_bar
I'm playing with ruby's metaprogramming features, and I'm finding it a bit hairy. I'm trying to wrap a method call using a module. Currently, I'm doing this:
module Bar
module ClassMethods
def wrap(method)
class_eval do
old_method = "wrapped_#{method}".to_sym
unless respond_to? old_method
alias_method old_method, method
define_method method do |*args|
send old_method, *args
end
end
end
end
end
def self.included(base)
base.extend ClassMethods
end
end
class Foo
include Bar
def bar(arg = 'foo')
puts arg
end
wrap :bar
end
Three questions:
Is there any way to do this without renaming the method, so as to allow the use of super? Or something cleaner/shorter?
Is there a clean way to set the default values?
Is there a means to move the wrap :bar call further up?
1) Cleaner/shorter
module ClassMethods
def wrap(method)
old = "_#{method}".to_sym
alias_method old, method
define_method method do |*args|
send(old, *args)
end
end
end
class Foo
extend ClassMethods
def bar(arg = 'foo')
puts arg
end
wrap :bar
end
As far as I know there is no way to achieve this without renaming. You could try to call super inside the define_method block. But first of all, a call to super from within a define_method will only succeed if you specify arguments explicitly, otherwise you receive an error. But even if you call e.g. super(*args), self in that context would be an instance of Foo. So a call to bar would go to the super classes of Foo, not be found and ultimately result in an error.
2) Yes, like so
define_method method do |def_val='foo', *rest|
send(old, def_val, *rest)
end
However, in Ruby 1.8 it is not possible to use a block in define_method, but this has been fixed for 1.9. If you are using 1.9, you could also use this
define_method method do |def_val='foo', *rest, &block|
send(old, def_val, *rest, &block)
end
3) No, unfortunately. alias_method requires the existence of the methods that it takes as input. As Ruby methods come into existence as they are parsed, the wrap call must be placed after the definition of bar otherwise alias_method would raise an exception.