Avoiding initialize method in ruby - 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

Related

Passing class as argument in Ruby

I have a Ruby class with all static methods. I want to use these methods in another class. Passing just the class name works but wondering if its the right way to go or is there another way.
class Foo
IDENTIFIER = 'UYT78'
def self.some_data
DB.fetch_records
end
# and so forth
end
class Bar
IDENTIFIER = 'XXXXX'
def self.some_data
DB.fetch_records
end
# and so forth
end
class Reporter
def report
Foo.some_data.select {|x| x.id == Foo::identifier}
end
def other_report
Bar.some_data.select {|x| x.id == Foo::identifier}
end
end
Can Reporter be changed to accept Foo as argument to the methods so these methods can be re-used based on the argument being passed?
Reporter.report(Foo)
Reporter.report(Bar)
and report method looks like
def report(klass)
klass.some_data.select {|x| x.id == Foo::identifier}
end
This approach works but passing a class name like this as argument doesn't feel right? Or may be it is?
That's fine... generally it's referred to as duck-typing... as long as the class passed supports #some_data method, it doesn't really matter what specific class it is. If it looks like a duck, and quacks like a duck...
It's possible you may want to store the target class at initialization.
class Reporter
attr_accessor :klass
def initialize(klass)
self.klass = klass
end
def report
klass.some_data.select
end
end
my_foo_reporter = Reporter.new(Foo)
my_foo_reporter.report # calls some_date on Foo class
What you are trying to do is nice but rather than passing the class like that I would use ruby's require.
foo.rb
class Foo
IDENTIFIER = 'UYT78'.freeze
def self.some_data
'Foo'
end
end
bar.rb
class Bar
IDENTIFIER = 'XXXXX'.freeze
def self.some_data
'Bar'
end
end
reporter.rb
require './foo'
require './bar'
class Reporter
def report
p Foo.some_data
p Foo::IDENTIFIER
end
def other_report
p Bar.some_data
p Bar::IDENTIFIER
end
end
Reporter.new.report
puts '----------------'
Reporter.new.other_report
Which would return like below (Just and example)-
"Foo"
"UYT78"
----------------
"Bar"
"XXXXX"
In my opinion it's much cleaner.
Perhaps this is what you are looking for.
class Foo
IDENTIFIER = 'UYT78'
def self.some_data
['ABC21', 'UYT78']
end
end
class Bar
IDENTIFIER = 'XXXXX'
def self.some_data
['XXXXX', 'DEF38']
end
end
class Reporter
def report(klass)
k = Module.const_get(klass)
k.public_send(:some_data).select {|x| x == k::IDENTIFIER}
end
end
reporter = Reporter.new
reporter.report("Foo") #=> ["UYT78"]
reporter.report("Bar") #=> ["XXXXX"]
reporter.report(:Bar) #=> ["XXXXX"]

Use current class in constant declaration

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"]

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"

Pre-defining variables

This is my class structure:
class Commodity
def initialize(name)
#name = name
end
class PriceSeries
def initialize(name)
#name = name
end
end
end
I want to instantiate the Commodity class:
gold = Commodity.new("gold")
then instantiate the PricePoint class:
gold.xau = Commodity::PriceSeries.new("gold")
It seems that I can only do this with an attribute accessor called xau in the gold class. Is this the only way to define that variable?
def xau
xau
end
I feel like this shouldn't be a function.
Well, there are a lot of ways to do it, but attr_accessor is by far the simplest:
class Commodity
attr_accessor :xau
end
gold = Commodity.new("gold")
gold.xau = some_value
What attr_accessor :xau does is defines a xau= method that assigns its argument to the instance variable #xau, and another method xau that returns the value of #xau. In other words, it basically does this:
class Commodity
# Setter
def xau=(value)
#xau = value
end
# Getter
def xau
#xau
end
end
For convenience and readability, Ruby lets you put whitespace before the = in gold.xau = foo, but in fact it's the same as the method call gold.xau=(foo).
I'm pretty sure what you want is attr_accessor:
class Commodity
attr_accessor :xau
end
Which is essentially equivalent to this:
class Commodity
def xau
#xau
end
def xau=(value)
#xau = value
end
end
It is not clear what you want to do, but it looks like you want to do this:
class Commodity
def initialize(name)
#name = name
#xau = Commodity::PriceSeries.new(name)
end
end
Then,
gold = Commodity.new("gold")
will automatically define
Commodity::PriceSeries.new("gold")
as the value of an instance variable #xau of gold.

How do I write a writer method for a class variable in Ruby?

I'm studying Ruby and my brain just froze.
In the following code, how would I write the class writer method for 'self.total_people'? I'm trying to 'count' the number of instances of the class 'Person'.
class Person
attr_accessor :name, :age
##nationalities = ['French', 'American', 'Colombian', 'Japanese', 'Russian', 'Peruvian']
##current_people = []
##total_people = 0
def self.nationalities #reader
##nationalities
end
def self.nationalities=(array=[]) #writer
##nationalities = array
end
def self.current_people #reader
##current_people
end
def self.total_people #reader
##total_people
end
def self.total_people #writer
#-----?????
end
def self.create_with_attributes(name, age)
person = self.new(name)
person.age = age
person.name = name
return person
end
def initialize(name="Bob", age=0)
#name = name
#age = age
puts "A new person has been instantiated."
##total_people =+ 1
##current_people << self
end
You can define one by appending the equals sign to the end of the method name:
def self.total_people=(v)
##total_people = v
end
You're putting all instances in ##current_people you could define total_people more accurately:
def self.total_people
##current_people.length
end
And get rid of all the ##total_people related code.
I think this solves your problem:
class Person
class << self
attr_accessor :foobar
end
self.foobar = 'hello'
end
p Person.foobar # hello
Person.foobar = 1
p Person.foobar # 1
Be aware of the gotchas with Ruby's class variables with inheritance - Child classes cannot override the parent's value of the class var. A class instance variable may really be what you want here, and this solution goes in that direction.
One approach that didn't work was the following:
module PersonClassAttributes
attr_writer :nationalities
end
class Person
extend PersonClassAttributes
end
I suspect it's because attr_writer doesn't work with modules for some reason.
I'd like to know if there's some metaprogramming way to approach this. However, have you considered creating an object that contains a list of people?

Resources