Use current class in constant declaration - ruby

class Cat
SUPERSTARS = [Cat.new('John'), Cat.new('Alfred')]
attr_accessor :name
def initialize(name)
#name = name
end
end
I get an error
ArgumentError: wrong number of arguments(1 for 0)
because initialize is not again defined.
If I put the definition on the end:
class Cat
attr_accessor :name
def initialize(name)
#name = name
end
SUPERSTARS = [Cat.new('John'), Cat.new('Alfred')]
end
it works.
Is it possible to keep constant declaration on the top of the file?

I think, you can use this trick:
class Cat
def self.const_missing(name)
[Cat.new('John'), Cat.new('Alfred')] if name == :SUPERSTARS
end
attr_accessor :name
def initialize(name)
#name = name
end
end

Avdi Grimm in his free episode of rubytapas called "Barewords" recommends that you don't use constants directly. Instead, wrap them in a method. And when you have a method, you don't even need the constant (which in ruby is not really constant anyway).
So, you can do something like this:
class Cat
def self.superstars
#superstars ||= [Cat.new('John'), Cat.new('Alfred')]
end
attr_accessor :name
def initialize(name)
#name = name
end
end
The only difference is that you now call it Cat.superstars instead of Cat::SUPERSTARS. Your code now works and looks better too! I call it a win-win. :)

The problem is that class definitions are just code that gets executed when the definition is reached. It's like asking if you can access the value of a local variable that you have not yet defined.
A possible workaround is to delay the evaluation of the constant until the class body is executed. Note that it's not really worth it and it shows a working solution that shouldn't be used in practice:
# BasicObject for minimal amount of methods
class Retarded < BasicObject
def initialize(&value)
#value = value
end
# Make sure we remove as many methods as we can to
# make the proxy more transparent.
(instance_methods - %i(__id__ __send__ __binding__)).each do |name|
undef_method name
end
def method_missing(*args, &block)
# Get the actual value
value = #value.call
# Suppress warnings while we traverse constants
warn_level, $VERBOSE = $VERBOSE, nil
traversed_modules = []
constant_finder = -> (root) do
constant_name = root.constants.find do |constant|
# We undefed ==, so we need a different way to compare.
# Given that this class should be used for constant values in place,
# comparing object ids does the job.
root.const_get(constant).__id__ == __id__
end
if constant_name
# Just set the constant to the actual value
root.const_set(constant_name, value)
else
# Recursively search for the containing module of the constant
root.constants.each do |child_name|
child_constant = root.const_get(child_name)
if child_constant.is_a?(::Module) &&
!traversed_modules.include?(child_constant)
# Handle circular references
traversed_modules.push child_constant
constant_finder.(child_constant)
end
end
end
end
# ::Object is the root module where constants are contained
constant_finder.(::Object)
# Bring back warnings
$VERBOSE = warn_level
# We have already found the constant and set its value to whatever was
# passed in the constructor. However, this method_missing was called
# in an attempt to call a method on the value. We now should make that
# invocation.
value.public_send(*args, &block)
end
end
# I know, the example is mononic.
class EdwardKing
DEAD = Retarded.new { [new('I'), new('II'), new('III')] }
def initialize(number)
#number = number
end
def whoami
"Edward #{#number} of England"
end
end
p EdwardKing::DEAD
# [#<EdwardKing:0x007f90fc26fd10 #number="I">, #<EdwardKing:0x007f90fc26fc70 #number="II">, #<EdwardKing:0x007f90fc26fc20 #number="III">
p EdwardKing::DEAD.map(&:whoami)
# ["Edward I of England", "Edward II of England", "Edward III of England"]

Related

Avoiding initialize method in ruby

I am writing the Ruby program found below
class Animal
attr_reader :name, :age
def name=(value)
if value == ""
raise "Name can't be blank!"
end
#name = value
end
def age=(value)
if value < 0
raise "An age of #{value} isn't valid!"
end
#age = value
end
def talk
puts "#{#name} says Bark!"
end
def move(destination)
puts "#{#name} runs to the #{destination}."
end
def report_age
puts "#{#name} is #{#age} years old."
end
end
class Dog < Animal
end
class Bird < Animal
end
class Cat < Animal
end
whiskers = Cat.new("Whiskers")
fido = Dog.new("Fido")
polly = Bird.new("Polly")
polly.age = 2
polly.report_age
fido.move("yard")
whiskers.talk
But when I run it, it gives this error:
C:/Users/akathaku/mars2/LearningRuby/Animal.rb:32:in `initialize': wrong number of arguments (1 for 0) (ArgumentError)
from C:/Users/akathaku/mars2/LearningRuby/Animal.rb:32:in `new'
from C:/Users/akathaku/mars2/LearningRuby/Animal.rb:32:in `<main>'
My investigations shows that I should create objects like this
whiskers = Cat.new("Whiskers")
Then there should be an initialize method in my code which will initialize the instance variable with the value "Whiskers".
But if I do so then what is the purpose of attribute accessors that I am using? Or is it like that we can use only one and if I have to use attribute accessors then I should avoid initializing the instance variables during object creation.
initialize is the constructor of your class and it runs when objects are created.
Attribute accessors are used to read or modify attributes of existing objects.
Parameterizing the constructor(s) gives you the advantage of having a short and neat way to give values to your object's properties.
whiskers = Cat.new("Whiskers")
looks better and it's easier to write than
whiskers = Cat.new
whiskers.name = "Whiskers"
The code for initialize in this case should look like
class Animal
...
def initialize(a_name)
name = a_name
end
...
end
All attr_reader :foo does is define the method def foo; #foo; end. Likewise, attr_writer :foo does so for def foo=(val); #foo = val; end. They do not do assume anything about how you want to structure your initialize method, and you would have to add something like
def initialize(foo)
#foo = foo
end
Though, if you want to reduce boilerplate code for attributes, you can use something like Struct or Virtus.
You should define a method right below your class name, something like
def initialize name, age
#name = name
#age = age
end

Is it bad practice to declare instance variable values based on the name of the class?

Let's say I have a couple subclasses that inherit from a base class like this:
class Base
def initialize
#name = self.class.name
#num1 = 10;
#num2 = 5;
end
def speak
puts "My name is #{name}"
end
end
class Sub1 < Base
end
class Sub2 < Base
end
class Sub3 < Base
end
Is it bad practice to declare the instance variable of #name to the name of the class? Would it be better to do something like this?
class Base
def initialize
#feet = 10;
#inches = 5;
end
def speak
puts "My name is #{name}"
end
end
class Sub1 < Base
def initialize
super()
#name = "Sub1"
end
end
class Sub2 < Base
def initialize
super()
#name = "Sub2"
end
end
class Sub3 < Base
def initialize
super()
#name = "Sub3"
end
end
If #name is always intended to be equal to the class name, you don't need it – just use self.class.name or myObject.class.name directly, or define an accessor method which returns it if you don't want to use .class.name from the outside. Initializing it manually would be bad practice since you'd always have to remember to do it in every subclass and changes to subclass names would need to be replicated to the initialization.
If #name is not always going to equal the class name in every subclass, then personally I'd still provide a default implementation where it is initialized from self.class.name and let subclasses override it as necessary.
The first method is more concise. What you probably should be doing is evaluating this in a lazy capacity:
def name
#name ||= self.class.to_s.split(/::/).last
end
That way you don't need to worry about assignment and any subclass can initialize if necessary with an override. Your speak method will continue to work without modification as presumably you had declared attr_reader :name for that to work in the first place.
I think it's quite pointless, look at this output:
class Base
def name
self.class.name
end
end
class Sub1 < Base
# This declaration is useless
def name
self.class.name
end
end
class Sub2 < Base
end
b = Base.new
b.name # "Base"
c = Sub1.new
c.name # "Sub1"
d = Sub2.new
d.name # "Sub2" (this function is inherited from base, but is called from Sub2 class context)
Next thing in ruby, you can use superclass method to get parent class name
Sub2.superclass.name # "Base"

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

What does ##variable mean in Ruby?

What are Ruby variables preceded with double at signs (##)? My understanding of a variable preceded with an at sign is that it is an instance variable, like this in PHP:
PHP version
class Person {
public $name;
public function setName($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
Ruby equivalent
class Person
def set_name(name)
#name = name
end
def get_name()
#name
end
end
What does the double at sign ## mean, and how does it differ from a single at sign?
A variable prefixed with # is an instance variable, while one prefixed with ## is a class variable. Check out the following example; its output is in the comments at the end of the puts lines:
class Test
##shared = 1
def value
##shared
end
def value=(value)
##shared = value
end
end
class AnotherTest < Test; end
t = Test.new
puts "t.value is #{t.value}" # 1
t.value = 2
puts "t.value is #{t.value}" # 2
x = Test.new
puts "x.value is #{x.value}" # 2
a = AnotherTest.new
puts "a.value is #{a.value}" # 2
a.value = 3
puts "a.value is #{a.value}" # 3
puts "t.value is #{t.value}" # 3
puts "x.value is #{x.value}" # 3
You can see that ##shared is shared between the classes; setting the value in an instance of one changes the value for all other instances of that class and even child classes, where a variable named #shared, with one #, would not be.
[Update]
As Phrogz mentions in the comments, it's a common idiom in Ruby to track class-level data with an instance variable on the class itself. This can be a tricky subject to wrap your mind around, and there is plenty of additional reading on the subject, but think about it as modifying the Class class, but only the instance of the Class class you're working with. An example:
class Polygon
class << self
attr_accessor :sides
end
end
class Triangle < Polygon
#sides = 3
end
class Rectangle < Polygon
#sides = 4
end
class Square < Rectangle
end
class Hexagon < Polygon
#sides = 6
end
puts "Triangle.sides: #{Triangle.sides.inspect}" # 3
puts "Rectangle.sides: #{Rectangle.sides.inspect}" # 4
puts "Square.sides: #{Square.sides.inspect}" # nil
puts "Hexagon.sides: #{Hexagon.sides.inspect}" # 6
I included the Square example (which outputs nil) to demonstrate that this may not behave 100% as you expect; the article I linked above has plenty of additional information on the subject.
Also keep in mind that, as with most data, you should be extremely careful with class variables in a multithreaded environment, as per dmarkow's comment.
# - Instance variable of a class
## - Class variable, also called as static variable in some cases
A class variable is a variable that is shared amongst all instances of a class. This means that only one variable value exists for all objects instantiated from this class. If one object instance changes the value of the variable, that new value will essentially change for all other object instances.
Another way of thinking of thinking of class variables is as global variables within the context of a single class.
Class variables are declared by prefixing the variable name with two # characters (##). Class variables must be initialized at creation time
## denotes a class variable, i.e. it can be inherited.
This means that if you create a subclass of that class, it will inherit the variable. So if you have a class Vehicle with the class variable ##number_of_wheels then if you create a class Car < Vehicle then it too will have the class variable ##number_of_wheels
The answers are partially correct because ## is actually a class variable which is per class hierarchy meaning it is shared by a class, its instances and its descendant classes and their instances.
class Person
##people = []
def initialize
##people << self
end
def self.people
##people
end
end
class Student < Person
end
class Graduate < Student
end
Person.new
Student.new
puts Graduate.people
This will output
#<Person:0x007fa70fa24870>
#<Student:0x007fa70fa24848>
So there is only one same ##variable for Person, Student and Graduate classes and all class and instance methods of these classes refer to the same variable.
There is another way of defining a class variable which is defined on a class object (Remember that each class is actually an instance of something which is actually the Class class but it is another story). You use # notation instead of ## but you can't access these variables from instance methods. You need to have class method wrappers.
class Person
def initialize
self.class.add_person self
end
def self.people
#people
end
def self.add_person instance
#people ||= []
#people << instance
end
end
class Student < Person
end
class Graduate < Student
end
Person.new
Person.new
Student.new
Student.new
Graduate.new
Graduate.new
puts Student.people.join(",")
puts Person.people.join(",")
puts Graduate.people.join(",")
Here, #people is single per class instead of class hierarchy because it is actually a variable stored on each class instance. This is the output:
#<Student:0x007f8e9d2267e8>,#<Student:0x007f8e9d21ff38>
#<Person:0x007f8e9d226158>,#<Person:0x007f8e9d226608>
#<Graduate:0x007f8e9d21fec0>,#<Graduate:0x007f8e9d21fdf8>
One important difference is that, you cannot access these class variables (or class instance variables you can say) directly from instance methods because #people in an instance method would refer to an instance variable of that specific instance of the Person or Student or Graduate classes.
So while other answers correctly state that #myvariable (with single # notation) is always an instance variable, it doesn't necessarily mean that it is not a single shared variable for all instances of that class.
# and ## in modules also work differently when a class extends or includes that module.
So given
module A
#a = 'module'
##a = 'module'
def get1
#a
end
def get2
##a
end
def set1(a)
#a = a
end
def set2(a)
##a = a
end
def self.set1(a)
#a = a
end
def self.set2(a)
##a = a
end
end
Then you get the outputs below shown as comments
class X
extend A
puts get1.inspect # nil
puts get2.inspect # "module"
#a = 'class'
##a = 'class'
puts get1.inspect # "class"
puts get2.inspect # "module"
set1('set')
set2('set')
puts get1.inspect # "set"
puts get2.inspect # "set"
A.set1('sset')
A.set2('sset')
puts get1.inspect # "set"
puts get2.inspect # "sset"
end
class Y
include A
def doit
puts get1.inspect # nil
puts get2.inspect # "module"
#a = 'class'
##a = 'class'
puts get1.inspect # "class"
puts get2.inspect # "class"
set1('set')
set2('set')
puts get1.inspect # "set"
puts get2.inspect # "set"
A.set1('sset')
A.set2('sset')
puts get1.inspect # "set"
puts get2.inspect # "sset"
end
end
Y.new.doit
So use ## in modules for variables you want common to all their uses, and use # in modules for variables you want separate for every use context.

Ruby - How to use the method parameter as the name of the variable?

How would I use the parameter value as the instance variable name of an object?
This is the object
Class MyClass
def initialize(ex,ey)
#myvar = ex
#myothervar = ey
end
end
I have the following method
def test(element)
instanceofMyClass.element #this obviously doesnt work
end
How can I have the test method return either myvar or myothervar value depending on the element parameter. I don't want to write an if condition though, I want to pass myvar or myother var via element to the object instance if possible.
def test(element)
instanceofMyClass.send(element.to_sym)
end
You'll get a missing method error if instanceofMyClass doesn't respond to element.
def test(element)
instanceofmyclass.instance_variable_get element
end
test :#myvar # => ex
test :#myothervar # => ey
I like the simplicity of send(), though one bad thing with it is that it can be used to access privates. The issue is still remains solution below, but at least then it's explicitly specified, and reader can see which methods are to be forwarded. The first one just uses delegation, while the second one uses more dynamic way to define methods on the fly.
require 'forwardable'
class A
extend Forwardable
def_delegators :#myinstance, :foo, :bar
class B
def foo
puts 'foo called'
end
def bar
puts 'bar called'
end
def quux
puts 'quux called'
end
def bif
puts 'bif called'
end
end
def initialize
#myinstance = B.new
end
%i(quux bif).each do |meth| # note that only A#quux and A#bif are defined dynamically
define_method meth do |*args_but_we_do_not_have_any|
#myinstance.send(meth)
end
end
end
a = A.new
a.foo
a.bar
a.quux
a.bif

Resources