To override class constants in a subclass one can do:
class Foo
CONST = [:foo, :baz]
def self.const
self::CONST
end
end
class Bar < Foo
CONST = [:foo, :bar]
end
print Foo.const # [:foo, :baz]
print Bar.const # [:foo, :bar]
This works as expected. The problem is when I try to call it from a class method, e.g when using define_method:
class Foo
CONST = [:foo, :baz]
def self.const
self::CONST
end
self.const.each do |c|
define_method("#{c}?") {
"#{c} exists"
}
end
end
foo = Foo.new
print foo.baz? # baz exists.
bar = Bar.new
print bar.bar? # undefined method `bar?'
How would one go about overriding the class constant so the right method bar? will get defined in this case, without having to duplicate the code in the subclass? Is there a DRY way of doing this without having to use class variables instead of class constants?
Because define_method is running in lexical scope, i.e. it's inline in the body of the Foo class definition so there is nothing to cause it to run in Bar.
class Foo
CONST = [:foo, :baz]
def self.define_const_methods(const)
const.each do |c|
define_method("#{c}?") { "#{c} exists" }
end
end
define_const_methods(CONST)
end
class Bar < Foo
CONST = [:foo, :bar]
define_const_methods(CONST)
end
That should do the trick. So you call define_const_methods at the end of the Foo class in it's lexical scope. And you also call it on any class that inherits from it. The inheriting class should find it's own version of that constant.
But this is pretty ugly, so you could dispense with the constants altogether and just use the define_const_methods to define them. Just like ActiveRecord does when it defines the association methods (has_one, has_many etc). So then you can whittle it down to;
class Foo
def self.define_my_methods(meths)
meths.each do |c|
define_method("#{c}?") { "#{c} exists" }
end
end
define_my_methods [:foo, :baz]
end
class Bar < Foo
define_my_methods [:foo, :bar]
end
Related
Here is an external class which has its class method overwritten.
class Foo
def class
"fooo"
end
end
class Boo < Foo
end
class Moo < Foo
end
Now I have an instance of the subclass. Is it possible to find out which class it belongs to?
foo.class # currently returns 'fooo', I want get Boo or Moo.
You could use instance_method to grab the class method from somewhere safe (such as Object) as an UnboundMethod, bind that unbound method to your instance, and then call it. For example:
class_method = Object.instance_method(:class)
# #<UnboundMethod: Object(Kernel)#class>
class_method.bind(Boo.new).call
# Boo
class_method.bind(Moo.new).call
# Moo
class_method.bind(Foo.new).call
# Foo
Of course, if you've also replaced Object#class (or Kernel#class) then all bets are off and you're in a whole new world of pain and confusion.
An object's class also happens to be the superclass of its singleton_class:
Boo.new.singleton_class.superclass
#=> Boo
Moo.new.singleton_class.superclass
#=> Moo
Foo.new.singleton_class.superclass
#=> Boo
I prefer #Stefan’s and #muistooshort's solutions, but here's an alternative.
class Foo
def class
"foo"
end
end
class Boo < Foo
end
class Who < Boo
def class
"who"
end
end
boo = Boo.new
boo.method(:class).super_method.call
#=> Boo
who = Who.new
who.method(:class).super_method.call
#=> "foo"
who.method(:class).super_method.super_method.call
#=> Who
More generally:
def my_class(obj)
m = obj.method(:class)
until m.owner == Kernel do
m = m.super_method
end
m.call
end
my_class(boo)
#=> Boo
my_class(who)
#=> Who
See Method#super_method.
This solution is a little hacky, but overriding class is pretty hacky in itself, so when in Rome, do as Romans do:
class Foo
def class
'fooo'
end
end
class Boo < Foo
end
boo = Boo.new
=> #<Boo:0x00007fd2361feba8>
boo.class
=> "fooo"
boo.inspect
=> "#<Boo:0x00007fd2361feba8>"
klass = boo.inspect.split(':').reject(&:empty?)[0..-2].join('::').sub('#<', '')
=> "Boo"
boo.is_a?(Kernel.const_get(klass))
=> true
This also works for classes in a module:
module Bar
class Foo
def class
'fooo'
end
end
class Boo < Foo
end
end
boo = Bar::Boo.new
=> #<Bar::Boo:0x00007fe5a20358b0>
boo.class
=> "fooo"
boo.inspect
=> "#<Bar::Boo:0x00007fe5a20358b0>"
klass = boo.inspect.split(':').reject(&:empty?)[0..-2].join('::').sub('#<', '')
=> "Bar::Boo"
boo.is_a?(Kernel.const_get(klass))
=> true
Suppose I have a setup like this:
class Foo
attr_accessor :bar
def initialize
#bar = Bar.new
end
end
class Bar
def bar_method
self.class # => Bar
whatever???.class # => Foo
end
end
foo = Foo.new
foo.bar.bar_method
I know that I can set up the method like this:
def bar_method(selfs_self)
selfs_self.class # => Foo
end
And call the method like this: foo.bar.bar_method(foo) to get what I want. But that seems pretty redundant. Is there any way, inside of bar_method, that I can get a reference to foo, without specifically passing in a reference to it?
No.
Usually this is done by passing a reference to the parent object when initializing child objects, like:
class Foo
attr_accessor :bar
def initialize
#bar = Bar.new(self)
end
end
class Bar
attr_reader :foo
def initialize(foo)
#foo = foo
end
def bar_method
self.class # => Bar
foo.class # => Foo
end
end
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.
Can someone explain why self.foo= works outside of the class << self, while foo= does not work within the class << self.
class A
class << self
attr_accessor :foo
foo = 'foo'
end
end
p A.foo # => "nil"
class A
class << self
attr_accessor :foo
end
self.foo = 'foo'
end
p A.foo # => "foo"
This is not the same question as When to use 'self' in Ruby
To be clear, I am not asking when to use self. I am asking why I cannot set the class variable inside a 'class << self' block, but I can outside of it.
your second example isn't a class variable, it's a class instance variable to this class the proof is that if you inherited like this you'll get nil
class A
class << self
attr_accessor :foo
end
self.foo = 'foo'
end
class B < A
end
B.foo # => nil
So if you want to add a class variable in class << self you can use ##
class A
class << self
##foo = :foo
def foo
##foo
end
end
end
When inheriting a class, I want to save class variables to the inheriting class instead of the parent class... like so...
class Foo
def self.inherited klass
klass.title = klass.to_s
end
def self.title= title
##title = title
end
def self.title
##title
end
end
class Bar < Foo
end
class Baz < Foo
end
Bar.title # returns `Baz` instead of `Bar`
Baz.title
This is a contrived example because after playing around with it, I more want to just understand than anything.
You need to change the class variable to instance variable.
class Foo
def self.inherited klass
klass.title = klass.to_s
end
def self.title= title
#title = title
end
def self.title
#title
end
end
class Bar < Foo
end
class Baz < Foo
end
Bar.title # => "Bar"
Baz.title # => "Baz"
##title class variable is a shared variable. Baz, Bar and Foo all are using the same class variable ##title. When you did class Baz < Foo ;end, then ##title has been updated to the name of Baz, thus all call to the getter method #title is giving currently updated value of the variable #title.
Follow the code below :-
class Foo
def self.inherited klass
klass.title = klass.to_s
end
def self.title= title
##title = title
end
def self.title
##title
end
end
class Bar < Foo
end
# see the output here
Bar.title # => "Bar"
class Baz < Foo
end
# here the output changed, as ##title value is holding the currently updated
#value
Bar.title # => "Baz"
Baz.title # => "Baz"
You can do that using Module#class_variable_get and Module#class_variable_set:
class Foo
def self.title= title
class_variable_set(:##title, title)
end
def self.title
class_variable_get(:##title)
end
end
class Bar < Foo
end
class Baz < Foo
end
Bar.title = 'Bar'
Baz.title = 'Baz'
Bar.title #=> "Bar"
Bar.title = "Cat"
Baz.title #=> "Baz"
Bar.class_variables #=> [:##title]
Baz.class_variables #=> [:##title]
Foo.class_variables #=> []
Foo.methods(false) #=> [:title=, :title]
Bar.methods(false) #=> []
Baz.methods(false) #=> []
If we were to execute Bar's a getter before its setter, we'd get:
Bar.title #=> NameError: uninitialized class variable ##title in Bar
If desired, the class variables can be initialized (to nil, say) by adding the following to the class definition:
def self.inherited klass
klass.class_variable_set(:##title, nil)
end