I'm learning ruby and have looked the internet to see whats wrong with this. I have tried adding spacing and removing it between the variables I'm passing but i keep getting this error:
25: syntax error, unexpected ',', expecting ')'
student1.grades =(60,70,80)
Here is the code:
class Student
attr_accessor :name, :age
def initialize(name,age)
#name = name
#age = age
end
def grades(math,english,science)
#math = math
#english = english
#science = science
average_grade = (math.to_i + english.to_i + science.to_i) / 3
return average_grade
end
def to_s
puts "Name = #{name}"
puts "Age = #{age}"
puts self.grades
end
end
student1 = Student.new("Tom","23")
student1.grades = (60,70,80)
puts student1
grades receives three parameters. You don't do that with assignment. So change
student1.grades = (60,70,80)
to
student1.grades(60,70,80)
You can assign the grades for student object as
student1.grades(60,70,80)
Also minor edits.You can add the method for computing average
def grades_details
average_grade = (#math.to_i + #english.to_i + #science.to_i) / 3
return average_grade
end
So when you override to string u call it instead of self.grades
def to_s
puts "Name = #{name}"
puts "Age = #{age}"
puts grades_details
end
Related
class Human # Problem received from Raul Romero
attr_reader :name
def initialize(name)
end
end
gilles = Human.new("gilles")
anna = Human.new("gilles")
puts anna.equal?(gilles) #should output true #
puts anna + gilles # should output annagilles
How do I make the last line of code output annagilles?
Alright, there are a couple of things you might need to complete your Human class:
class Human # Problem received from Raul Romero
attr_reader :name
def initialize(name)
#name = name
end
end
you need to set the instance variable #name in order to read it.
next your puts anna.equal?(gilles) check wont work because .equal? checks if you are dealing with the same object. likewise "a".equal("a") #=> false, so in your case only anna.equal?(anna) will return true.
then puts anna + gilles shouldnt work because you are trying to add two instances together (not their names). so maybe this would work:
gilles = Human.new("gilles")
anna = Human.new("anna") # note that you have 'gilles' here in your example
puts anna.name + gilles.name #=> 'annagilles'
so in order to get the value you passed to the initialize (which is then set to #name) you need to call that value with .name, ie some_human.name
If you need to get a result using + then below is the solution
class Human
attr_reader :name
def initialize(name)
#name = name
end
def +(other)
name + other.name
end
end
gilles = Human.new("gilles")
anna = Human.new("anna")
puts anna.equal?(gilles)
puts anna + gilles
Output:
false
annagilles
but I prefer the below solution
class Human
attr_reader :name
def initialize(name)
#name = name
end
def +(other)
Human.new(name + other.name)
end
def to_s
name
end
end
gilles = Human.new("gilles")
anna = Human.new("anna")
puts anna.equal?(gilles)
result = anna + gilles
puts result.to_s
Output:
false
annagilles
I'm trying to access a class variable from a method outside of the class.
This is my class:
class Book
##bookCount = 0
##allBooks = []
def self.allBooks
##allBooks
end
def self.bookCount
##bookCount
end
attr_accessor :name,:author,:date,:genre,:rating
def initialize(name, author, date, genre, rating)
#name = name
#author = author
#date = date
#genre = genre
#rating = rating
##bookCount += 1
##allBooks << self
end
end
This is the method trying to access the class variable ##bookCount
def seeBookShelf
if ##bookCount == 0
puts "Your bookshelf is empty."
else
puts "You have " + #bookCount + " books in your bookshelf:"
puts allBooks
end
end
When I try to execute the method I get this:
undefined local variable or method `bookCount' for main:Object (NameError)
How can I access bookCount from the outside?
Use class_variable_get to access a class variable outside of a class:
class Foo
##a = 1
end
Foo.class_variable_get(:##a)
=> 1
For most cases, class instance variables are preferred to class variables. The latter are prone to all manner of strange behaviour when used with inheritance.
Consider:
class Book
#book_count = 0
#all_books = []
class << self
attr_reader :book_count
attr_reader :all_books
end
# further code omitted.
end
With this code Book.book_count and Book.all_books get the expected data.
You can use class_eval to evaluate a block of code within the scope of a specific class:
class Book
##bookCount = 1
end
Book.class_eval '##bookCount'
# => 1
And just for fun... you can actually do all kinds of trickery with class_eval such as define a new method in the class without monkey patching:
Book.class_eval { ##bookCount = 5 }
Book.class_eval '##bookCount'
# => 5
Book.class_eval do
def self.hey_look_a_new_method
return "wow"
end
end
Book.hey_look_a_new_method
# => "wow"
You need a getter to access the class variable, try this code.
See http://www.railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/ for an explanation.
You are also better to use string interpolation otherwise you get a Type error, also it is more Rubyesque.
class Book
##bookCount = 0
def self.bookCount
##bookCount
end
end
def seeBookShelf
if Book.bookCount == 0
puts "Your bookshelf is empty."
else
puts "You have #{Book.bookCount} books in your bookshelf:"
end
end
seeBookShelf # Your bookshelf is empty.
You have to specify the Class of the variable :
def seeBookShelf
if Book.bookCount == 0
puts "Your bookshelf is empty."
else
puts "You have " + Book.bookCount + " books in your bookshelf:"
puts Book.allBooks
end
end
Hi I am a student learning Ruby. I am using the quick start guide at ruby-lang.org, which has some examples of Ruby basics.
I studied the MegaGreeter class, and I am trying to figure out how to puts two arguments (name and age) in the same each block in order to simplify my code.
I think there would be another way. (Using regular loops instead of each.)
Calculate the array's size.
Use a loop like in C.
But I want to use the each loop. Below is my code:
class MegaGreeter
attr_accessor :name
attr_accessor :age
#Creat the object
def initialize(name=nil, age=0)
#name = name
#age = age
#tmp = Array.new()
#i = 0
end
#Say hi to everybody
def say_hi
if #name.nil?
puts "please give me the input !!"
elsif #name.respond_to?("each")
#list responding
#name.each do |name|
#tmp[#i] = "hi ~! #{name}"
#i += 1
end
#i=0
#age.each do |age|
#tmp[#i] += " and you are #{age} years old"
puts #tmp[#i]
#i += 1
end
else
puts "give me array ~"
end
end
end
a = MegaGreeter.new()
a.name = ["juno","yoonhe"]
a.age = [1,2]
a.say_hi
You can use the Array method zip to first combine your two arrays. It groups the elements by their position in the array, so the first element of the #name array will be grouped with the first element of the #age array and so on.
#name = ['Foo', 'Bar']
#age = [23, 41]
name_and_age = #name.zip(#age)
# [['Foo', 23], ['Bar' 41]]
Now the names and ages are grouped together, and you can iterate over them using each.
name_and_age.each do |name, age|
puts name, age
end
# Foo 23
# Bar 41
Putting it back into your original code:
class MegaGreeter
attr_accessor :name, :age
#Creat the object
def initialize(name = nil, age = 0)
#name = name
#age = age
end
#Say hi to everybody
def say_hi
if #name.nil?
puts "please give me the input !!"
elsif #name.respond_to?("each")
#list responding
#name.zip(#age).each do |name, age|
puts "hi ~! #{name} and you are #{age} years old"
end
else
puts "give me array ~"
end
end
end
I want to load a file, split its content into arrays, and have the class apply to the content.
class Student
def initialize( name, grade )
#name = name
#grade = grade
#grade = #grade.to_i
#newgrade = #grade*1.45
end
def show()
return "#{#name} ,#{#grade} , #{#newgrade}"
end
end
# Opening the file into an array
arr = File.open("exam_results.txt", "r+")
allStudents = Array.new
for a in arr
b = a.split(",")
name = b[0]
score = b[1]
allStudents << Student.new(#name, #grade)
end
for i in Student
puts show()
end
I'm getting
undefined method 'each' for Student:Class (NoMethodError)
on line 28, which is the puts show() line. Any clues on how I can get further on this?
I think you have a typo there (among other things). You're doing this:
for i in Student
puts show()
end
Clearly, the Student class is not a collection which you can iterate. I think, what you meant to write is this:
allStudents.each do |student|
puts student.show
end
That is because you are trying to iterate over "Student" class and not Array/Collection object at for i in Student
Basically you are doing it wrong. It rather should be something like
allStudents.each do |student|
puts student.show
end
In Java you can overload constructors:
public Person(String name) {
this.name = name;
}
public Person(String firstName, String lastName) {
this(firstName + " " + lastName);
}
Is there a way in Ruby to achieve this same result: two constructors that take different arguments?
The answer is both Yes and No.
You can achieve the same result as you can in other languages using a variety of mechanisms including:
Default values for arguments
Variable Argument lists (The splat operator)
Defining your argument as a hash
The actual syntax of the language does not allow you to define a method twice, even if the arguments are different.
Considering the three options above these could be implemented with your example as follows
# As written by #Justice
class Person
def initialize(name, lastName = nil)
name = name + " " + lastName unless lastName.nil?
#name = name
end
end
class Person
def initialize(args)
name = args["name"]
name = name + " " + args["lastName"] unless args["lastName"].nil?
#name = name
end
end
class Person
def initialize(*args)
#Process args (An array)
end
end
You will encounter the second mechanism frequently within Ruby code, particularly within Rails as it offers the best of both worlds and allows for some syntactic sugar to produce pretty code, particularly not having to enclose the passed hash within braces.
This wikibooks link provides some more reading
I tend to do
class Person
def self.new_using_both_names(first_name, last_name)
self.new([first_name, last_name].join(" "))
end
def self.new_using_single_name(single_name)
self.new(single_name)
end
def initialize(name)
#name = name
end
end
But I don't know if this is the best approach.
class Person
def initialize(name, lastName = nil)
name = name + " " + lastName unless lastName.nil?
#name = name
end
end
class StatementItem
attr_reader :category, :id, :time, :amount
def initialize(item)
case item
when Order
initialize_with_order(item)
when Transaction
initialize_with_transaction(item)
end
end
def valid?
!(#category && #id && #time && #amount).nil?
end
private
def initialize_with_order(order)
return nil if order.status != 'completed'
#category = 'order'
#id = order.id
#time = order.updated_at
#amount = order.price
end
def initialize_with_transaction(transaction)
#category = transaction.category
#id = transaction.id
#time = transaction.updated_at
#amount = transaction.amount
end
end
You can use konstructor gem to declare multiple constructors in Ruby and imitate overloading:
class Person
def initialize(name)
#name = name
end
konstructor
def from_two_names(first_name, last_name)
#name = first_name + ' ' + last_name
end
end
Person.new('John Doe')
Person.from_two_names('John', 'Doe')
You could use the double splat operator ** in conjunction with logical or (double pipes) || inside the initialize method to achieve the same effect.
class Person
def initialize(**options)
#name = options[:name] || options[:first_name] << ' ' << options[:last_name]
end
end
james = Person.new(name: 'James')
#=> #<Person #name="James">
jill_masterson = Person.new(first_name: 'Jill', last_name: 'Masterson')
#=> #<Person #name="Jill Masterson">
However, if a new Person is created without a first_name, then the append << operation will fail with NoMethodError: undefined method '<<' for nil:NilClass. Here is a refactored initialize method to handle this case (using strip to remove whitespace if either option is excluded).
class Person
def initialize(**options)
#name = options[:name] || [ options[:first_name] , options[:last_name] ].join(' ').strip
end
end
goldfinger = Person.new(last_name: 'Goldfinger')
#=> #<Person #name="Goldfinger">
oddjob = Person.new(first_name: 'Oddjob')
#=> #<Person #name="Oddjob">
In fact, this approach handles calling Person.new without arguments or with an unexpected key to return the new instance with #name set to an empty string:
nameless = Person.new
#=> <#Person #name="">
middle_malcom = Person.new(middle_name: 'Malcom')
#=> <#Person #name="">
checkout functional-ruby gem which is inspired by Elixir pattern matching features.
class Person
include Functional::PatternMatching
defn(:initialize, String) { |name|
#name = name
}
defn(:initialize, String, String) {|first_name, last_name|
#name = first_name + ' ' + last_name
}
end