class methosd (ruby) - ruby

Can you tell me why this isn't working?
The code below outputs student1 & student2 properly, but I cannot get the class method to work on student3.
All I am trying to do is assign a class method .create_with_species , with the species attribute = to "Human".
However, when I run the program, I get an error that "local variable or method sex is undefined". I'm a newbie and I can't figure out what I've done wrong!
It was my understanding that since I had identified "sex" in the Initialize method I should be able to use it within a class method such as create_with_species. I tried explicitly defining sex in the class method, as student.sex = sex, but still got the same error.
class Students
attr_accessor :sex, :age, :species
def self.create_with_species(species)
student = Students.new(sex,age)
student.species = species
return student
end
def initialize(sex, age)
#sex = sex
#age = age
puts "----A new student has been added----"
end
end
student1 = Students.new("Male", "21")
puts student1.sex
puts student1.age
puts
student2 = Students.new("Female", "19")
puts student2.sex
puts student2.age
student3 = Students.create_with_species("human")

sex and age are not defined. Do you mean to use a class variable? Using #age or #sex will result in nils, as no class attributes are set and the instance is not yet created, hence no instance variables set either. You don't need to state Students.new in create_with_species. new will suffice because you are scoped in Students. I think you would be better off passing a hash object to initialize, and just set the species variable there. It will be nil if not assigned explicitly, or you can have a default value.
Try this:
class Students
attr_accessor :sex, :age, :species
def initialize(options)
#sex = options[:sex]
#age = options[:age]
#species = options[:species]
puts "----A new student has been added----"
end
end
student1 = Students.new(:sex => "Male", :age => "21")
puts student1.sex
puts student1.age
puts student1.species
puts
student2 = Students.new(:sex => "Female", :age => "19")
puts student2.sex
puts student2.age
student3 = Students.new(
:sex => "Hermi", :age => "2", :species => "alien"
)

#sex and #age are instance attributes. You can't use them (what would they mean?) inside a class method. You've written just sex and age inside create_with_species, but again: what are these supposed to refer to? (In an instance method they'd be calls to the accessors, but at this point there's no instance to call the accessors on.)
Consider: when you say
student3 = Students.create_with_species("human")
what sex and age should this new student object get? You haven't specified any.

As far as the syntax goes, you have to use #sex and #age instead of sex and age. Otherwise they won't be interpreted as instance attributes.
But you do realize that the logic of the code is broken? I understand you want to use the static factory method pattern but this is not how you should do it since #sexand #age are nil when you call that method and those variables are instance attributes not class attributes.

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

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

Creating an object from a class which is inherited ruby

Im struggling on understanding (after googling) on how to implement this: I have a class:
class Student
# constructor method
def initialize(name,age)
#name, #age = name, age
end
# accessor methods
def getName
#name
end
def getAge
#age
end
# setter methods
def setName=(value)
#name = value
end
def setAge=(value)
#age = value
end
end
And lets say I have another class which inherits from Student
class Grade < Student
#constructor method
def initialize(grade)
super
#grade = grade
end
# accessor methods
def getGrade
#grade
end
# setter methods
def setGrade=(value)
#grade = value
end
end
I understand how to build an abject:
student = Student.new(name, age)
How can I build this Student (that I have just created) a Grade object associated with the student and how would I call the inherited object, for example i wanted to:
puts 'student name and associated grade'
I know I can place the grade variable within the Student class, but for the purpose of learning im doing it this way.
This code would do what you wanted:
class Grade
attr_accessor :value
def initialize value
#value = value
end
end
class Student
attr_accessor :name, :age, :grade
def initialize name, age, grade
#name, #age, #grade = name, age, Grade.new(grade)
end
end
st = Student.new 'John', 18, 5
puts "student #{st.name} and associated grade #{st.grade.value}"
First off, no need to define accessors in Ruby like that, it's far from idiomatic. Let's clean that up first:
class Student
attr_accessor :name, :age
def initialize(name, age)
#name =name
#age = age
end
end
class Grade
attr_accessor :value
def initialize(grade)
#value = grade
end
end
Secondly it doesn't seem like Grade should inherit from Student at all, just adjust the latter to also store a Grade instance variable:
class Student
attr_accessor :name, :age, :grade
def initialize(name, age, grade = nil)
#name =name
#age = age
#grade = grade
end
end
You can then instantiate a student like this:
student = Student.new("Test", 18, Grade.new(1))
Or because of the default value you leave off the grade and assign it later:
student = Student.new("Test", 18)
# later
student.grade = Grade.new(1)

What is the most elegant way to get the value in an array, minimizing a certain class attribute?

Say I have the following class:
class Person
def initialize(name, age)
#name = name
#age = age
end
def get_age
return #age
end
end
And I have an array of Person objects. Is there a concise, Ruby-like way to get the person with the minimum (or maximum) age? What about sorting them by it?
This will do:
people_array.min_by(&:get_age)
people_array.max_by(&:get_age)
people_array.sort_by(&:get_age)
Your question has been answered properly but, since you are interested in doing things in a Ruby-like way,
I am going to show you a better Ruby-like way to define your Person class.
If do you do not have behaviour (methods, etc.) in your class, the simplest way is using a Struct:
Person = Struct.new(:name, :age)
# example of use
person = Person.new("My name", 21)
Otherwise create a custom class like this, using attr_reader and a hash of arguments:
class Person
attr_reader :name, :age
def initialize(args = {})
#name = args[:name]
#age = args[:age]
end
end
# example of use
person = Person.new(:name => "My name", :age => 21)
Assuming you got an array named array having 5 different instances of Person.
To get the older person
for person in array
olderperson = person if maxperson.get_age < person.get_age
end
And for sorting them
array.sort! { |a,b| a.get_age <=> b.get_age }
Something like below:
youngest_person = people.select { |p| p.get_age == people.map(&:get_age).min }

Ruby object initialization: params, hash, vanilla?

I ran into some confusion over when/why or if it is just a matter of preference when initializing a method with a hash type structure.
class Person1
attr_reader :name, :age
def initialize(params)
#name = params[:name]
#age = params[:age]
end
end
me = Person1.new(name: 'John Doe', age: 27)
puts me.name
puts me.age
#----------------------------------------------
class Person2
attr_reader :name, :age
def initialize(name, age)
#name = name
#age = age
end
end
me = Person2.new('John Doe', 27)
puts me.name
puts me.age
#----------------------------------------------
class Person3
attr_reader :person
def initialize(name, age)
#person = { name: name,
age: age }
end
end
me = Person3.new('John Doe', 27)
puts me.person[:name]
puts me.person[:age]
If it is a matter of preference I like just passing the hash but I could see this being an issue if you need different attr reader, writer within the hash itself. Is there a rule of thumb? I see a lot of rails articles using params.
Your third way will most likely never appear in the wild - you are already constructing a person object, why does it have an accessor for person? The attributes should be on the class itself. The difference between 1 and 2 is mostly preference, but the second one can be advantageous when you regulary want to set only specific attributes.
Concering rails' usage of params: params is the hash that contains the request parameters of the particular request you are handling. As you deal with requests a lot in rails, it will appear in every controller and can also appear in some views.

Resources