Deprecating an old module name in Ruby? - ruby

How would I best go about renaming a Ruby Module in a backwards compatible manner? I have ActiveSupport, so can call deprecation warnings this way, but in this case I'm deprecating the old module name and not the method names.
Example follows:
module OldName
def self.method1
...
end
end
Will become:
module NewName
def self.method1
...
end
end
I want to generate warnings directing the developer to using NewName::method1 rather than OldName::method1
Any suggestions?

ActiveSupport provides two ways of deprecating a constant: DeprecatedConstantAccessor and DeprecatedConstantProxy. They each have trade-offs and limitations in when they show the warning (Accessor is when it's accessed; Proxy is when its methods are called) and how they generally behave. You'll probably want to try both to see which one feels right for your use case.

Rails 4.1.8 Ruby 2.2.0p0
module Fred
extend self
def aaa; end
def bbb; end
def ccc; end
def ddd; end
def eee; end
end
module Bar
extend self
def ccc; end
end
ActiveSupport::Deprecation.deprecate_methods(Fred, :aaa, bbb: :zzz, ccc: 'use Bar#ccc instead')
Fred.aaa
DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 4.2. (called from __pry__ at (pry):15)
=> nil
Same code tried in rails 5.2.0 but no DEPRECATION WARNING.
Rails 5.2.0 Ruby 2.5.1p57
class Fred
def aaa; end
def bbb; end
def ccc; end
def ddd; end
def eee; end
end
class Bar
def ccc; end
end
ActiveSupport::Deprecation.deprecate_methods(Fred, :aaa, bbb: :zzz, ccc: 'use Bar#ccc instead')
> Fred.new.aaa
DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 6.0 (called from irb_binding at (irb):13)
=> nil

You could go with something like:
module Bar
def self.aaa
'Bar::aaa'
end
def baz
'Bar#baz'
end
end
module Foo
{included: :include,extended: :extend,prepended: :prepend}.each do |mod_method, called_method|
singleton_class.define_method(mod_method) do |base|
warn "Foo is deprecated please use Bar instead"
base.send(called_method,Bar)
end
end
def self.method_missing(meth,*args,&block)
warn "Foo is deprecated please use Bar instead"
Bar.respond_to?(meth) ? Bar.send(meth,*args,&block) : super
end
end
class A
include Foo # this will throw a warning
end
Then
Foo.aaa
# Foo is deprecated please use Bar instead
#=> 'Bar::aaa'
A.new.baz
#=> 'Bar#baz'

module OldName
def instance_method(*args)
yield(args)
end
instance_methods.each do |im|
alias_name = "_#{im.to_s}"
alias_method alias_name, im
define_method(im) do |*args, &block|
warn "Foo is deprecated please use Bar instead"
public_send(alias_name, *args, &block)
end
end
end
class C
include OldName
end
C.instance_methods && [:im, :_im]
#=> [:im, :_im]
c = C.new
c._instance_method(2,3) { |a,b| a+b }
#=> 5
c.instance_method(2,3) { |a,b| a+b }
# Foo is deprecated please use Bar instead
#=> 5
Alternatively, public_send(alias_name, *args, &block) can be replaced with method(alias_name).call(*args, &block).

I was sideswiped by a bug in the gem I was patching, which caused a few false starts, but the final answer is extremely simple and I include the working code as an example for others:
require 'active_support'
OldName = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OldName', 'NewName')
module NewName
def self.method1
puts 'Hello World!'
end
end
NewName.method1
#=> Hello World!
OldName.method1
#=> DEPRECATION WARNING: OldName is deprecated! Use NewName instead. (called from ..)
#=> Hello World!
Simples!
My thanks to #matthewd for re-pointing me in the right direction.

Related

How is it I can't access a module function from within a class from the same module in ruby?

I'm having some issues with the access rules of ruby. At one point I thought that something like this worked:
module Foo
def bar(x)
puts "#{x}"
end
class Baz
def initialize(x)
bar(x)
end
end
end
Foo::Baz.new(3)
but somehow it no longer does. I've tried to declare bar using self.bar and Foo.bar, but neither work. What am I missing?
The inclusion of the module methods isn't "automatic", I think because it can lead to unexpected behavior.
You can try making them a class method as you mentioned, or including the module and getting access to the methods there:
module Foo
class << self
def bar(x)
puts "#{s}" % x
end
def s
"s1"
end
end
def bar(x)
puts "#{s}" % x
end
def s
"s2"
end
class Baz
include Foo
def initialize(x)
bar(x)
Foo.bar(x)
end
end
end
Foo::Baz.new(3)
# s2
# s1

How to work through name collisions in ruby

Two modules Foo and Baa respectively define a method with the same name name, and I did include Foo and include Baa in a particular context.
When I call name, how can I disambiguate whether to call the name method of Foo or Baa?
Only the order of modules inclusion decides which one will get called. Can't have both with the same name - the latter will override the former.
Of course, you can do any tricks, just from the top of my head:
module A
def foo
:foo_from_A
end
end
module B
def foo
:foo_from_B
end
end
class C
def initialize(from)
#from = from
end
def foo
from.instance_method(__method__).bind(self).call
end
private
attr_reader :from
end
C.new(A).foo #=> :a_from_A
C.new(B).foo #=> :a_from_B
But that's no good for real life use cases :)
Technically, there is no name collision because the method foo is redefined.
In the following exemple, A.foo is redefined and is never called
module A
def foo
raise "I'm never called"
end
end
module B
def foo
puts :foo_from_B
end
end
class C
include A
include B
end
C.new.foo
# =>
# foo_from_B
If you write A and B module, you can use super to call previous definition of foo. As if it where an inherited method.
module A
def foo
puts :foo_from_A
end
end
module B
def foo
super
puts :foo_from_B
end
end
class C
include A
include B
end
C.new.foo
# =>
# foo_from_A
# foo_from_B
There are side effects and I would not use this but this is doing the trick :
module A
def foo
puts :foo_from_A
end
end
module B
def foo
puts :foo_from_B
end
end
class C
def self.include_with_suffix(m, suffix)
m.instance_methods.each do |method_name|
define_method("#{method_name}#{suffix}", m.instance_method(method_name))
end
end
include_with_suffix A, "_from_A"
include_with_suffix B, "_from_B"
end
c= C.new
c.foo_from_A
c.foo_from_B
begin
c.foo
rescue NoMethodError
puts "foo is not defined"
end
# =>
# foo_from_A
# foo_from_B
# foo is not defined
Provided none of the methods of Foo or Baa call name (which seems a reasonable assumption), one can simply create aliases.
module Foo
def name; "Foo#name"; end
end
module Baa
def name; "Baa#name"; end
end
class C
include Foo
alias :foo_name :name
include Baa
alias :baa_name :name
undef_method :name
end
c = C.new
c.foo_name
#=> "Foo#name"
c.baa_name
#=> "Baa#name"
C.instance_methods & [:foo_name, :baa_name, :name]
#=> [:foo_name, :baa_name]
The keyword alias is documented here. One may alternatively use the method #alias_method. See this blog for a comparison of the two.
Module#undef_method is not strictly necessary. It's just to ensure that an exception is raised if name is called.
You should definetely read about method lookups.
Anyway, I would do it this way:
module Foo
def name
:foo
end
end
module Bar
def name
:bar
end
end
class MyClass
include Foo
include Bar
def foo_name
Foo.instance_method(:name).bind(self).call
end
def bar_name
Bar.instance_method(:name).bind(self).call
end
#
# or even like this: obj.name(Foo)
#
def name(mod)
mod.instance_method(:name).bind(self).call
end
end
BTW if you are using Module#instance_method and UnboundMethod#bind you don't really need to include specific module. This code works:
Foo.instance_method(:name).bind('any object (e.g. string)').call

Is the current Ruby method called via super?

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.

Execute method like before_filter in Rails

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..

Executing code for every method call in a Ruby module

I'm writing a module in Ruby 1.9.2 that defines several methods. When any of these methods is called, I want each of them to execute a certain statement first.
module MyModule
def go_forth
a re-used statement
# code particular to this method follows ...
end
def and_multiply
a re-used statement
# then something completely different ...
end
end
But I want to avoid putting that a re-used statement code explicitly in every single method. Is there a way to do so?
(If it matters, a re-used statement will have each method, when called, print its own name. It will do so via some variant of puts __method__.)
Like this:
module M
def self.before(*names)
names.each do |name|
m = instance_method(name)
define_method(name) do |*args, &block|
yield
m.bind(self).(*args, &block)
end
end
end
end
module M
def hello
puts "yo"
end
def bye
puts "bum"
end
before(*instance_methods) { puts "start" }
end
class C
include M
end
C.new.bye #=> "start" "bum"
C.new.hello #=> "start" "yo"
This is exactly what aspector is created for.
With aspector you don't need to write the boilerplate metaprogramming code. You can even go one step further to extract the common logic into a separate aspect class and test it independently.
require 'aspector'
module MyModule
aspector do
before :go_forth, :add_multiply do
...
end
end
def go_forth
# code particular to this method follows ...
end
def and_multiply
# then something completely different ...
end
end
You can implement it with method_missing through proxy Module, like this:
module MyModule
module MyRealModule
def self.go_forth
puts "it works!"
# code particular to this method follows ...
end
def self.and_multiply
puts "it works!"
# then something completely different ...
end
end
def self.method_missing(m, *args, &block)
reused_statement
if MyModule::MyRealModule.methods.include?( m.to_s )
MyModule::MyRealModule.send(m)
else
super
end
end
def self.reused_statement
puts "reused statement"
end
end
MyModule.go_forth
#=> it works!
MyModule.stop_forth
#=> NoMethodError...
You can do this by metaprogramming technique, here's an example:
module YourModule
def included(mod)
def mod.method_added(name)
return if #added
#added = true
original_method = "original #{name}"
alias_method original_method, name
define_method(name) do |*args|
reused_statement
result = send original_method, *args
puts "The method #{name} called!"
result
end
#added = false
end
end
def reused_statement
end
end
module MyModule
include YourModule
def go_forth
end
def and_multiply
end
end
works only in ruby 1.9 and higher
UPDATE: and also can't use block, i.e. no yield in instance methods
I dunno, why I was downvoted - but a proper AOP framework is better than meta-programming hackery. And thats what OP was trying to achieve.
http://debasishg.blogspot.com/2006/06/does-ruby-need-aop.html
Another Solution could be:
module Aop
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def before_filter(method_name, options = {})
aop_methods = Array(options[:only]).compact
return if aop_methods.empty?
aop_methods.each do |m|
alias_method "#{m}_old", m
class_eval <<-RUBY,__FILE__,__LINE__ + 1
def #{m}
#{method_name}
#{m}_old
end
RUBY
end
end
end
end
module Bar
def hello
puts "Running hello world"
end
end
class Foo
include Bar
def find_hello
puts "Running find hello"
end
include Aop
before_filter :find_hello, :only => :hello
end
a = Foo.new()
a.hello()
It is possible with meta-programming.
Another alternative is Aquarium. Aquarium is a framework that implements Aspect-Oriented Programming (AOP) for Ruby. AOP allow you to implement functionality across normal object and method boundaries. Your use case, applying a pre-action on every method, is a basic task of AOP.

Resources