Cache class method instance variable in Ruby - ruby

In this example code where I have child classes (Bar, Baz) inheriting the class methods of the parent (Foo), how might I ensure #foo is only created once across all children?
class Foo
def self.foo
# only want #foo to be set once across any child classes
# that may call this inherited method.
#foo ||= expensive_operation
end
end
class Bar < Foo
def self.bar
self.foo + 'bar'
end
end
class Baz < Foo
def self.baz
self.foo + 'baz'
end
end

Instead of depending on a class-specific instance variable being present, just reference it directly:
class Baz < Foo
def self.baz
Foo.foo + 'baz'
end
end

This is exactly what class instance variable is intended for - language mechanism to have one variable shared between classes which inherit from a class. It's generally not recommended to use because it behaves like you want to - and that's confusing for people coming from other languages.
class Foo
def self.foo
# notice ##
##foo ||= expensive_operation
end
def self.expensive_operation
puts "Expensive operation"
"cached value "
end
end
class Bar < Foo
def self.bar
self.foo + 'bar'
end
end
class Baz < Foo
def self.baz
self.foo + 'baz'
end
end
Foo.foo
Bar.bar
Baz.baz
This prints Expensive operation only once.

Related

Initializing instance variables inside << self blocks

Specifically, why does the following code output nil instead of 1?
class Foo
class << self
#bar = 1
def bar
#bar
end
end
end
p Foo.bar
In the << self block, self is an instance of #<Class:Foo>.
class Foo
puts "self in Foo declaration is: #{self} #{self.class} #{self.object_id}"
class << self
#bar = 1
puts "self in Foo.self declaration is: #{self} #{self.class} #{self.object_id}"
def bar
puts "self in class method is #{self} #{self.class} #{self.object_id}"
#bar
end
end
end
p Foo.bar
This code outputs:
self in Foo declaration is: Foo Class 69910818825400
self in Foo.self declaration is: #<Class:Foo> Class 69910818825380
self in class method is Foo Class 69910818825400
So a slight modification to your code, that works as expected, is:
class Foo
#bar = 1
class << self
def bar
#bar
end
end
end
p Foo.bar
Even though you're modifying the Class definition of Foo with self you are still setting an instance variable which is not available through class scope.
If you want to have a class variable do it like.
class Foo
class << self
##bar = 1
def bar
##bar
end
end
end
p Foo.bar # 1
This is in no way different from:
class Foo
#bar = 1
def bar; #bar end
end
Instance variables belong to objects and are always looked up on self. bar is an instance method, so the #bar inside bar belongs to some instance of Foo (self is the receiver of bar at that point). The #bar at the beginning belongs to the class itself (self is Foo at that point).
Those are simply two completely different objects. The two ยด#bar`s are completely unrelated.
In your example: the first #bar belongs to the singleton class of Foo, the second #bar belongs to the instance of the singleton class of Foo (i.e. Foo).
Here's how you could make it work:
class Foo
#bar = 1
def self.bar; #bar end
end

inheritance changes class of method

The following prints Bar twice:
class Foo
def foo
p self.class # => prints Bar
end
end
class Bar < Foo
def foo
p self.class # => prints Bar
super
end
end
b = Bar.new
b.foo
How do I get it to print
Bar
Foo
? i.e. I want to know what class each method is defined on.
To capture the context in which a method was originally defined, you can use define_method instead of def to get the appropriate closure. A simple example:
class Foo
klass = self
define_method(:foo){p klass}
end
class Bar < Foo
def foo
p self.class
super
end
end
b = Bar.new
b.foo
You could change Foo#foo like so (provided there is just one subclass level):
class Foo
def foo
if self.class == Foo
p self.class
else
p self.class.superclass
end
end
end
class Bar < Foo
def foo
p self.class
super
end
end
Foo.new.foo
Foo
Bar.new.foo
Bar
Foo
You can use
b.class.superclass <= "Foo"
The problem you are having there is that self is the instance of Bar, b.
b.class <= always going to be Bar
self.class <= always going to be Bar if you are invoking Bar.
You say that you are defining a method at runtime, and that you don't know the class name. I don't really know what you mean ... the way I would handle this would be something like
class Bar
def initialize
puts 'In BAR class'
end
def foo
p self.class.name # => prints Bar
end
end
and then
Bar.class_eval do
def brand_new_method
# do something new
p "Still in Bar, but this is dynamically added"
end
end
Maybe you are talking about dynamically adding methods to classes higher in the inheritance chain ... to "Foo" in your example ... based on some conditional happening in an instance of "Bar". If that is the case, then why don't you use a single module to define your inherited methods:
module Foo
def foo
p self.class
end
end
and then use module_eval the same way as class_eval?

Can I overwrite instance method from module?

I know, I can overwrite class method from module this way
class Foo
class << self
def some_static_method
puts 'some_static_method'
end
end
end
module BAR
class << Foo
def some_static_method
puts 'another_static_method'
end
end
end
class Foo
include BAR
end
Foo.some_static_method # => 'another_static_method'
Is it possible for an instance method?
You can do the following:
class Foo
def self.some_static_method; puts "Hello from Foo" end
end
module Bar
def self.included(base)
base.instance_eval do
def some_static_method; puts "Hello from Bar" end
end
end
end
class Foo
include Bar
end
Foo.some_static_method
This should work
UPDATE
To override instance method use:
class Foo
def some_instance_method; puts "Hello from Foo" end
end
module Bar
def self.included(base)
base.class_eval do
def some_instance_method; puts "Hello from Bar" end
end
end
end
class Foo
include Bar
end
Foo.new.some_instance_method
Your question is actually not about method overriding. It is about what class is referred to within a class ... construction in a module body.
When you do
module Bar
class << Foo
p self
end
end
# => #<Class:Foo>
the << Foo points to the singleton class of the Foo in the main environment because class << Foo cannot define the singleton class directly of a class Foo that has not been defined in advance. So it looks up for Foo that is already defined, and such class is found in the main environment.
When you do
module Bar
class Foo
p self
end
end
# => Bar::Foo
a new class Bar::Foo is created; the Foo points to this Bar::Foo that is newly created, and it does not point to the Foo in the main environment. In order to point to it, you have to explicitly specify that with ::.
module Bar
class ::Foo
p self
end
end
# => Foo
If you are using Ruby > 2.0.0 then what you can use is Module#prepend. Instead of include you can prepend an module and that way all of the module's methods are overriding any existing class instance methods with the same name. You can see a quick example here.
Prior to Ruby 2, Rails had introduced a similar hack: #alias_method_chain
Here is a nice comparison of the two approaches.

Get included method names

How I can get all instance method names in the baz method call, which are only present in the Bar module (without other instance methods of this class) ?
class Foo
include Bar
def a
end
def b
end
def baz
#HERE
end
end
class Foo
include Bar
def a
end
def b()
end
def baz
Bar.instance_methods(false)
end
end
puts Foo.new.baz

Ruby Module Inclusion in Methods

In class Foo I'd like to include method Bar under certain conditions:
module Bar
def some_method
"orly"
end
end
class Foo
def initialize(some_condition)
if !some_condition
"bar"
else
class << self; include Bar; end
end
end
end
Is there any cleaner (and clearer) way to achieve the include in the method without having to do it inside the singleton class?
extend is the equivalent of include in a singleton class:
module Bar
def some_method
puts "orly"
end
end
class Foo
def initialize(some_condition)
extend(Bar) if some_condition
end
end
Foo.new(true).some_method # => "orly"
Foo.new(false).some_method # raises NoMethodError

Resources