1

I have a ruby class which contains a lot of logic which will no longer be used but I need to keep around (for a while at least) for backwards-compatibility.

My plan is to move these methods to a module like LegacyStuff and include it in the class. I'm wondering if there's a neat way to add something such that when any method in the module is called, a warning is generated, without having to actually add warn statements to every individual method body.

I guess what I'm looking for is behaviour like a 'before call' or 'after call' hook on a whole module. I guess the sub-question here is "is this even a good idea?"

9
  • 1
    "is this even a good idea?" -- The fact that you have one module so big that this question has even occurred to you is the real problem IMO! At least you're changing how it works, now... Commented Jun 19, 2018 at 11:55
  • You could probably do it with some method_added magic. Commented Jun 19, 2018 at 11:56
  • 1
    You can (heavily) adapt my answer for another question stackoverflow.com/a/50908899/681520 to do this, at least I think it should give you some tips. Commented Jun 19, 2018 at 12:20
  • 1
    @Beejamin "I've been writing ruby for 10 years and never used method_added" - me too. Unless you're into heavy MP, you don't need it at all. :) Commented Jun 19, 2018 at 13:40
  • 2
    @engineersmnky Using Module#prepend makes it much easier to do that sort of thing. I intentionally wrote that code without Module#prepend as a key part of the challenge :) Commented Jun 19, 2018 at 15:39

2 Answers 2

4

While this is not exactly what you asked ("mark all methods at once") and there probably exists something like this in ruby itself or big frameworks like rails, it might still be useful to somebody.

module DeprecatedMethods def deprecated(method_name) prepend(Module.new do define_method method_name do |*args| puts "calling deprecated method #{method_name}" super(*args) end end) end end module AncientCode extend DeprecatedMethods deprecated def foo # selectively mark methods as deprecated puts "doing foo" end def bar puts "doing bar" end end class Host include AncientCode end host = Host.new host.foo host.bar # >> calling deprecated method foo # >> doing foo # >> doing bar 

Currently this injects a module per deprecated method, which is kinda wasteful. You can put all deprecated methods in one module, by doing something like this:

deprecated_methods :foo, :bar 
Sign up to request clarification or add additional context in comments.

1 Comment

Also, it is trivial to combine this with method_added and get a blanket deprecation thingie.
2

Code

First create a module containing a single module method, setup. This module could be put in a file that is required as needed. This module method will be invoked from a module containing instance methods to be included in a given class. Using aliases, it modifies those instance methods to print a message (containing the method name) before executing the remainder of its code.

Module AddMessage

module AddMessage def self.setup(mod, msg_pre, msg_post) mod.instance_methods(false). each do |m| am = "_#{m}".to_sym mod.send(:alias_method, am, m) mod.send(:private, am) mod.send(:define_method, m) do |*args, &block| puts "%s %s %s" % [msg_pre, m, msg_post] send(am, *args, &block) end end end end 

Module to be included in class

module LegacyStuff def old1 "hi" end def old2(a, b) yield(a, b) end AddMessage.setup(self, "Warning:", "is deprecated") end 

AddMessage::setup is passed three arguments, the name of the calling module and a message prefix and message suffix that are used to form the warning message. When an instance method m in this module is executed by a class instance the message "Warning: #{m} is deprecated" is printed (e.g., "Warning: old1 is deprecated") before the remaining calculations are performed.

Use

LegacyStuff is simply included by a class.

class C include LegacyStuff # <constants, methods and so forth go here> end c = C.new c.old1 # Warning: old1 is deprecated #=> "hi" c.old2(1,2) { |a,b| a+b } # Warning: old2 is deprecated #=> 3 c.cat #=> NoMethodError: undefined method `cat' for #<C:0x000000008ef0a8> 

Explanation of the module method AddMessage:setup

The following (of the generally less-familiar) methods are used by this method: Module#instance_methods, Module#alias_method, Module#private and Module#define_method.

The following three steps are performed for each instance method m defined in module mod (e.g., elements of the array LegacyStuff.instance_methods(false)).

mod.send(:alias_method, am, m)

Create an alias am of the method (e.g., _:old1 for old1).

mod.send(:private, am)

Make the alias am a private method (e.g., _old1).

mod.send(:define_method, m) do |*args, &block| ... end

Redefine the method m (e.g., old1) to print the indicate string and then execute the alias am (e.g., _old1).

3 Comments

Interesting approach. I would ideally like something that is contained to the LegacyStuff module if possible, though.
I hope this Take #24 is a wrap.
That is beautiful. Thanks for all the effort - exceptional answer and explanation.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.