Inherit class-level instance variables in Ruby? - ruby

I want a child class to inherit a class-level instance variable from its parent, but I can't seem to figure it out. Basically I'm looking for functionality like this:
class Alpha
class_instance_inheritable_accessor :foo #
#foo = [1, 2, 3]
end
class Beta < Alpha
#foo << 4
def self.bar
#foo
end
end
class Delta < Alpha
#foo << 5
def self.bar
#foo
end
end
class Gamma < Beta
#foo << 'a'
def self.bar
#foo
end
end
And then I want this to output like this:
> Alpha.bar
# [1, 2, 3]
> Beta.bar
# [1, 2, 3, 4]
> Delta.bar
# [1, 2, 3, 5]
> Gamma.bar
# [1, 2, 3, 4, 'a']
Obviously, this code doesn't work. Basically I want to define a default value for a class-level instance variables in the parent class, which its subclasses inherit. A change in a subclass will be the default value then for a sub-subclass. I want this all to happen without a change in one class's value affecting its parent or siblings. Class_inheritable_accessor gives exactly the behavior I want... but for a class variable.
I feel like I might be asking too much. Any ideas?

Rails has this built into the framework as a method called class_attribute. You could always check out the source for that method and make your own version or copy it verbatim. The only thing to watch out for is that you don't change the mutable items in place.

What I did in my project for using resque is to define a base
class ResqueBase
def self.inherited base
base.instance_variable_set(:#queue, :queuename)
end
end
In the other child jobs, the queue instance will be set by default.
Hope it can help.

Use a mixin:
module ClassLevelInheritableAttributes
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def inheritable_attributes(*args)
#inheritable_attributes ||= [:inheritable_attributes]
#inheritable_attributes += args
args.each do |arg|
class_eval %(
class << self; attr_accessor :#{arg} end
)
end
#inheritable_attributes
end
def inherited(subclass)
#inheritable_attributes.each do |inheritable_attribute|
instance_var = "##{inheritable_attribute}"
subclass.instance_variable_set(instance_var, instance_variable_get(instance_var))
end
end
end
end
Including this module in a class, gives it two class methods: inheritable_attributes and inherited.
The inherited class method works the same as the self.included method in the module shown. Whenever a class that includes this module gets subclassed, it sets a class level instance variable for each of declared class level inheritable instance variables (#inheritable_attributes).

Related

Getting 'instance' variable from parent metaclass

With the following code, in what way can I access #arr from Child?
class Parent
class << self
def create_singleton_variable
#arr = [1,2,3]
end
def arr
#arr
end
end
end
class Child < Parent
def get
puts self.arr
end
def self.get
puts self.arr
end
end
p "class method call #{Child.get}"
#=> ➜ ruby child.rb
#=> "class method call "
c = Child.new
p "instance call #{c.get}"
#=> ➜ ruby child.rb
#=> Traceback (most recent call last):
#=> 1: from child.rb:24:in `<main>'
#=> child.rb:15:in `get': undefined method `arr' for #<Child:0x00007fe0eb02e7d8> (NoMethodError)
I've tried many other ways as well, but don't feel the need to post them here.
edit to the question, since it appears I do need a bit more context:
I'm attempting to prepend a module into the Thor framework. I want to then access this bit of code
module ThorExtensions
module Thor
module CompletionGeneration
def self.prepended(base)
base.singleton_class.prepend(ClassMethods)
end
module ClassMethods
def completion
puts "Start Completion"
p self
p self.superclass
p self.class.superclass.subcommands
puts "End Completion"
end
end
end
end
end
results in
Start Completion
Debug
Thor
bundler: failed to load command: exe/pt (exe/pt)
NoMethodError: undefined method `subcommands' for Module:Class
/Users/tyler.thrailkill/Documents/code/backend/pt-cli/lib/thor_extensions/completion_generation.rb:13:in `completion'
/Users/tyler.thrailkill/Documents/code/backend/pt-cli/lib/debug/debug.rb:24:in `<class:Debug>'
/Users/tyler.thrailkill/Documents/code/backend/pt-cli/lib/debug/debug.rb:4:in `<top (required)>'
/Users/tyler.thrailkill/Documents/code/backend/pt-cli/lib/pt.rb:5:in `require'
/Users/tyler.thrailkill/Documents/code/backend/pt-cli/lib/pt.rb:5:in `<top (required)>'
exe/pt:13:in `require'
exe/pt:13:in `<top (required)>'
which of course is not what I want. It appears that maybe my issue is with prepending?
Edit 2
I seem to have done a terrible job of explaining my issue with prepending. Here is a fully working example showing my issue. I believe this is due to how prepending something to a class essentially creates another Class in the call stack that is called first. My hope is that I'm actually still able to access this method somehow.
class Parent
class << self
def create_singleton_variable
#arr = [1,2,3]
puts "arr is initialized #{#arr}"
end
# ... lots of code here.
def arr
puts "arr is #{#arr.inspect}"
#arr
end
end
end
module CompletionGeneration
def self.prepended(base)
base.singleton_class.prepend(ClassMethods)
end
module ClassMethods
def completion
puts "self.superclass.arr == #{self.superclass.arr.inspect}" # unable to access superclass arr
puts "self.class.superclass.arr == #{self.class.superclass.arr}" # likewise, unable to access Child's metaclass superclass
rescue Exception => e
# do nothing, this is just so you can see arr is actually initialized in the context of the Child
p e
end
end
end
Parent.prepend CompletionGeneration
class Child < Parent
create_singleton_variable
completion
arr
end
Child.new
results in the output
➜ ruby child.rb
arr is initialized [1, 2, 3]
arr is nil
self.superclass.arr == nil
#<NoMethodError: undefined method `arr' for Module:Class>
arr is [1, 2, 3]
This code should be simply copy and pastable as is.
Here is your code, slightly modified.
class Parent
def self.create_singleton_variable
#arr = [1,2,3]
end
def self.arr
puts "self = #{self} in the getter for #arr"
#arr
end
end
class Child < Parent
def get
puts self.arr
end
def self.get
puts self.arr
end
end
I have written Parent in the more conventional way. Except for the addition of the puts statement, it is equivalent to that contained in the question.
First, a head-slapper: Kernel#puts-anything returns nil. You need to remove puts from both methods:
class Child < Parent
def get
self.arr
end
def self.get
self.arr
end
end
Parent.create_singleton_variable
#=> [1, 2, 3]
Child.get.nil?
self = Child in the getter for #arr
#=> true
We see that within the getter arr, invoked by Child's class method get, self equals Child, so the method looks for a class instance variable #arr of Child not of Parent. As no such instance variable has been initialized, nil is returned.
You need the following.
class Parent
class << self
def create_singleton_variable
#arr = [1,2,3]
end
def arr
puts "self = #{self} in the getter for #arr"
#arr
end
end
end
class Child < Parent
def get
self.class.superclass.arr
end
def self.get
superclass.arr
end
end
The crucial difference to that given in the question is that Class#superclass changes the scope (i.e., self) to Parent.
We see the desired result is obtained.
Child.get
self = Parent in the getter for #arr
#=> [1, 2, 3]
Child.new.class.superclass.arr
self = Parent in the getter for #arr
#=> [1, 2, 3]
A common misconception is that the Child class method defined def self.get; self.arr; end invokes the getter Parent::arr, and therefore returns the value of Parent's instance variable #arr. It is Child::arr that is invoked, however, that method having been inherited from Parent, and it is Child's class instance variable #arr that is being retrieved, a subtle, but important, distinction.
Edit 2
The first observation is that Parent can be written in the more conventional (and completely equivalent) way.
class Parent
def self.create_singleton_variable
#arr = [1,2,3]
puts "arr is initialized #{#arr}"
end
def self.arr
puts "arr is #{#arr.inspect}"
#arr
end
end
Regardless of how its written, self will equal Parent when either class method is involked on parent. The first, therefore, will create the class instance variables #arr.
Parent.methods(false)
#=> [:create_singleton_variable, :arr]
Parent.instance_variables
#=> []
Parent.ancestors
#=> [Parent, Object, Kernel, BasicObject]
Now let's create a class variable for Parent.
Parent.create_singleton_variable
# arr is initialized [1, 2, 3]
Parent.instance_variables
#=> [:#arr]
Now let me change the value of #arr.
Parent.instance_variable_set(:#arr, ['dog', 'cat'])
#=> ["dog", "cat"]
Parent.arr
# arr is ["dog", "cat"]
#=> ["dog", "cat"]
Next, create the class Child, but do not yet prepend the module.
class Child < Parent
create_singleton_variable
arr
end
arr is initialized [1, 2, 3]
arr is [1, 2, 3]
Child.ancestors
#=> [Child, Parent, Object, Kernel, BasicObject]
Child.instance_variables
#=> [:#arr]
Child.instance_variable_get(:#arr)
#=> [1, 2, 3]
There are no surprises. Next load the module.
module CompletionGeneration
def self.prepended(base)
base.singleton_class.prepend(ClassMethods)
end
module ClassMethods
def completion
puts "self=#{self}"
puts "superclass=#{superclass}"
puts "self.class=#{self.class}"
puts "self.class.superclass == #{self.class.superclass}"
puts "superclass.arr == #{superclass.arr.inspect}"
puts "self.class.superclass.arr == #{self.class.superclass.arr}"
rescue Exception => e
# do nothing, this is just so you can see arr is actually
# initialized in the context of the Child
puts "Exception => e=#{e}"
end
end
end
(Note self. is not needed in "superclass.arr == #{superclass.arr.inspect}") Now prepend this module to Parent.
Parent.prepend CompletionGeneration
Parent.ancestors
#=> [CompletionGeneration, Parent, Object, Kernel, BasicObject]
Parent.methods.include?(:completion)
#=> true
Child.ancestors
#=> [Child, CompletionGeneration, Parent, Object, Kernel, BasicObject]
Child.methods.include?(:completion)
#=> true
The callback modules method CompletionGeneration::prepended is fired with base equal to Parent, causing Parent's singleton class to prepend ClassMethods, thereby adding the class method Parent::completion. Since Parent did not previously have a method by that name using prepend or include would have the same effect. Further, instead of Parent.singleton_class.include ClassMethods, one could have used the included(base) callback instead, and executed Parent.extend ClassMethods. Perhaps prepend is being used here for a general case where Parent may have a class method by that name.1
Now execute the following.
Child.completion
self=Child
superclass=Parent
self.class=Class
self.class.superclass == Module
arr is ["dog", "cat"]
superclass.arr == ["dog", "cat"]
Exception => e=undefined method `arr' for Module:Class
The exception was raised when
puts "self.class.superclass.arr == #{self.class.superclass.arr}"
was being executed. As that amounts to
puts "self.class.superclass.arr == #{Module.arr}"
but of course Module has no module method arr.
1 In view of Child.ancestors, prepending Parent with the module only causes Parent's children to include (rather than prepend) the module; that is, if a child already has a method completion before the prepending, that method will not be preempted by the the module's method by the same name.

How to use 'attr_accesor'

I have a class with a private method:
class MyClass
attr_accessor :my_attr
def some_mth?(num)
# I want to use my_attr as a variale #myattr here
#and here i want to check if arr include num
#myattr.include?(num)
end
private
def some_pvt_mth
#myattr = [1,2,3,4]
for example generation array here
end
end
When I call #myattr inside some_mth, my variable #myattr is nil
How to use variable #myatt inside class, in every method is it possible?
How do I do it properly?
You do not need to define attr_accessor in order to use an instance variable within the defined class. It's purpose is to create a 'getter' and a 'setter' method, but those are only needed for other classes to access the data.
This is a class:
class Foo
def initialize
#my_attr = [1,2,3,4]
end
def attr_includes?(x)
#my_attr.include?(x)
end
end
There's no attr accessor, but this will work.
The attr accessor essentially includes this code in your class...
class Foo
def my_attr
#my_attr
end
def my_attr=(x)
#my_attr = x
end
end
But if you don't want that, you can just leave it out, and access the variable via other methods (such as your include example).
You have to define the instance variable value first:
class MyClass
attr_accessor :my_attr
def initialize
#myattr = [1, 2, 3, 4]
end
def some_mth?(num)
#myattr.include?(num)
end
end

Extend return value of class instance method

I have a class that have an instance method, that returns a hash. I can't change the code of that class directly, but I can extend it with modules. I need to add some new keys to the returning hash of the method. Something like this:
class Processor
def process
{ a: 1 }
end
end
module ProcessorCustom
def process
super.merge(b: 2) # Not works :(
end
end
Processor.send :include, ProcessorCustom
processor = Processor.new
processor.process # returns { a: 1 }, not { a: 1, b: 2 }
How can I do that? Thanks.
You could call prepend instead of include:
Processor.prepend(ProcessorCustom)
processor = Processor.new
processor.process
#=> {:a=>1, :b=>2}
prepend and include result in different ancestor order:
module A; end
module B; end
module C; end
B.ancestors #=> [B]
B.include(C)
B.ancestors #=> [B, C]
B.prepend(A)
B.ancestors #=> [A, B, C]
Alternatives
Depending on your use-case, you could also extend a specific instance: (this doesn't affect other instances)
processor = Processor.new
processor.extend(ProcessorCustom)
processor.process
#=> {:a=>1, :b=>2}
Or use SimpleDelegator to implement a decorator pattern:
require 'delegate'
class ProcessorCustom < SimpleDelegator
def process
super.merge(b: 2)
end
end
processor = ProcessorCustom.new(Processor.new)
processor.process #=> {:a=>1, :b=>2}
I think the first option to consider would be the one requiring the least work by the reader to comprehend; and in Object Oriented software that would be a subclass to specialize the behavior of the superclass. I would deviate from this if, and only if, there were a compelling reason to do so.
How about this?:
#!/usr/bin/env ruby
class Processor
def foo
{ x: 3 }
end
end
class MyProcessor < Processor
def foo
super.merge({ y: 7 })
end
end
p MyProcessor.new.foo # outputs: {:x=>3, :y=>7}
I think it's better to create a proxy than to pollute the original class.
class Proxy
def initialize(target)
#target = target
end
# add some syntactic sugar
singleton_class.class_eval { alias [] new }
def process
#target.process.merge!(b: 2)
end
end
Proxy[Processor.new].process #=> {a: 1, b: 2}
You can even create your own dynamic proxy.
class DynamicProxy < BasicObject
def initialize(target)
#target = target
end
# again, add some syntactic sugar
singleton_class.class_eval { alias [] new }
def method_missing(name, *args, &block)
super unless #target.respond_to?(name)
# Do something before calling the target method
result = #target.send(name, *args, &block)
# Do something after calling the target method
end
def respond_to_missing?(name, include_private = false)
#target.respond_to?(name, include_private)
end
end
To hide the detail of creating Processor instance, you can make a simple factory to handle the creation.
module ProcessorFactory
def self.create
DynamicProxy[Processor.new]
end
end
Then you can do your job
ProcessorFactory.create.process

Having a module singleton extending a class

I have a singleton in my application that gets reused across applications. I want that singleton to get some default methods from my class, but also be able to customize the module/eigenclass. Most of all, I don't want to call up instance on every call the the utility singleton.
Here's an example. Let's say my default class is Universe::Earth. Then I want an Earth module in my application that "extends" that class.
module Universe
class Earth
def self.grow!
#grown = true
end
end
end
module Earth
class < Universe::Earth << self; end
grow!
end
When that's run, grow! is a NoMethodError.
Tried those approaches:
Class.new(Goodluck::Contest) << self
class < Universe::Earth << self; end
extend Universe::Earth
How do I make it work?
Is this the sort of thing you are looking for?
module Universe
class Earth
def self.grow!
#grown = true
end
end
end
module Earth
Universe::Earth.class_eval do
define_method(:instance_howdy) do
puts "instance_howdy!"
end
end
def (Universe::Earth).class_howdy
puts "class_howdy!"
end
end
Universe::Earth.methods(false) #=> [:grow!, :class_howdy]
Universe::Earth.instance_methods(false) #=> [:instance_howdy]
Universe::Earth.new.instance_howdy #=> instance_howdy!
Universe::Earth.class_howdy #=> class_howdy!
[Edit: If you just want to set #grown => true, and retrieve it's value, you merely need:
module Earth
Universe::Earth.grow! #=> true
end
Verify:
Universe::Earth.instance_variable_get("#grown") #=> true
If you wish to also add an accessor for the class instance variable, you could do this:
def add_class_accessor(c, accessor, var)
c.singleton_class.class_eval("#{accessor} :#{var}")
end
Universe::Earth.methods(false)
#=> [:grow!]
module Earth
Universe::Earth.grow! #=> true
add_class_accessor(Universe::Earth, "attr_accessor", "grown")
end
Universe::Earth.methods(false)
#=> [:grow!, :grown, :grown=]
Universe::Earth.grown
#=> true
Universe::Earth.grown = "cat"
#=> "cat"
Universe::Earth.grown
#=> "cat"
Object#singleton_class was added in Ruby 1.9.2. For earlier versions you could do this:
def add_class_accessor(c, accessor, var)
eigenclass = class << c; self; end
eigenclass.class_eval("#{accessor} :#{var}")
end
You might consider putting add_class_accessor in a module to be included as needed. Other methods you might add to the same module might be:
add_instance_method(klass, method, &block)
add_class_method(klass, method, &block)
add_instance_accessor(klass, accessor, var)
:tidE

Delegate attribute methods to the parent object

I have the following class:
class Alphabet
attr_reader :letter_freqs, :statistic_letter
def initialize(lang)
#lang = lang
case lang
when :en
#alphabet = ('A'..'Z').to_a
#letter_freqs = { ... }
when :ru
#alphabet = ('А'..'Я').to_a.insert(6, 'Ё')
#letter_freqs = { ... }
...
end
#statistic_letter = #letter_freqs.max_by { |k, v| v }[0]
end
end
foo = Alphabet.new(:en)
The central member here is #alphabet.
I'd like to make it some sort of a container class to invoke Array methods directly like
foo[i]
foo.include?
instead of explicitly accessing #alphabet:
foo.alphabet[i]
foo.alphabet.include?
I know I could define a lot of methods like
def [](i)
#alphabet[i]
end
but I'm looking for a proper way of "inheriting" them.
You can use Forwardable (it is included in the Ruby standard library):
require 'forwardable'
class Alphabet
extend Forwardable
def_delegators :#alphabet, :[], :include?
def initialize
#alphabet = ('A'..'Z').to_a
end
end
foo = Alphabet.new
p foo[0] #=> "A"
p foo.include? 'ç' #=> false
If you wish to delegate all the methods not defined by your class you can use SimpleDelegator (also in the standard library); it lets you delegate all the methods that are not responded by the instance to an object specified by __setobj__:
require 'delegate'
class Alphabet < SimpleDelegator
def initialize
#alphabet = ('A'..'Z').to_a
__setobj__(#alphabet)
end
def index
'This is not #alphabet.index'
end
end
foo = Alphabet.new
p foo[0] #=> "A"
p foo.include? 'ç' #=> false
p foo.index #=> "This is not #alphabet.index"
When the delegate doesn't need to be dynamic you can arrange the master class to be a subclass of DelegateClass, passing the name of the class to be delegated as argument and calling super passing the object to be delegated in the #initialize method of the master class:
class Alphabet < DelegateClass(Array)
def initialize
#alphabet = ('A'..'Z').to_a
super(#alphabet)
end
More info about the delegation design pattern in Ruby here
You could extend the Forwardable module:
class Alphabet
require 'forwardable'
extend Forwardable
attr_accessor :alphabet
def initialize
#alphabet = [1,2,3]
end
def_delegator :#alphabet, :[], :include?
end
Then you can do:
alpha = Alphabet.new
alpha[1]==hey.alphabet[1]
=> true
Warning:
Don't try to delegate all methods (don't know if that's even possible) since they probably share some of the same method names such as class, which would probably make chaos.
In Ruby you can extend Objects like this.
class Array
def second
self[1]
end
end
[1, 2, 3, 4, 5].second
# => 2
Or if you want to inherit an Array.
class Foo < Array
def second
self[1]
end
end
[1, 2, 3, 4, 5].include? 2
# => true
[1, 2, 3, 4, 5].second
# => 2
If you have any further questions, comment and I will update the answer.

Resources