What I want to achieve is something like below, i.e. calling a base class method from extended modules method:
class BaseClass
def behavior
puts 'base class behavior'
end
end
module ChildModule
def behavior
super.behavior
puts 'child module behavior'
end
end
o = BaseClass.new
o.extend ChildModule
o.behavior
and it outputs as follows (with ruby 1.9.2p290 (2011-07-09 revision 32553) [x86_64-linux])
base class behavior
t.rb:9:in `behavior': undefined method `behavior' for nil:NilClass (NoMethodError)
from t.rb:16:in `<main>'
My guess is that I can not use super, as super does not exist in the module. But it prints out that line from the super method, is that strange?
How do I achieve it what I want above?
Answer by #davidrac is working, however being more curious, would like to know, how can I get a handle to base class instance? Say for instance I added the following method to BaseClass
def behavior2
puts 'base class behavior2'
end
and overrides it in ChildModule. Now from ChildModule behavior can I make a call to behavior2 of BaseModule?
I think the correct syntax is:
module ChildModule
def behavior
super
puts 'child module behavior'
end
end
Related
How can one get the class a method was defined in?
I've found how to look up descendents and ansestors: Look up all descendants of a class in Ruby
But that doesn't necessarily get me the defining class (last defining class really).
I've found how to get the calling class:
Ruby Inheritance Get Caller Class Name
But I want the opposite. I would like how to get the defining class.
I've also tried Module.nesting. That gets me what I want in this case, but I worry it will be inconsistent and not acceptable in a larger codebase of which I don't have ultimate control.
puts RUBY_VERSION
# Test class vs super.
class Super
def test_func
puts "#{self.class}, #{ __method__}"
end
end
class Child < Super
def test_func2
self.test_func
end
end
Child.new.test_func
I had hoped for:
1.8.7
Super, test_func
But got:
1.8.7
Child, test_func
You asked self.class of Child object and you got it.
You need use Method#owner to return the class or module that defines the method.
class Super
def test_func
puts "#{method(__method__).owner}, #{ __method__}"
end
end
class Child < Super
def test_func2
self.test_func
end
end
Child.new.test_func
# will print: Super, test_func
or just
Child.new.method(:test_func).owner
#=> Super
So why is this happening? It has to be a namespace error, I just don't understand where it is. I add a method to Fixnum like so in a file file.rb
module M
class Fixnum
def foo
return true
end
end
end
then I'll make a test like so:
require 'minitest/autorun'
require './file.rb' #the path is correct
class SomeTest < MiniTest::Test
def test_foo
assert 3.foo
end
end
which will in turn throw a
NoMethodError: undefined method `foo' for 3:Fixnum
when I run the test, and I am left scratching my head - even if I include M to include the module (applying the namespace?) for the test it still throws the error. I can use custom classes just fine, it's only when I try to add a method to an existing "open class".
Yes, you have defined your own M::Fixnum class which actually has nothing to do with ::Fixnum in the global namespace. The following will solve an issue:
module M
class ::Fixnum
def foo
return true
end
end
end
5.foo
#⇒ true
Please note, in the code above module M has no sense, since the code nevertheless monkey-patches the global Fixnum. The code is here just to show how you would monkey-patch the global class from inside another module code.
Plus, Ruby2 introduced refinements, which are likely what you are intended to use.
I am writing a method that will define an instance method inside a class; something similar to attr_accessor:
class Foo
custom_method(:foo)
end
I have implemented that by adding custom_method function to the Module module, and defining the method with define_method, which works fine. But I cannot figure out how to take into account visibility attributes from the class. For example, in the following class
class Foo
custom_method(:foo)
private
custom_method(:bar)
end
the first generated method (foo) must be public, and the second one (bar) must be private. How do I do that? Or, how do I find the context in which my custom_method is called: private, public, or protected?
Thanks!
After experimenting with this for a bit, I'm completely baffled. Initially, I thought that Ruby took the default visibility (public, private, or protected) into account when you call Module#define_method. It turns out though that on Ruby versions <= 2.0, that's not the case:
class Foo
private
define_method :foo do
puts "Foo called!"
end
end
Foo.new.foo # Prints "Foo called!"
On Ruby 2.1+, it's even more confusing. Module#define_method seems to take default method visibility into account:
class Foo
private
define_method :foo do
puts "Foo called!"
end
end
Foo.new.foo # NoMethodError: private method `foo' called for #<Foo:0x8cb75ac>
But it only works when you are calling define_method from directly inside the class. Calling a method which then calls define_method doesn't work:
class Foo
def self.hello_on name
define_method name do
puts "Hello, #{name}!"
end
end
private
hello_on :foo
end
Foo.new.foo # Prints "Hello, foo!"
Dang it Ruby! Why?
Okay, this calls for desperate measures...
module DefaultMethodVisibilityAccessor
attr_reader :current_default_method_visibility
def public(*args)
#current_default_method_visibility = :public if args.empty?
super
end
def protected(*args)
#current_default_method_visibility = :protected if args.empty?
super
end
def private(*args)
#current_default_method_visibility = :private if args.empty?
super
end
end
class Module
prepend DefaultMethodVisibilityAccessor
end
module MethodDefiner
def hello_on name
define_method name do
puts "Hello, #{name}!"
end
case current_default_method_visibility
when :public
public name
when :protected
protected name
when :private
private name
end
end
end
Usage:
class Foo
extend MethodDefiner
hello_on :foo
private
hello_on :bar
end
Foo.new.foo # Prints "Hello, foo!"
Foo.new.bar # NoMethodError: private method `bar' called for #<Foo:0x8ec18fc>
There, fixed!
I think this is impossible, because the scope visibility level set by Module.private is managed at the C virtual machine level and not exposed to Ruby.
EDIT: and it's only available in the same syntactical scope that it is called, so when you call custom_method it loses the visibility level set inside the class declaration.
It's set in set_visibility(), and used in vm_define_method(), but I can't find any reference to the corresponding variable being available from Ruby.
I suggest using some kind of custom parameter to specify the visibility level of your methods.
You can use Module#private_method_defined? to verify if a method is defined as private
Suppose I want to override a method of an existing class Array as below.
class Array
def to_s
self.join(',')
end
end
So my question is - How does this overriding work? Does this add this definition of method to_s to the class Array? I mean if the class contained 'n' method definitions, it would now contain 'n+1' method definitions. Also, is it that there are two definitions of to_s method and the one that is added last is the one that would work?
Thanks.
You aren't overriding the method, you are re-defining it. The one that was there is gone, replaced with what you put in. This is a risky thing to do w/the standard libraries, you don't know what behavior other code is relying upon.
You can try it w/your own class.
class Foo
def bar
puts 'One'
end
end
class Foo
def bar
puts 'Two'
end
end
Foo.new.bar
# Two
class Foo
def bar
puts 'Three'
super
end
end
Foo.new.bar
# Three
# test.rb:18:in `bar': super: no superclass method `bar'
# for #<Foo:0x007fd642029278> (NoMethodError)
I have some base class A with a method that is not to be overridden.
class A
def dont_override_me
puts 'class A saying, "Thank you for not overriding me!"'
end
end
And another class B that extends A and tries to override the dont_override_me method.
class B < A
def dont_override_me
puts 'class B saying, "This is my implementation!"'
end
end
If I instantiate B and call dont_override_me, class B's instance method will be called.
b = B.new
b.dont_override_me # => class B saying, "This is my implementation!"
This is because of ruby's properties. Understandable.
However, how do I force the base class method dont_override_me to be non-overridable by it's derived classes? I could not find a keyword like final in java for ruby. In C++, the base class methods can be made non-virtual so that they become non-overridable by the derived classes. How do I achieve this in ruby?
You can do it, by hooking the change event and changing it back, but it seems a bit smelly to me:
http://scie.nti.st/2008/9/17/making-methods-immutable-in-ruby
It's one of those things that sort of defines Ruby, so fighting against it seems a little pointless imo. If someone redefines something so it breaks horribly.. that's their problem ;-)
Here's a way to do it:
http://www.thesorensens.org/2006/10/06/final-methods-in-ruby-prevent-method-override/
This has also been packaged into a gem called "finalizer" (gem install finalizer)
This makes use of the method_added callback and compares the new method name with a list of methods that you wish to make final.
I recommend:
class A #This is just as you've already defined it.
def dont_override_me
puts 'class A saying, "Thank you for not overriding me!"'
end
end
module BehaviorForB
def dont_override_me
puts 'class B saying, "This is my implementation!"'
end
def greet
"Hello, Friend."
end
end
class B < A
include BehaviorForB
end
b = B.new
b.dont_override_me #=> class A saying, "Thank you for not overriding me!"
b.greet #=> Hello, Friend.
By keeping B's methods tucked away in an mixin, you get exactly what you desire. Any method of B's methods that are not already in A will be available. Methods that are already in A will not be overridden.
One way to prevent a method from being overridden by a subclass (but not recommend) :
class Class
def frozen_method(method)
if class_variable_defined?(:##__frozen_methods__)
add= class_variable_get(:##__frozen_methods__) | [method]
class_variable_set(:##__frozen_methods__,add)
else
class_variable_set(:##__frozen_methods__,[method])
end
class << self
def inherited(child)
def method_added(method)
if class_variable_get(:##__frozen_methods__).include? method
send(:remove_method, method)
error="Cannot change method #{method} because it's not overridde"
raise TypeError, error
end
end
end
end
end
end
class Foo
def hello
'hello'
end
def foo
'foo'
end
frozen_method :foo
end
class Bar < Foo
def foo
'new foo'
end
end
#=> TypeError: Cannot change method foo because it's not overridde
Bar.new.foo #=> 'foo'
Warning: this example is not complete. If you add frozen_method for a previously defined method in the subclass, when this method will be modified in the subclass, it will lose its implementation.