Ruby: Class methods and initialization - ruby

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=

Related

Wrong number of arguments (0 for 1) Ruby

def self.grab
article = self.article_names
links = self.article_links
body = self.article_body
articles = {}
articles[:title] = article
articles[:url] = links
articles[:body] = body
art = Ello::Hello.new
art(articles)
end
When I run this with
class Ello::Hello
attr_accessor :url, :article, :body,
##all = []
def initialize(hash)
#article = hash["title"]
#body = hash["body"]
#url = hash["url"]
##all << self
end
def self.all
##all
end
end
I get wrong number of arguments error? I know that usually when it says wrong number it means that it's not exactly reading the argument that I put in. But I feel like I did put in an argument but I'm unsure of why it's not being read.
In such cases, you should always paste the complete error message, and indicate which line in your code is affected.
Anyway, I can see that your wrote art = Ello::Hello.new (0 arguments), but the initialize method for this class expects 1 argument.

Method is not returning a value in local machine for ruby

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

Book Capitalization Class Error

I'm teaching myself how to code and can't seem to understand how the inputted title is being called by this class. Where does the argument go to first? The attr_accessor or the def title capital_it(#title) end?
class Book
attr_accessor :title
def title
capital_it(#title)
end
def capital_it(title)
word_arr = #title.capitalize.split(" ")
word_arr.map do |word|
word.capitalize! unless little_words.include?(word)
end
word_arr.join(" ")
end
def little_words
["the", "a", "an", "and", "in", "of"]
end
end
Where does the argument go to first? The attr_accessor or the def title ... ?
attr_accessor :title defines two methods for you: title (the "reader") and title= (the "writer"). When you do def title after attr_accessor, you replace the title method entirely with your new method.
In other words, the method generated by attr_accessor is never called, because you've overwritten it with your own method.
Since you're writing your own "reader" method, you should just use attr_writer:
class Book
attr_writer :title
def title
capital_it(#title)
end
# ...
end
First of all, you have an issue in your def capital_it(title) method.
.map method will return a new array which you didn't assign to anything. If you want to reassign it to local variable word_arr use .map! instead.
You should probably rewrite condition word.capitalize! unless little_words.include?(word) to this little_words.include?(word) ? word : word.capitalize
P.S. In your case it works because you use capitalize! on each string of element but the style of writing like that isn't good.
So I'd recommend you to write your class like this one:
class Book
SKIP = %w(the a an and in of)
attr_writer :title
def initialize(title)
#title = title
end
def title
capital(#title)
end
private
def capital(title)
array = #title.split(/\s+/)
array.map! do |word|
SKIP.include?(word) ? word: word.capitalize
end
array.join(" ")
end
end

Issue properly formatting ruby to pass rspec

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

Printing variables

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

Resources