My problem is probably quite easy, but I couldn't find an answer anywhere.
When create a class, for example:
class Book
#author = "blabla"
#title = "blabla"
#number_of_pages"
I want to create a method to print out my variables. And here I'm getting a problem when I try:
def Print
puts #author, #title, #number_of_pages
end
I am getting nothing.
When I try:
def Print
puts "#author, #title, #number_of_pages"
end
I get straight: "#author, #title, #number_of_pages"
How can I make the Print method print out the variables' values?
You should move your variable initializations to initialize:
class Book
def initialize
#author = "blabla"
#title = "blabla"
#number_of_pages = 42 # You had a typo here...
end
end
The way you have it in your question, the variables are class instance variables (which you can Google if you're curious about, but it's not really relevant here).
Initialized as (normal) instance variables, your first version of Print() works if you're just looking to dump the state -- it prints each parameter on its own line.
To make your second version of Print() work, you need to wrap your variables in #{} to get them interpolated:
def print # It's better not to capitalize your method names
puts "#{#author}, #{#title}, #{#number_of_pages}"
end
In addition to the allready exellent answer of Darshan, here is the way you would do it optimally
class Book
attr_accessor :author, :title, :number_of_pages
#so that you can easily read and change the values afterward
def initialize author, title, number_of_pages = nil
#so that you don't really need to provide the number of pages
#author = author
#title = title
#number_of_pages = number_of_pages
end
def print
puts "#{#author}, #{#title}, #{#number_of_pages}"
end
end
my_book = Book.new("blabla", "blabla", 42)
my_book.title = "this is a better title"
my_book.print
#=>blabla, this is a better title, 42
I think Darshan Computing has already solved your problem very well. But here I would like to give you alternative ways of achieving that.
I assume that you'd like to print out all the instance variables you have in the class. The method instance_variables could return an array of all your instance_variables in symbols. And then you can iterate them do whatever you want. Please be careful: instance_variable_get is pretty convenient but not the best practice.
class Book
attr_reader :author, :title, :number_of_pages
def initialize(author, title, number_of_pages)
#author = author
#title = title
#number_of_pages = number_of_pages
end
def print_iv(&block)
self.instance_variables.each do |iv|
name = iv
value = send(iv.to_s.gsub(/^#/, ''))
# value = instance_variable_get(iv) # Not recommended, because instance_variable_get is really powerful, which doesn't actually need attr_reader
block.call(name, value) if block_given?
end
end
end
rb = Book.new("Dave Thomas", "Programming Ruby - The Pragmatic Programmers' Guide", 864)
# rb.instance_variables #=> [:#author, :#title, :#number_of_pages]
rb.print_iv do |name, value|
puts "#{name} = #{value}"
end
#=> #author = Dave Thomas
#=> #title = Programming Ruby - The Pragmatic Programmers' Guide
#=> #number_of_pages = 864
# You can also try instance_eval to run block in object context (current class set to that object)
# rb.instance_eval do
# puts author
# puts title
# puts number_of_pages
# end
Related
I am very new to Ruby. Started just few days back. I am learning from the website called The Odin Project and there they got this problem where I have to capitalize a word. I know how to capitalize but the method I am using doesn't seem to return the capitalized string.
I Tried the same code in Repl.it. There it seems to return what I expected, but I can't get the same result in my local machine. I cant understand the problem. Can someone explain it to me..
Here's the code :
class Book
attr_accessor :title
def title= str
str.capitalize!
end #title
end #book
#book = Book.new
#book.title="inferno"
For capitalize vs capitalize!, the latter modifies the string in place, and returns nil if no substitution was made. The former, however, returns a new string with the replacements made. However, there's a weird thing using a setter like this in ruby, that I've yet to fully understand: ruby returns the value you passed in to the setter, not the last line executed as normal
class YourBook
attr_accessor :title
def title= str
str.capitalize!
end #title
end #book
new_title = "inferno"
#book = YourBook.new
puts #book.title = new_title # => Inferno # side note, this is because you modified the original string as seen down below
puts #book.title # => # was never set
puts new_title # => Inferno # the string in the original variable was mutated, which is why the setter returned the "correct" version
class MyBook
attr_reader :title
def title=(str)
#title = str.capitalize
end
def set_title(val) # even with the odd return value from `title=`, I still recommend that instead of using `set_` methods
#title = val.capitalize
end
end
new_title = "inferno"
#book = MyBook.new
puts #book.title = new_title # => inferno # side note, not sure why it does this, but ruby returned the string you entered, not the string you set
puts #book.title # => Inferno # was set correctly
puts new_title # => inferno # the original string was not mutated
new_title = "inferno two"
puts #book.set_title(new_title) # => Inferno two # as expected
puts #book.title # => Inferno two # also as expected
puts new_title # => inferno two # again, didn't modify the original
Looking the code and trying to understand what you want, maybe I can clear your mind.
Your code is doing fine, the capitalize method is working properly. Like you said during the comments if you write puts inside the method it returns what you expect.
#mudasobwa it is changing the string to capitalize if i use a puts to
print the string inside the method it prints as expected. but i am not
getting the expected return.
Now checking the call of this method.
#book.title="inferno"
Here we can see "inferno" passing an argument for title method. Then this method is returning the value, but nothing is getting it.
If you wanna check what's being returned just use puts(#book.title = "inferno").
class Book
attr_accessor :title
def title= str
str.capitalize!
end #title
end #book
#book = Book.new
puts(#book.title="inferno")
This will depend on what exactly you want to achieve. I'm trying to guess you want to capitalize the whole word there you have to use upcase method.
EDIT
I have rewritten your code to reflect your TDD needs. I'm initializing the object the same way as your teaching TDD. You need only attr_accessor which generates both setters and getters for title.
class Book
attr_accessor :title
def name_capitalize
# only first letter capitalized
title.capitalize
end #title
end #book
#book = Book.new
#book.title="inferno" # stores the book title in the `#book.title`
# to print the book's name and first letter in capital
puts #book.name_capitalize # uses the stored `title` variable to return the capitalized name
I am new to Ruby and I got a question: is the nil checking necessary in following code? would you please explain a little bit? Thank you in advance!!if you think this is too easy to answer, would you please tell me the document( or link) that I need check to solve my doubts?
this is the original question:
within the say_hi method, the author checks if the instance variable #names is nil. Why is this check done? Is the check really needed in the MegaGreeter class as it is written? Why or Why not?
class MegaGreeter
attr_accessor :names
# Create the object
def initialize(names = "World")
#names = names
end
# Say hi to everybody
def say_hi
if #names.nil?
puts "..."
elsif #names.respond_to?("each")
# #names is a list of some kind, iterate!
#names.each do |name|
puts "Hello #{name}!"
end
else
puts "Hello #{#names}!"
end
end
# Say bye to everybody
def say_bye
if #names.nil?
puts "..."
elsif #names.respond_to?("join")
# Join the list elements with commas
puts "Goodbye #{#names.join(", ")}. Come back soon!"
else
puts "Goodbye #{#names}. Come back soon!"
end
end
end
if __FILE__ == $0
mg = MegaGreeter.new
mg.say_hi
mg.say_bye
# Change name to be "Zeke"
mg.names = "Zeke"
mg.say_hi
mg.say_bye
# Change the name to an array of names
mg.names = ["Albert", "Brenda", "Charles",
"Dave", "Engelbert"]
mg.say_hi
mg.say_bye
# Change to nil
mg.names = nil
mg.say_hi
mg.say_bye
end
The way the initializer is set up, #names should never be nil because of the default value of 'World' being assigned if nothing is provided.
The problem is the public attr_accessor in the MegaGreeter class, which you can read about this method here. It creates a setter method on the #names instance variable, meaning it can be changed to anything, including nil.
I am trying to write a method that returns "Title was written by author", where title and author are variables. I think I'm close, but I'm not sure what to add at this point.
class Book
def set_title_and_author=(title, author)
#title = title
#author = author
end
def set_title_and_author
"#{#title} was written by #{#author}"
end
end
Your problem is that methods ending in = are unusual in that they must have a single argument, and you are passing two (title and author). (Also, Ruby allows you to insert spaces before =; e.g., set_title_and_author = arg.)
I can suggest four ways you might fix that:
make the argument the array [title, author] (i.e., a single value);
change the name of the method to set_title_and_author (and rename the getter);
replace the method set_title_and_author= with two setters, title= and author=, which you can write out explicitly or have Ruby do it for you by executing the method attr_writer :title, :author or attr_accessor :title, :author; or
use the initialize method as the setter.
First way
class Book
def title_and_author=(arr)
#title, #author = arr
end
def title_and_author
"#{#title} was written by #{#author}"
end
end
b = Book.new
#=> #<Book:0x007f91e414eb78>
b.title_and_author=(["Moby Dick", "Herman Melville"])
#=> ["Moby Dick", "Herman Melville"]
b.title_and_author
#=> "Moby Dick was written by Herman Melville"
Notice that I renamed your methods by dropping set_. set is not needed in the first case and is misleading in the second, where get would be more appropriate, but again, is not needed.
Second way
class Book
def set_title_and_author(title, author)
#title = title
#author = author
end
def title_and_author
"#{#title} was written by #{#author}"
end
end
b = Book.new
b.set_title_and_author("Moby Dick", "Herman Melville")
b.title_and_author
#=> "Moby Dick was written by Herman Melville"
Notice that I've changed the name of the first method back to set_title_and_author. It can't have the same name as the getter method and, without the = on the end, the name needs modification to suggest what it does.
I prefer this to the "second way" (making the argument an array).
Third way
class Book
attr_writer :title, :author
def title_and_author
"#{#title} was written by #{#author}"
end
end
b = Book.new
b.title = "Moby Dick"
b.author = "Herman Melville"
b.title_and_author
#=> "Moby Dick was written by Herman Melville"
Fourth way
class Book
def initialize(title, author)
#title = title
#author = author
end
def title_and_author
"#{#title} was written by #{#author}"
end
end
b = Book.new("Moby Dick", "Herman Melville")
b.title_and_author
#=> "Moby Dick was written by Herman Melville"
This is a very common way of setting instance variables.
Following up on #orde's answer, I would also use the wonderful #attr_accessor method to automatically create setters and getters for title and author.
class Book
# create the setter and getter methods
attr_accessor :title, :author
# create an optional initializer
def initialize(title = nil, author = nil)
#title = title
#author = author
end
def print_title_and_author
puts "#{#title} was written by #{#author}"
end
end
book = Book.new("Blood Meridian", "Cormac McCarthy")
book.print_title_and_author
# => Blood Meridian was written by Cormac McCarthy
book.title = "Blue moon"
book.print_title_and_author
# => Blue moon was written by Cormac McCarthy
Creating different setters and getters for different attributes is preferred.
You can also create chain-setters, which is becoming a trend now, so you can set many attributes with one line of code. you do this by returning self after performing the task (setting the attribute):
# Reopen the class to add methods,
# the old code is still being used!
class Book
def set_title title
#title = title
self
end
def set_author author
#author = author
self
end
def to_s
"#{#title} was written by #{#author}"
end
end
#chain set attributes
book.set_title('My New Title').set_author('Me')
#print the output - notice the #to_s is implied
puts book
# => My New Title was written by Me
You don't need the initializer, but please notice that the current #print_title_and_author and #to_s methods produce a funny output if the attributes aren't set.
We can change that with an if statement (actually, I will use unless). Le't open the class again and re-define these methods:
# Reopen the class to add methods,
# the old code is still being used!
class Book
def to_s
return "author or title missing!" unless #author && #title
"#{#title} was written by #{#author}"
end
def print_title_and_author
puts self.to_s #DRY code - do not repeat yourself.
end
end
new_book = Book.new
puts new_book
# => author or title missing!
I think this is better.
Good Luck, and welcome to Ruby!
Instead of using a setter method to assign the title and author, you should use the initialize method to set up an object's initial state:
class Book
def initialize(title, author)
#title = title
#author = author
end
def print_title_and_author
puts "#{#title} was written by #{#author}"
end
end
book = Book.new("Blood Meridian", "Cormac McCarthy")
book.print_title_and_author
#=> Blood Meridian was written by Cormac McCarthy
I'm currently working through a set of TestFirst problems. The spec for the problem I'm working on can be found here: http://testfirst.org/live/learn_ruby/book_titles
I've tested the title method outside of the class so I know that it works, but once I place it in the class I get the following error:
1) Book title should capitalize the first letter
Failure/Error: #book.title.should == "Inferno"
ArgumentError:
wrong number of arguments (0 for 1)
Here's what I have so far:
class Book
attr_accessor :title
def initialize(title=nil)
#title = title
end
def title(title)
first_check = 0
articles = %w{a the an in and of}
words = title.split(" ")
words.each do |word|
if first_check == 0
word.capitalize!
first_check += 1
elsif !articles.include?(word)
word.capitalize!
end
end
#title = words.join(" ")
end
end
If someone could explain how the class should be formatted, it'd be greatly appreciated!
Your problem is right here:
def title(title)
Your Book#title method expects an argument but you're not giving it one in your spec:
#book.title.should == "Inferno"
# ----^^^^^ this method call needs an argument
I think you actually want a Book#title= method:
def title=(title)
# The same method body as you already have
end
Then you'll use the title accessor method that attr_accessor :title supplies and assigning a new title will use your title= method. And since you're supplying your own mutator method, you could use attr_reader instead:
class Book
attr_reader :title
def initialize(title=nil)
#title = title
end
def title=(title)
#...
end
end
Currently struggling through an rspec tutorial and would really appreciate some clarification.
Code is:
class Book
attr_reader :title
def initialize(title=nil)
#title = title = title && title.capitalize!
end
def title=(new_title = nil)
#title = new_title && new_title.each do |word|
word.capitalize!
end
end
Two questions:
Why are there two sets of #title (that is: why is it defined in both initialize and title as being set = to different things)?
Why does the title method have an = after it? The code breaks if I do not use the =.
edit: for the purposes of my rspec tutorial this is the code i finally tried that worked
class Book
attr_accessor :title
def initialize(title = nil)
#title = title
end
def title=(book_title = nil)
#title = book_title.capitalize
end
end
My initial problem was with the title= method. Finally I came upon a thread that explain what method= function was. It is necessary if you want to assign a value to something within a class method (at least that is my understanding at this point. Feel free to correct me).
I would appreciate any tips in this new code as well.
Let's analize that:
attr_reader :title
Here we are basically defining the method:
def title; #title; end
which returns the instance variable #title.
def initialize(title=nil)
#title = title = title && title.capitalize!
end
Here we are defining a 0-1 arguments constructor which can be reduced to:
def initialize(title=nil)
title && #title = title.capitalize
end
The fact is that title within the constructor is the argument variable and not the title or title= method, therefore the title= method defined later is never called here. Notice that && is used for short-circuit evaluation here.
def title=(new_title = nil)
#title = new_title && new_title.each do |word|
word.capitalize!
end
Here we actually have two syntax errors: the first one is that for Strings (which I assume is the type of a title as it appears to call String#capitalize! later) does not have the each method. Whoever wrote this probably meant String#each_char or to String#split it first instead.
The second error is that the block after the each is not closed with an end.
Now assuming this version instead:
def title=(new_title = nil)
#title = new_title && new_title.split(' ').each { |word| word.capitalize! }.join(' ')
end
the title= would just assign title to the #title variable for the same reason (short-circuit evaluation) as before and could be reduced to:
def title=(new_title = nil)
new_title && #title = new_title
end
The initialize method is called when an instance of the class is constructed. The #title = ... there sets the initial value of #title.
The title= method is called when someone subsequently sets the value of title on an instance of the class. It then adjusts the value of #title accordingly. See Ruby Accessors for a detailed explanation.
As an example:
book = Book.new # calls initialize
book.title = 'foo' # calls title=