ruby initialize method - purpose of initialize arguments - ruby

Im a bit confused on the initialize method. I understand that it is automatically called when you do Person.new and you add the arguments to it like Person.new("james"). What I dont understand is, why would you have instance variables in your initialize method that are not an used as an argument also. Is it so you can use them later on after the instance has been created?
See below. What reason is there to have #age in the initialize method but not as an argument. thanks.
class Person
attr_accessor :name, :age
def initialize(name)
#name = name
#age = age
end

You can set an instance variable in any method in your class.
initialize is a method that is executed immediately after calling Person.new.
All external data for new object is passed through the arguments of .new(args).
Your line #age = age - it's the same that #age = nil.
This is due to the fact that age is absent in the arguments of initialize.
Also you have attr_accessor :age.
It's equal, that you have methods:
def age
#age
end
def age=(age)
#age = age
end
So you can set instance variable like this:
john = Person.new('John')
p john.age #=> nil
john.age = 5
p john.age #=> 5

The instance variables declared inside your initialize method only need to be those which you want to set during initialization. In your Person class example, you wouldn't need to set #age in initialization (it actually would throw an error as you currently have it).
class Person
attr_accessor :name, :age
def initialize(name)
#name = name
end
def birthday
if #age.nil?
#age = 1
else
#age += 1
end
end
end
Hopefully, this helps. If the initialize method doesn't have an age set, you can still use/set age in other methods. In this case, the first time the Person.birthday method is called, it would set their #age to 1, and then increment it from there.

For example if you need to call a method to assign a value to the instance variable while instantiating the object.
This is silly, but gives an idea:
class Person
attr_accessor :name, :age
def initialize(name)
#name = name
#age = random_age
end
def random_age
rand(1..100)
end
end
jack = Person.new('jack')
p jack.age #=> 29

Related

Ruby object initialization using instance_eval

Consider the following class:
class Person
attr_accessor :first_name
def initialize(&block)
instance_eval(&block) if block_given?
end
end
When I create an instance of Person as follows:
person = Person.new do
first_name = "Adam"
end
I expected the following:
puts person.first_name
to output "Adam". Instead, it outputs only a blank line: the first_name attribute has ended up with a value of nil.
When I create a person likes this, though:
person = Person.new do
#first_name = "Adam"
end
The first_name attribute is set to the expected value.
The problem is that I want to use the attr_accessor in the initialization block, and not the attributes directly. Can this be done?
Ruby setters cannot be called without an explicit receiver since local variables take a precedence over method calls.
You don’t need to experiment with such an overcomplicated example, the below won’t work as well:
class Person
attr_accessor :name
def set_name(new_name)
name = new_name
end
end
only this will:
class Person
attr_accessor :name
def set_name(new_name)
# name = new_name does not call `#name=`
self.name = new_name
end
end
For your example, you must explicitly call the method on a receiver:
person = Person.new do
self.first_name = "Adam"
end
If the code is run with warnings enabled (that is ruby -w yourprogram.rb)
it responds with : "warning: assigned but unused variable - first_name", with a line-number pointing to first_name = "Adam". So Ruby interprets first_name as a variable, not as a method. As others have said, use an explicit reciever: self.first_name.
Try this:
person = Person.new do |obj|
obj.first_name = "Adam"
end
puts person.first_name
I want to use the attr_accessor in the initialization block, and not the attributes directly
instance_eval undermines encapsulation. It gives the block access to instance variables and private methods.
Consider passing the person instance into the block instead:
class Person
attr_accessor :first_name
def initialize
yield(self) if block_given?
end
end
Usage:
adam = Person.new do |p|
p.first_name = 'Adam'
end
#=> #<Person:0x00007fb46d093bb0 #first_name="Adam">

Ruby - initialize inheritance, super with only certain arguments?

I've been playing around with Ruby as of late and I can't seem to find the answer to my question.
I have a class and a subclass. Class has some initialize method, and subclass has its own initialize method that is supposed to inherit some (but not all) variables from it and additionally add its own variables to the subclass objects.
My Person has #name, #age and #occupation.
My Viking is supposed to have a #name and #age which it inherits from Person, and additionally a #weapon which Person doesn't have. A Viking obviously doesn't need any #occupation, and shouldn't have one.
# doesn't work
class Person
def initialize(name, age, occupation)
#name = name
#age = age
#occupation = occupation
end
end
class Viking < Person
def initialize(name, age, weapon)
super(name, age) # this seems to cause error
#weapon = weapon
end
end
eric = Viking.new("Eric", 24, 'broadsword')
# ArgError: wrong number of arguments (2 for 3)
You can make it work in the following ways, but neither solution appeals to me
class Person
def initialize(name, age, occupation = 'bug hunter')
#name = name
#age = age
#occupation = occupation
end
end
class Viking < Person
def initialize(name, age, weapon)
super(name, age)
#weapon = weapon
end
end
eric = Viking.new("Eric", 24, 'broadsword')
# Eric now has an additional #occupation var from superclass initialize
class Person
def initialize(name, age, occupation)
#name = name
#age = age
#occupation = occupation
end
end
class Viking < Person
def initialize(name, age, occupation, weapon)
super(name, age, occupation)
#weapon = weapon
end
end
eric = Viking.new("Eric", 24, 'pillager', 'broadsword')
# eric is now a pillager, but I don't want a Viking to have any #occupation
The question is either
is it by design and I want to commit some Cardinal Sin against OOP principles?
how do I get it to work the way I want to (preferably without any crazy complicated metaprogramming techniques etc)?
How super handles arguments
Regarding argument handling, the super keyword can behave in three ways:
When called with no arguments, super automatically passes any arguments received by the method from which it's called (at the subclass) to the corresponding method in the superclass.
class A
def some_method(*args)
puts "Received arguments: #{args}"
end
end
class B < A
def some_method(*args)
super
end
end
b = B.new
b.some_method("foo", "bar") # Output: Received arguments: ["foo", "bar"]
If called with empty parentheses (empty argument list), no arguments are passed to the corresponding method in the superclass, regardless of whether the method from which super was called (on the subclass) has received any arguments.
class A
def some_method(*args)
puts "Received arguments: #{args}"
end
end
class B < A
def some_method(*args)
super() # Notice the empty parentheses here
end
end
b = B.new
b.some_method("foo", "bar") # Output: Received arguments: [ ]
When called with an explicit argument list, it sends those arguments to the corresponding method in the superclass, regardless of whether the method from which super was called (on the subclass) has received any arguments.
class A
def some_method(*args)
puts "Received arguments: #{args}"
end
end
class B < A
def some_method(*args)
super("baz", "qux") # Notice that specific arguments were passed here
end
end
b = B.new
b.some_method("foo", "bar") # Output: Received arguments: ["baz", "qux"]
Yes, you are committing a Cardinal Sin (obviously, you are aware of it, since you are asking about it). :)
You are breaking Liskov substitution principle (and probably some other named or unnamed rules).
You should probably extract another class as a common superclass, which does not contain occupation. That will make everything much clearer and cleaner.
You can avoid setting a default value to your occupation parameter in Person Class by simply passing the nil argument for occupation in super(). This allows
you to call Viking.new with your 3 arguments (name, age, weapon) without
having to take extra consideration for occupation.
class Person
def initialize(name, age, occupation)
#name = name
#age = age
#occupation = occupation
end
end
class Viking < Person
def initialize(name, age, weapon)
super(name, age, nil)
#weapon = weapon
end
end
eric = Viking.new("Eric", 24, 'broadsword')
p eric
output Viking:0x00007f8e0a119f78 #name="Eric", #age=24, #occupation=nil, #weapon="broadsword"
Really this is just 2 ways - auto include all attributes (just the word super) and super where you pick which arguments get passed up. Doing super() is picking no arguments to hand to the parent class.
Edit: This was meant as a comment on the above comment but they don't allow comments when someone is new... oh well.

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

I can not use in-class assigned property of a class in Ruby

Well, I can do this:
class Person
attr_accessor :name
def greeting
"Hello #{#name}"
end
end
p = Person.new
p.name = 'Dave'
p.greeting # "Hello Dave"
but when I decide to assign the property in the class itself it doesnt work:
class Person
attr_accessor :name
#name = "Dave"
def greeting
"Hello #{#name}"
end
end
p = Person.new
p.greeting # "Hello"
This is the default behavior, albeit a confusing one (especially if you're used to other languages in the OOP region).
Instance variables in Ruby starts being available when it is assigned to and normally this happens in the initialize method of your class.
class Person
def initialize(name)
#name = name
end
end
In your examples you're using attr_accessor, this magical method produces a getter and a setter for the property name. A Person#name and Person#name=, method is created which overrides your "inline" instance variable (that's why your first example works and the second one doesn't).
So the proper way to write your expected behaviour would be with the use of a initialize method.
class Person
def initialize(name)
#name = name
end
def greeting
"Hello, #{#name}"
end
end
Edit
After a bit of searching I found this awesome answer, all rep should go to that question.
Think of a Person class as a blueprint that you can create single person instances with. As not all of these person instances will have the name "Dave", you should set this name on the instance itself.
class Person
def initialize(name)
#name = name
end
attr_accessor :name
def greeting
"Hello #{#name}"
end
end
david = Person.new("David")
p david.greeting
# => "Hello David"
mike = Person.new("Mike")
p mike.greeting
# => "Hello Mike"

Ruby class set/get

What is wrong with this set/get?
class Pupil
def name
#name
end
def name=(name)
#name = name
end
def age
#age
end
def age=(age)
#age
end
end
Further on the same, if there was a child class with 3 arguments, name, age, sex, would the set get method in the child for sex only. Can you please show the set/get method and initialize in the child class.
def age=(age)
#age
end
should be
def age=(age)
#age = age
end
You can also make your code beautiful by replacing get/set with attr_accessor which itself provides a getter/setter
class Pupil
attr_accessor :age,:name
end
You forgot to set #age = age.

Resources