Unable to override base class method in derived class - ruby

Here are the Ruby classes I have:
class MyBase
class << self
def static_method1
##method1_var ||= "I'm a base static method1"
end
def static_method1=(value)
##method1_var = value
end
def static_method2
##method2_var ||= "I'm a base static method2"
end
def static_method2=(value)
##method2_var = value
end
end
def method3
MyBase::static_method1
end
end
class MyChild1 < MyBase
end
class MyChild2 < MyBase
class << self
def static_method1
##method1_var ||= "I'm a child static method1"
end
end
end
c1 = MyChild1.new
puts c1.method3 #"I'm a base static method1" - correct
c2 = MyChild2.new
puts c2.method3 # "I'm a base static method1" - incorrect. I want to get "I'm a child static method1"
I'm aware of attr_accessor and modules, but I can't use use them here because I want them to give default values in MyBase class. I want to override MyBase.static_method1 in MyChild2.

The problem is that method3 is always explicitly calling the method on the base class. Change it to this:
def method3
self.class.static_method1
end
After that, consider not using ##.
## in ruby is extremely counterintuitive and rarely means what you think it means.
The problem with ## is that it is shared across the all of the inherited classes and the base class. See this blog post for an explanation.

Related

ruby pass self of caller of method into method being called automaticly

I'm trying to create a method that passes the caller as the default last argument. According to this, I only need:
class A
def initialize(object = self)
# work with object
end
end
so that in:
class B
def initialize
A.new # self is a B instance here
end
end
self will be B rather than A;
However, this doesn't seem to work. Here's some test code:
class A
def self.test test, t=self
puts t
end
end
class B
def test test,t=self
puts t
end
end
class T
def a
A.test 'hey'
end
def b
B.new.test 'hey'
end
def self.a
A.test 'hey'
end
def self.b
B.new.test'hey'
end
end
and I get:
T.new.a # => A
T.new.b # => #<B:0x000000015fef00>
T.a # => A
T.b # => #<B:0x000000015fed98>
whereas I expect it to be T or #<T:0x000000015fdf08>. Is there a way to set the default last argument to the caller?
EDIT:
class Registry
class << self
def add(component, base=self)
self.send(component).update( base.to_s.split('::').last => base)
end
end
end
The idea is pretty simple, you would use it like this
class Asset_Manager
Registry.add :utilities
end
and you access it like:
include Registry.utilities 'Debugger'
I'm trying to de-couple classes by having a middle-man management type class that takes care of inter-class communications, auto-loading of missing classes and erroring when it doesn't exist, it works but I just want to be able to use the above rather than:
class Asset_Manager
Registry.add :utilities, self
end
It just feels cleaner, that and I wanted to know if such a thing was possible.
You can't escape the explicit self. But you can hide it with some ruby magic.
class Registry
def self.add(group, klass)
puts "registering #{klass} in #{group}"
end
end
module Registrable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def register_in(group)
Registry.add(group, self)
end
end
end
class AssetManager
include Registrable
register_in :utilities
end
# >> registering AssetManager in utilities
In short, you can't.
Ruby resolves the default arguments in the context of the receiver. That is, the object before the . in a method call. What you called the receiver should be the caller, actually.
class A
def test1(value = a)
puts a
end
def test2(value = b)
puts b
end
def a
"a"
end
end
a = A.new
a.test1 #=> a
def a.b; "b" end
a.test2 #=> b
If I were you, I would use the extended (or included) hook, where both the extending class and the extended module can be accessed. You can program what ever logic you want based on the information.
module Registry
module Utilities
def self.extended(cls)
#puts cls
::Registry.send(component).update( cls.to_s.split('::').last => cls)
end
end
end
class Asset_Manager
extend Registry::Utilities
end

Creating class methods from a module

Given the simple example here:
class Base
#tag = nil
def self.tag(v = nil)
return #tag unless v
#tag = v
end
end
class A < Base
tag :A
end
class B < Base
tag :B
end
class C < Base; end
puts "A: #{A.tag}"
puts "B: #{B.tag}"
puts "A: #{A.tag}"
puts "C: #{C.tag}"
which works as expected
A: A
B: B
A: A
C:
I want to create a module that base will extend to give the same functionality but with all the tag information specified by the class. Eg.
module Tester
def add_ident(v); ....; end
end
class Base
extend Tester
add_ident :tag
end
I've found i can do it with a straight eval, so:
def add_ident(v)
v = v.to_s
eval "def self.#{v}(t = nil); return ##{v} unless t; ##{v} = t; end"
end
but i really dislike using eval string in any language.
Is there a way that i can get this functionality without using eval? I've gone through every combination of define_method and instance_variable_get/set i can think of and i can't get it to work.
Ruby 1.9 without Rails.
You want to define a dynamic method on the singleton class of the class you're extending. The singleton class of a class can be accessed with expression like this: class << self; self end. To open the scope of a class's class, you can use class_eval. Putting all this together, you can write:
module Identification
def add_identifier(identifier)
(class << self; self end).class_eval do
define_method(identifier) do |*args|
value = args.first
if value
instance_variable_set("##{identifier}", value)
else
instance_variable_get("##{identifier}")
end
end
end
end
end
class A
extend Identification
add_identifier :tag
end
If you're using recent versions of Ruby, this approach can be replaced with Module#define_singleton_method:
module Identification
def add_identifier(identifier)
define_singleton_method(identifier) do |value = nil|
if value
instance_variable_set("##{identifier}", value)
else
instance_variable_get("##{identifier}")
end
end
end
end
I don't believe you want to use self.class.send(:define_method), as shown in another answer here; this has the unintended side effect of adding the dynamic method to all child classes of self.class, which in the case of A in my example is Class.
module Tester
def add_ident(var)
self.class.send(:define_method, var) do |val=nil|
return instance_variable_get("##{var}") unless val
instance_variable_set "##{var}", val
end
end
end
My favourite ruby book Metaprogramming Ruby solved these questions like the following way:
module AddIdent
def self.included(base)
base.extend ClassMethods # hook method
end
module ClassMethods
def add_ident(tag)
define_method "#{tag}=" do |value=nil|
instance_variable_set("##{tag}", value)
end
define_method tag do
instance_variable_get "##{tag}"
end
end
end
end
# And use it like this
class Base
include AddIdent
add_ident :tag
end
Bah isn't it always the way that once you get frustrated enough to post you then find the answer :)
The trick seems to be in (class << self; self; end) to give you the class instance without destroying the local scope. Referencing: How do I use define_method to create class methods?
def add_ident(v)
var_name = ('#' + v.to_s).to_sym
(class << self; self; end).send(:define_method, v) do |t = nil|
return instance_variable_get(var_name) unless t
instance_variable_set(var_name, t)
end
end
I'll accept better answers if them come along though.

class << notation in modules

I'm trying to mix a module into a class, and I want some of the methods to behave as class methods and others to be instance methods.
However, I don't want to both include and extend the module. I'd rather just include it.
When I wrap the methods I want to be class methods in this notation, it works:
class <<
# ...
end
However, when I use this notation it doesn't work:
class << self
# ...
end
I suspect the self keyword is establishing an explicit binding to the module, rather than the class it gets mixed into. But I've not seen any documentation that recommends leaving the self keyword off when using the class << notation.
Does anyone know what's going on with this?
UPDATE: Here's some sample code for more clarity:
module M
class <<
def class_method
puts "From inside the class_method"
end
end
def instance_method
puts "From inside the instance_method"
end
end
class Object
include M
end
class C
end
C.class_method
obj = C.new
obj.instance_method
class << must always be followed by an object. Just class <<; end is a syntax error. In your case it looks like it works because of the following:
class <<
def class_method
puts "From inside the class_method"
end
end
is the same as
class << def class_method
puts "From inside the class_method"
end
end
which is the same as
temp = def class_method
puts "From inside the class_method"
end
class << temp
end
which is the same as
def class_method
puts "From inside the class_method"
end
class << nil
end
which is the same as
def class_method
puts "From inside the class_method"
end
Of course that doesn't actually define a class method. It defines an instance method.
Yeah, if you want to get a real self in your module you should use included callback. Something like this point you in the right direction:
module Bar
def self.included(base)
class << base
def class_method
"class_method"
end
end
end
end
class Foo
include Bar
end
p Foo.class_method # => "class_method"

How do you delegate class methods?

I would like to make a class which delegates all instance and class methods to another class. My aim is to be able to give a class which is set in the app options a name which appears to fit in with the code around it.
This is what I have so far:
require 'delegate'
class Page < DelegateClass(PageAdapter)
# Empty
end
However, this only seems to delegate instance methods, not class methods.
You could define your own delegation scheme:
class Page < DelegateClass(PageAdapter)
##delegate_class=PageAdaptor
def self.delegate_class_method(*names)
names.each do |name|
define_method(name) do |*args|
##delegate_class.__send__(name, *args)
end
end
end
delegate_class_method ##delegate_class.singleton_methods
end
You don't want delegation, you want direct subclassing:
class Page < PageAdapter
# Empty
end
Proof:
class Foo
def self.cats?
"YES"
end
def armadillos?
"MAYBE"
end
end
class Bar < Foo; end
p Bar.new.armadillos?
#=> "MAYBE"
p Bar.cats?
#=> "YES"

Ruby module with a static method call from includer class

I need to define the constant in the module that use the method from the class that includes this module:
module B
def self.included(base)
class << base
CONST = self.find
end
end
end
class A
def self.find
"AAA"
end
include B
end
puts A::CONST
But the compiler gives the error on the 4th line.
Is there any other way to define the constant?
The more idiomatic way to achieve this in Ruby is:
module B
def self.included(klass)
klass.class_eval <<-ruby_eval
CONST = find
ruby_eval
# note that the block form of class_eval won't work
# because you can't assign a constant inside a method
end
end
class A
def self.find
"AAA"
end
include B
end
puts A::CONST
What you were doing (class << base) actually puts you into the context of A's metaclass, not A itself. The find method is on A itself, not its metaclass. The thing to keep in mind is that classes are themselves objects, and so have their own metaclasses.
To try to make it clearer:
class Human
def parent
# this method is on the Human class and available
# to all instances of Human.
end
class << self
def build
# this method is on the Human metaclass, and
# available to its instance, Human itself.
end
# the "self" here is Human's metaclass, so build
# cannot be called.
end
def self.build
# exactly the same as the above
end
build # the "self" here is Human itself, so build can
# be called
end
Not sure if that helps, but if you don't understand it, you can still use the class_eval idiom above.
In your specific case.
module B
def self.included(base)
base.const_set("CONST", base.find)
end
end
class A
def self.find
"AAA"
end
include B
end
puts A::CONST
Despite it works, it's a little bit messy. Are you sure you can't follow a different way to achieve your goal?
module B
def self.included(base)
class << base
CONST = self.find
end
end
end
class A
class << self
def self.find
"AAA"
end
end
include B
end
then the compiler error is fixed, pls try.

Resources