Ruby, class attribute with limited range - ruby

I have been working with Ruby for three days. I am trying to set a default in the way shown below, for #happiness. i need #happiness to have a range of 0-10, cannot be less than zero or more than ten. Thanks in advance.
class Person
attr_reader :name
attr_accessor :bank_account, :happiness={value > 11}
def initialize(name)
#name = name
#bank_account = 25
#happiness = 8
end
end

If you need a writer method that performs some validation, then you have to write a writer method which performs that validation. For example, something like this:
class Person
attr_reader :name, :happiness
attr_accessor :bank_account
def initialize(name)
#name = name
#bank_account = 25
#happiness = 8
end
def happiness=(value)
raise ArgumentError, "`happiness` needs to be between 0 and 10, but you supplied `#{value}`" unless 0..10 === value
#happiness = value
end
end

Related

ruby initialize method - purpose of initialize arguments

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

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

Access variables in methods that are in a class, outside of that class

I have the following code that creates a viking game character and gives them random stats such as health, age and strength.
class Viking
def initialize(name, health, age, strength)
#name = name
#health = health
#age = age
#strength = strength
end
def self.create_warrior(name)
age = rand * 20 + 15
health = [age * 5, 120].min
strength = [age/2, 10].min
Viking.new(name, health, age, strength)
end
end
brad = Viking.create_warrior("Brad")
puts "New Warrior Created!"
The create_warrior function returns all those values, but how do I access them so I could see the stats.
For example this doesn't work but I would like to see the age or health of the new Viking brad (i.e brad.age even though that wouldn't work because it's not a method).
So how do I access those variables (without making them global).
Use attr_accessor :name, :health, :age, :strength if you would like the variables to be both readable and writable or attr_reader :name, :health, :age, :strength if you would like them to be read only.
After this you can access with brad.varname e.g. brad.name etc...
[Documentation]
Use attr_reader :age, so you can simply use brad.age, same goes for the other variables
If you really don't want them global, try instance_variable_get method http://apidock.com/ruby/Object/instance_variable_get
puts brad.instance_variable_get(:#age)
Use the attr_reader method, it creates a attribute method to point to the variable in the initialize method which is private. Its read-only
You can use attr_writer to write only
And attr_accessor to both read and write
class Viking
attr_reader :age, :name, :health, :strength
def initialize(name, health, age, strength)
#name = name
#health = health
#age = age
#strength = strength
end
def self.create_warrior(name)
age = rand * 20 + 15
health = [age * 5, 120].min
strength = [age/2, 10].min
Viking.new(name, health, age, strength)
end
end
brad = Viking.create_warrior("Brad")
puts "New Warrior Created!"

Ruby make 2 different constructors with same ammount of parameters [duplicate]

This question already has answers here:
In Ruby is there a way to overload the initialize constructor?
(7 answers)
Closed 9 years ago.
I've got a class just like this:
class Equipment
attr_reader :text
attr_reader :name
attr_reader :array1
attr_reader :number
end
then, I want to make 2 constructors with 3 parameters each:
1º one -> (text,name,array1)
2º one -> (text, name,number)
The first one as an argument has an array and the other one has an integer (1,2...), so I need to define both constructors so when I create an object of this class it makes a difference between array or integer as the 3º argument.
Any ideas?
EDIT.: I thought this:
def initialize(text = "", name = "", array = array.new, number =0)
#text = text
#name = name
#array1 = array
#number = number
end
(initializing all of them) then:
def Equipment.newc_witharray(sometext, somename, somearray)
#text = sometext
#name = somename
#array1 = somearray
end
def Equipment.newc_withint(sometext, somename, somenumber)
#text = text
#name = name
#number = somenumber
end
and finally calling objects like this:
character1 = Equipment.newc_withint("Barbarian", "conan", 3)
shouldn't this work?
You can create as many constructors as you want on the class with whatever name you want. There is one constructor new, which is inherited from Object, and that can be used to write other constructors. What other answers mention as the constructor, namely the instance method initialize is not a constructor. That is the method called by the constructor method new by default.
class Foo
def self.new1 text, name, array1
obj = new
# do something on obj with text, name, array1
obj
end
def self.new2 text, name, number
obj = new
# do something on obj with text, name, number
obj
end
end
Foo.new1(text, name, array1)
Foo.new2(text, name, number)
There are various ways to achieve this.
Hash arguments
You could pass a hash and extract the values you're interested in:
def initialize(options={})
#text = options.fetch(:text) # raises KeyError if :text is missing
#name = options.fetch(:name) # raises KeyError if :name is missing
#array = options.fetch(:array, []) # returns [] if :array is missing
#number = options.fetch(:number, 0) # returns 0 if :number is missing
end
Keyword arguments
In Ruby 2.0 you can use keyword arguments with default values:
def initialize(text: text, name: name, array: [], number: 0)
#text = text
#name = name
#array = array
#number = number
end
Switching on argument type
This makes the method harder to read, but would work, too:
def initialize(text, name, number_or_array)
#text = text
#name = name
#number = 0
#array = []
case number_or_array
when Integer then #number = number_or_array
when Array then #array = number_or_array
else
raise TypeError, "number_or_array must be a number or an array"
end
end
Built into the language, no, Ruby does not give you that ability.
However, if you want that ability, I would create an initialize method which takes a hash as its parameter. Then you could create an instance of the class using any number of parameters.
E.g:
class Equipment
attr_reader :text, :name, :array1, :number
def initialize(options)
[:text, :name, :array1, :number].each do |sym|
self.send(sym) = options[sum]
end
end
end
The ruby interpreter wouldn't be able to differentiate between the constructors, as the types are not known until runtime :(
However, you can use a very nice workaround:
class Foobar
def initialize(h) # <-- h is a hash
# pass combination of params into the hash, do what you like with them
end
end
and then, using this pattern, you can pass any combination of params into the constructor:
foobar = Foobar.new(:foo => '5', :bar => 10, :baz => 'what?')

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