Class method behavior during inheritence - ruby

class A
#define class level attribute called key
class << self
attr_accessor :key
end
end
class B < A
end
B.key = "foo"
B.key # returns "foo"
A.key # returns nil
.
What is the approach if I want A.key to return "foo" in the above scenario?

The only way I know is to manually declare the class functions. Subclasses will return the parent's value, but you cannot have them return a different value.
class A
def self.key
##key
end
def self.key=(new_val)
##key = new_val
end
end

Class methods can't be virtual. C'est la vie. When you have a class, you have no virtual table.

Related

How to Best Factor Out Common Class Methods

I am building an in-memory instance model in Ruby. There are a bunch of classes that each get instantiated and managed by class methods on that class. There are a bunch of those class methods, e.g. list all instances, retrieve all instances, etc.
The code for these methods is common across all classes and does not need to take any account of any particularities of those classes. Hence, I would like that code to live in a common place. See the list method below. My question: How to best achieve this.
class A
attr_reader :value
##instances = []
def initialize(value:)
#value = value; ##instances << self
end
def self.list
##instances.each { |i| puts "#{i.value}"}
end
end
class B
attr_reader :value
##instances = []
def initialize(value:)
#value = value; ##instances << self
end
def self.list
##instances.each { |i| puts "#{i.value}"}
end
end
A.new(value: '100')
A.new(value: '101')
B.new(value: '200')
B.new(value: '201')
A.list
B.list
Ideally, I define the list method only once. I have also tried moving that to a super-class:
class Entity
def self.list
##instances.each { |i| puts "AB: #{i.value}"}
end
end
class A < Entity
attr_reader :value
##instances = []
def initialize(value:)
#value = value; ##instances << self
end
end
class B < Entity
attr_reader :value
##instances = []
def initialize(value:)
#value = value; ##instances << self
end
end
...but as one would expect the super-class cannot access the ##instances array of its sub-classes. Moving the ##instances array to the super-class results in the array being common to all classes, which is not what I need.
The main change you need to make is to use class instance variables rather than class variables. For reasons explained here class variables should be used sparingly; class instance variables are generally a better choice, as is illustrated nicely by this question.
class Entity
attr_reader :value
class << self
attr_reader :ins
end
def self.inherited(klass)
klass.instance_variable_set(:#ins, [])
end
def initialize(value:)
#value = value
self.class.ins << self
end
def self.list
#ins.each { |i| puts "#{i.value}"}
end
end
class A < Entity; end
class B < Entity; end
A.new(value: '100')
#=> #<A:0x00005754a59dc640 #value="100">
A.new(value: '101')
#=> #<A:0x00005754a59e4818 #value="101">
A.list
# 100
# 101
B.new(value: '200')
#=> #<B:0x00005754a59f0910 #value="200">
B.new(value: '201')
#=> #<B:0x00005754a59f8b88 #value="201">
B.list
# 200
# 201
I defined a getter for the class instance variable #ins in Entity's singleton class1:
class << self
attr_reader :ins
end
When subclasses of Entity are created the callback method Class::inherited is executed on Entity, passing as an argument the class that has been created. inherited creates and initializes (to an empty array) the class instance variable #ins for the class created.
Another way of doing that, without using a callback method, is as follows.
class Entity
attr_reader :value
class << self
attr_accessor :ins
end
def initialize(value:)
#value = value
(self.class.ins ||= []) << self
end
def self.list
#ins.each { |i| puts "#{i.value}"}
end
end
The fragment:
(self.class.ins ||= [])
sets #ins to an empty array if #ins equals nil. If #ins is referenced before it is created, nil is returned, so either way, #ins is set equal to []. In order to execute this statement I needed to change attr_reader :ins to attr_accessor :ins in order to perform the assignment #ins = [] (though I could have used instance_variable_set instead).
Note that if I were to add the line #ins = [] to Entity (as th first line, say), the instance variable #ins would be created for every subclass when the subclass is created, but that instance variable would not be initialized to an empty array, so that line would serve no purpose.
1. Alternatively, one could write, singleton_class.public_send(:attr_reader, :ins).

Try create my own Struct via Ruby

I can't inherit the Struct. I must implement class which act like Struct.
Is there a way improve my code for use "ClassName" and functional like Struct ? And write k=Dave.new("Rachel" , "Greene") ???
class MyStruct
def self.new(*attributes)
puts "ppp"
dynamic_name = "ClassName"
Kernel.const_set(dynamic_name,Class.new() do
attributes.each do |action|
self.send(:define_method, action) {
puts "call #{action}"
}
end
end
)
end
end
# class ClassName
# def new *args
# puts "iii"
# end
# end
Dave = MyStruct.new(:name, :surname)
k=Dave.new() # k=Dave.new("Rachel" , "Greene")
k.surname
k.name
Here is a version of your code which works:
class MyStruct
def self.new(*attributes)
Class.new do
self.send(:attr_accessor, *attributes)
self.send(:define_method, :initialize) do |*values|
values.each_with_index { |val, i| self.send("#{attributes[i]}=", val) }
end
end
end
end
Dave = MyStruct.new(:name, :surname)
k = Dave.new('Rachel', 'Green')
# => #<Dave:0x00000001af2b10 #name="Rachel", #surname="Green">
k.name
# => "Rachel"
k.surname
# => "Green"
You don't need to const_set inside the method - Dave = is enough
I'm creating here an attr_accessor for each of the attributes, so you are getting a getter and a setter for each
In the initialize method I'm sending each value to its corresponding setter, to set all values. If there are less values than anticipated, the last attributes will not be set, if there are more - an exception will be thrown (undefined method '=')
Have you looked at the Struct class in Ruby?
http://www.ruby-doc.org/core-2.1.2/Struct.html
class MyStruct < Struct.new(:first_name, :last_name)
end
MyClassObj = MyStruct.new("Gavin", "Morrice")
Also, you shouldn't ever overwrite self.new, define initialize instead

Passing values into a class created with Class.new

I have a list of names and values I'm trying to read in and turn into classes so I'm using Class.new.
The end result I want is a number of classes that work as if defined like:
module MyMod
class AA < Base
def self.value
value1
end
end
class AB < Base
def self.value
value2
end
end
...
end
My current code looks like:
name = 'AA'
value = 'test'
MyMod.const_set name, Class.new(Base) do
???
end
Setting the name works great, but haven't figured out what I need in the block for get value in. Calling def doesn't work because the closure for value gets lost.
I have managed to get things working with:
temp = const_set name, Class.new(Base)
temp.define_singleton_method(:value) { value }
However, it seems like there should be a way to do it with the block of Class.new. Also, I'm really not sure define_singleton_method is actually putting the method in the right place. It works in my tests, but I'm not sure if the method is actually where I think it is or somewhere else up the call chain. I've tried various combinations of class_variable_set, attr_reader, class_eval, instance_eval, and others, but it got to a point where it was just guess and check. I think I still haven't quite wrapped my head around metaprogramming :-/
if i correctly understood your question, this should work for you:
class Base
end
class AA < Base
name = :Blah
klass = self.const_set name, Class.new(Base)
class << klass
def value
__method__
end
end
end
p AA::Blah.value
#=> :value
UPDATE: seems you want it defined in the block:
class Base
end
class AA < Base
name = :Blah
klass = Class.new(Base) do
class << self
def value
__method__
end
end
end
self.const_set name, klass
end
p AA::Blah.value
you trying this:
const_set name, Class.new(Base) do
...
end
it does not work cause the block is referring to const_set rather than to Class.new
If you prefer define_singleton_method over class << self:
class Base
end
class AA < Base
name = :Blah
klass = Class.new(Base) do
self.define_singleton_method :value do
__method__
end
end
self.const_set name, klass
end
And finally if you really want to define them at once, use brackets instead of do...end:
class Base
end
class AA < Base
name = :Blah
self.const_set name, Class.new(Base) {
self.define_singleton_method :value do
__method__
end
}
end
Here is a working demo

ruby - return an instance of current class

I noticed if I make a subclass which inherits from Datetime, it's .now will return the subclass instance, not Datetime instance.
class MyDateTime < DateTime
end
MyDateTime.now
>#<MyDateTime: 2012-06-05T16:42:57+08:00 ((2456084j,31377s,900801494n),+28800s,2299161j)>
It seems magical. I can't reproduce this behavior in my own class:
class A
def self.a
return A.new
end
end
class B < A
end
B.a
#<A:0x00000001e22358>
I tried to read the source code of DateTime but it's written in C. Is it possible to write a class method which returns an instance of the class it belongs to?
Try it with self:
class A
def self.a
return self.new
end
end
class B < A
end
B.a

Dynamic object creation and const_set

I'm trying to create enum - like java analog. I've this module.
module Enum
def self.included(base)
super
base.extend(Enum)
base.private_class_method :new
end
def enum(key, fields = {}, &class_body)
value = Class.new(self) do
fields.each_with_index do |item, i|
define_singleton_method(item.first.to_s.downcase) { item.last }
define_singleton_method("order") { i }
end
end
self.const_set key, value
end
end
and
class TestEnum
include Enum
enum :TEST, value: 1
end
When I tried to
>> TestEnum::TEST.is_a? TestEnum
false <-
I've got this. Why? I expected to see 'true'.
But
>> TestEnum::TEST.ancestors
TestEnum::TEST.ancestors
[TestEnum::TEST, TestEnum, Enum, Object
What I'm doing wrong?
obj.is_a? C is true if C is the class of obj, or a module included by obj's class, or one of the superclasses of the class of obj. It is used on instances, not subclasses.
class A; end
class B < A; end
b = B.new
b.is_a? A # => true
If you want to know if a class is a subclass of another given class, simply use ancestors.include?.

Resources