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
Related
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
I'm trying to define a class method using two arguments - title and author. When I try to pass my arguments I'm getting an argument error
syntax error, unexpected ',', expecting ')'
book.set_title_and_author= ("Ender's Game", "Orson Scott Card")
class Book
def set_title_and_author= (title, author)
#title = title
#author = author
end
def description
"#{#title}was written by #{#author}"
end
end
book = Book.new
book.set_title_and_author= ("Ender's Game", "Orson Scott Card)
p book.description
Am I not allowed to pass more than one argument in my setter method or is there something else that I'm missing?
You indeed can't pass more than one argument to a method that ends in =. A setter method doesn't need to end in =, though, naturally: you can just do set_title_and_author(title, author).
Another alternative would be to have the method take an array:
def set_title_and_author= (title_and_author)
#title, #author = title_and_author
end
#...
book.set_title_and_author= ["Ender's Game", "Orson Scott Card"]
If you do the latter, stylistically I'd recommend removing the set and just calling the method title_and_author=. set is redundant with =.
class Book
def set_title_and_author(title, author)
#title = title
#author = author
end
def description
"#{#title}was written by #{#author}"
end
end
book = Book.new
book.set_title_and_author("Ender's Game", "Orson Scott Card")
p book.description
but more clear approach will be
class Book
attr_accessor :title, :author
def description
"#{#title}was written by #{#author}"
end
end
book = Book.new
book.title = "Ender's Game"
book.author = "Orson Scott Card"
p book.description
And finally using constructor for setting attributes (and avoiding unnecessary mutability) is much better
book = Book.new("Ender's Game", "Orson Scott Card")
The = sign is unnecessary. Do following:
class Book
def set_title_and_author(title, author)
#title = title
#author = author
end
def description
"#{#title} was written by #{#author}"
end
end
book = Book.new
book.set_title_and_author("Ender's Game","Orson Scott Card")
p book.description
This worked for me.
Suppose I have:
Book = Struct.new(:title, :content)
book = Book.new('harry potter', 'a bunch of content here')
p book.title #=> harry potter
What I want the last line to produce is "Harry Potter". I know I can do something like:
Book = Struct.new(:title, :content) do
def capitalized_title
self.title.gsub(/\S+/, &:capitalize)
end
end
and then call capitalized_title, but what I want is to not have to create a separate method, instead, have some way of when you assign "title" to a new Book object, the title is immediately capitalized. Some kind of hook method, I would guess.
class Book < Struct.new(:title, :content)
def title
super.gsub(/\S+/, &:capitalize)
end
end
book = Book.new('harry potter', 'a bunch of content here')
book.title # => "Harry Potter"
Book = Struct.new(:title, :content) do
alias orig_title title
def title
orig_title.gsub(/\S+/, &:capitalize)
end
end
To prevent title is called every time, override title=:
Book = Struct.new(:title, :content) do
alias orig_title= title=
def initialize(*args)
super
self.title = title
end
def title= value
self.orig_title = value.gsub(/\S+/, &:capitalize)
end
end
You could create an initialize method for the class Book (borrowing #falsetru's nice way of capitalizing the the words in title). First note that Book does not have its own initialize method;;
Book = Struct.new(:title, :content)
#=> Book
Book.private_instance_methods(false)
#=> []
Rather, it inherits from Struct:
Book.private_instance_methods.include?(:initialize)
#=> true
Book.instance_method(:initialize).owner
#=> Struct
Struct.private_instance_methods(false)
#=> [:initialize, :initialize_copy]
We add an initialize method in the usual way:
Book = Struct.new(:title, :content) do
def initialize(title,content)
self.title = title.gsub(/\S+/, &:capitalize)
self.content = content
end
end
confirm:
Book.private_instance_methods(false)
#=> [:initialize]
and test:
book = Book.new('harry potter', 'a bunch of content here')
book.title
#=> "Harry Potter"
book.content
#=> "a bunch of content here"
If there were more than just two instance variables, we might do this:
Book = Struct.new(:title, :content) do
def initialize(title,content)
super
self.title = title.gsub(/\S+/, &:capitalize)
end
end
I'm studying Ruby and try to implement method_missing, but it's not working. For example, I want to print method name after find_ but ruby raises "undefined method 'find_hello'" when i call it on Book instance.
TEST_05.RB
module Searchable
def self.method_missing(m, *args)
method = m.to_s
if method.start_with?("find_")
attr = method[5..-1]
puts attr
else
super
end
end
end
class Book
include Searchable
BOOKS = []
attr_accessor :author, :title, :year
def initialize(name = "Undefined", author = "Undefined", year = 1970)
#name = name
#author = author
#year = year
end
end
book = Book.new
book.find_hello
You are calling method on object which looks for instance_level method. So you need to define instance_level method_missing method:
module Searchable
def method_missing(m, *args)
method = m.to_s
if method.start_with?("find_")
attr = method[5..-1]
puts attr
else
super
end
end
end
class Book
include Searchable
BOOKS = []
attr_accessor :author, :title, :year
def initialize(name = "Undefined", author = "Undefined", year = 1970)
#name = name
#author = author
#year = year
end
end
book = Book.new
book.find_hello #=> hello
When you use self with method definition. It is defined as class level method. In your case Book.find_hello will output hello.
You've defined method_missing as a class method on Searchable, but you're trying to invoke it as an instance method. To invoke the method as it is, run it against the class:
Book.find_hello
If your intention is to find something from the entire collection of books, this is the canonical way it's done. ActiveRecord uses this approach.
You could similarly have a find_* instance method that would search the current book instance for something. If that's your intention, then change def self.method_missing to def method_missing.
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