railstutorial.org 4.4.5 exercise 1 - ruby

In the exercise, we have to add a method called "full_name" which takes the first and last name attributes and separates them by a space.
class User
attr_accessor :first_name, :last_name, :email
def initialize(attributes = {})
#first_name = attributes[:first_name]
#last_name = attributes[:last_name]
#email = attributes[:email]
end
def full_name
"#{#first_name} #{#last_name}"
end
def formatted_email
full_name "<#{#email}>"
end
end
I created 2 separate first and last name attributes, I defined the full_name method, I am stuck on how to implement that method into the "formatted_email" method. I tried
"full_name <#{#email}>"
but was unsuccessful. Where should I put the full_name?

def formatted_email
"#{full_name} <#{#email}>"
end

Related

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)

Undefined local variable error with simple program that adds items to array

class Books
attr_accessor :name, :book_id
def initialize(name, book_id)
#name = name,
#book_id = book_id
end
end
class BookCollection
def intialize
#book_names = []
end
def add_to_books(book_name)
book_name.push(book_names)
end
end
book1 = Books.new("catch22", "12345")
book_collection1 = BookCollection.new
book_collection1.add_to_books(book1.name)
puts book_collection1
end
That is my code and the error I'm getting is "undefined local variable or method `book_names'". I tried adding " attr_accessor :book_names" and when I do that the printed output doesn't make sense.
There are a few mistakes in your code:
line 4 should not end with a comma.
initialize in class BookCollection is misspelled, resulting in #book_names not being initialized. #book_names therefore equals nil when you attempt to add an element to it with push. nil does not have a method push; hence the exception, and the message printed with the exception.
book_name.push(book_names) should be #book_name.push(book_name). (#book_name must be an instance_variable, as opposed to a local variable, to be visible outside a method, within the class definition.
puts book_collection1 prints the class instance; you want to print #book_names.
Here I've fixed your code. I've used << instead of push. Either is OK, but the former seems to be favored my most.
class Books
attr_accessor :name, :book_id
def initialize(name, book_id)
puts "name = #{name}, book_id = #{book_id}"
#name = name
#book_id = book_id
end
end
class BookCollection
attr :book_names
def initialize
#book_names = []
end
def add_to_books(book_name)
#book_names << book_name
end
end
book_collection1 = BookCollection.new
book1 = Books.new("Catch22", "12345")
book2 = Books.new("Hawaii", "67890")
book_collection1.add_to_books(book1.name)
book_collection1.add_to_books(book2.name)
book_collection1.book_names # => ["Catch22", "Hawaii"]
Probably just a typo at
book_name.push(book_names)
Should have been
book_names.push(book_name)
With attr_accessor :book_names

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.

ruby hash within hash and a singleton method- cant access instance variable

#!/usr/bin/env ruby
# this is the data I have
#data = {
:student => {
:id => '123477',
:first_name => 'Lazlo',
:last_name =>'Fortunatus',
:email=>'Lazlo#fortunatus.org'
},
:contact_info => {
:telephone=>'1 415 222-2222',
:address => '123 Main St',
:city =>'Beverly Hills',
:state=>'California',
:zip_code=>90210,
:social_security_number =>'111-11-1111'
}
}
class Student
# not fully implemented - this is what I need help on.
def get_id_original
# I need this to return the value #data[:student][:id]
end
def get_city_original
# I need this to return the value #data[:contact_info][:city]
end
end
s = Student.new
# this is the original method
# how can I access the #data variable here I tried #data[:student][:id] doesnt work
# I realize that data is outside of the scope of this method. However, is there any way!
s.get_id_original
# My goal is to have a singleton method that acts exactly like get_id_original,
# but get_id_original doesn't work.
def s.id
get_id_original
end
It can be done!
It didn't at first work because #data is an instance attribute of the top level object, so even though Student is derived from Object the attribute isn't in the new instance.
But you can pass self into s.id, and so then the only thing you need to add is an accessor for the data attribute.
However, that's slightly tricky because attr_reader et al are private class methods so you can't use them directly, and you can't (because it's private) just say self.class.attr_reader, you have to open up Object and do it...with these changes your program works...
#data = { :student => { :id => '123477'} }
class Student
end
s = Student.new
def s.id o
o.data[:student][:id]
#how can I access the #data variable here I tried #data[:student][:id] doesnt work
#I realize that data is outside of the scope of this method. However, is there any way!
end
class Object
attr_reader :data
end
puts s.id self
First off, your id method actually has to go into the class.
You could try something like this:
#data = { :student => { :id => '123477'} }
class Student
attr_accessor :id
def initialize(student)
self.id = student[:id]
end
end
s = Student.new(#data[:student])
puts s.id
#!/usr/bin/ruby
#data = { :student => { :id => '123477', :first_name => 'Lazlo', :last_name =>'Fortunatus', :email=>'Lazlo#fortunatus.org' }, :contact_info => { :telephone=>'1 415 222-2222', :address => '123 Main St', :city =>'Beverly Hills', :state=>'California', :zip_code=>90210, :social_security_number =>'111-11-1111' } }
class Student
def initialize( data )
#data = data
end
def get_id_override
#data[:student][:id]
end
def get_first_name_override
#data[:student][:first_name]
end
def get_last_name_override
#data[:student][:last_name]
end
def get_email_override
#data[:student][:email]
end
def get_telephone_override
#data[:contact_info][:telephone]
end
def get_city_override
#data[:contact_info][:city]
end
def get_state_override
#data[:contact_info][:state]
end
def get_zip_code_override
#data[:contact_info][:zip_code]
end
def get_social_security_number_override
#data[:contact_info][:social_security_number]
end
end
s = Student.new(#data)
def s.id
get_id_override
end
def s.first_name
get_first_name_override
end
def s.last_name
get_last_name_override
end
def s.email
get_email_override
end
def s.contact_info
get_telephone_override
end
def s.city
get_city_override
end
def s.state
get_state_override
end
def s.zipcode
get_zip_code_override
end
def s.ssn
get_social_security_number_override
end
puts s.id
puts s.first_name
puts s.last_name
puts s.email
puts s.contact_info
puts s.city
puts s.state
puts s.zipcode
puts s.ssn
Here is the answer after some work. Anyone has a better answer than mine let me know.
You really should be passing in the data object so it s has its own reference to it.
#data = { :student => { :id => '123477'} }
class Student
attr_accessor :data
def initialize(data)
#data = data
end
end
s = Student.new(#data)
# or you can use s.data = #data
def s.id
#data[:student][:id]
end
puts s.id
A word of caution. Any modifications to #data in the outermost scope will be reflected in s, because both #data variables point to the same object in memory.
But what if you don't want to modify the Student class? You can just add the accessor to s:
#data = { :student => { :id => '123477'} }
class Student
end
s = Student.new
class << s
attr_accessor :data
end
def s.id
#data[:student][:id]
end
s.data = #data
puts s.id
This code does the equivalent of your own answer, with some improvements. (Only by reading that did I realize what you were trying to accomplish.) To avoid being overly complex, I tried to avoid dynamically generating method names.
#!/usr/bin/env ruby
require 'forwardable'
#data = {
:student => {
:id => '123477',
:first_name => 'Lazlo',
:last_name =>'Fortunatus',
:email=>'Lazlo#fortunatus.org'
},
:contact_info => {
:telephone=>'1 415 222-2222',
:address => '123 Main St',
:city =>'Beverly Hills',
:state=>'California',
:zip_code=>90210,
:social_security_number =>'111-11-1111'
}
}
class ContactInfo
def initialize( data )
#data = data
end
def get_telephone_override
#data[:telephone]
end
def get_city_override
#data[:city]
end
def get_state_override
#data[:state]
end
def get_zip_code_override
#data[:zip_code]
end
def get_social_security_number_override
#data[:social_security_number]
end
end
class Student
extend Forwardable # enables delegation (see ruby-doc.org's standard library)
# delegates multiple methods to #contact_info, so they can be called on Student.
# Remember to have the leading colon.
def_delegators :#contact_info,
:get_telephone_override,
:get_city_override,
:get_state_override,
:get_zip_code_override,
:get_social_security_number_override
def initialize( data )
#data = data[:student]
# this is an example of composing objects to achieve separation of concerns.
# we use delegators so ContactInfo methods are available on the Student instance.
#contact_info = ContactInfo.new(data[:contact_info])
end
def get_id_override
#data[:id]
end
def get_first_name_override
#data[:first_name]
end
def get_last_name_override
#data[:last_name]
end
def get_email_override
#data[:email]
end
end
s = Student.new(#data)
class << s
alias_method :id, :get_id_override
alias_method :first_name, :get_first_name_override
alias_method :last_name, :get_last_name_override
alias_method :email, :get_email_override
alias_method :contact_info, :get_telephone_override
alias_method :city, :get_city_override
alias_method :state, :get_state_override
alias_method :zipcode, :get_zip_code_override
alias_method :ssn, :get_social_security_number_override
end
puts s.id
puts s.first_name
puts s.last_name
puts s.email
puts s.contact_info
puts s.city
puts s.state
puts s.zipcode
puts s.ssn
I think your question would've been clearer if you posted the code as you wanted it to work. I'm going to suggest an edit.
Should you be defining an instance variable (prefixed by "#") outside of a class definition?
Also, you can't define a method with a period in the name

Resources