new Class item displaying as object instead of string - ruby

I'm still extremely new to Ruby. I took a class on Codecademy and I'm currently doing the "Final" where I have to make a todo list.
One of the parts of the todo list is to be able to add tasks (obviously). Another part is to be able to show all current tasks. Now, technically, both of these are working. But, when I create a new task using the class I made (Task) and then show the tasks, it displays the object ID instead of the string. If I simply use my add method without using my Task class, it will display the string like I want it too.
My goal is to get my script to display the string while using the Task class. If someone could please explain to me why it's not working and how I can fix it, I'd appreciate that.
Here's the code:
## Classes ##
#List Class - Used for anything involving the list
class List
attr_reader :all_tasks
def initialize
#all_tasks = []
end
def add(task)
all_tasks << task
end
def show
all_tasks
end
end
#Task Class - Used for anything involving Tasks
class Task
attr_reader :description
def initialize(description)
#description = description
end
end
## Modules ##
module Promptable
def prompt(message = "What would you like to do?", symbol = " >: ")
print message
print symbol
gets.chomp
end
def show
menu
end
end
module Menu
def menu
puts "
'add' - Add a task to the list \n
'delete' - Delete a task from the list \n
'update' - Update a task in the list \n
'show' - Shows current tasks in list"
end
end
#Methods - various methods
#Program Runner
if __FILE__ == $PROGRAM_NAME
include Menu
include Promptable
my_list = List.new
puts "Please choose from the following list: "
until ['q'].include?(user_input = prompt(show).downcase)
case user_input
when 'add'
puts "What task would you like to do?"
my_list.add(Task.new(gets.chomp))
when 'q'
puts "Qutting...."
when 'show'
puts my_list.show
else "That is not a valid command"
end
end
end

puts my_list.show
Will display the tasks one by one. Since the Task class doesn't have a to_s method, the default one will be used. Just add one:
class Task
# ...
alias to_s description
end
BTW strings are objects too. Pretty much everything in Ruby is an object.

Related

How to fix "uninitialized constant List::Task (NameError)" in Ruby

Output: <'class:List'> uninitialized constant List::Task (NameError)
You have created a new list
What I think is happening is that when I call Task.new, the List class is looking for possibly a task method or variable within its own class.
So far I tried using include Task and require Task in my List class with no luck. I also tried to declare the List class in my Task class. I also tried making the list class a parent of the Task class. After some digging online I thought it was the Ruby version and even changed the PATH to an older ruby version.
class List
attr_reader :all_tasks
if __FILE__ == $PROGRAM_NAME
my_list = List.new
puts 'You have created a new list'
my_list.add(Task.new('Make breakfest'))
puts 'You added a task'
end
def initialize
#all_tasks = []
end
def add(task)
all_tasks << task
end
end
class Task
attr_reader :description
def initialize(description)
#description = description
end
end
It might have helped if you provided more information about where (like: in which files) the classes are defined and how you call/execute your ruby code.
Usually, you would put code in a lib directory, with one class per file and strong semantics (class List goes into lib/list.rb), but its a tiny bit more complicated than that.
If you want to hack around and learn play a bit with ruby it is also perfectly understandable, that you do not want to cope with requireing (loading) other files, dealing with dependencies and all that (although this will need to happen one day as with most other programming languages).
For now, this would fix your issue and should get you going:
# file name: task_list_program.rb (or anything you want)
class Task
attr_reader :description
def initialize(description)
#description = description
end
end
class List
attr_reader :all_tasks
def initialize
#all_tasks = []
end
def add(task)
all_tasks << task
end
end
# note that for playing around in a single file, you actually
# do not even need the 'if ...' part here
if __FILE__ == $PROGRAM_NAME
my_list = List.new
puts 'You have created a new list'
my_list.add(Task.new('Make breakfest'))
puts 'You added a task'
end
The main "trick" here is to move the if __FILE__ == ... thing out of your class definition, because otherwise (you are accidentally dealing with a special case here), stuff evaluated in that class at runtime will not be able to pick up and reference other "definitions" in the way you seem to expect.
I hope this helps you to get going. The differences between the code example could teach you a lot, e.g. about "namespaces" (which afaik are not a real concept in Ruby), but I believe this short answer is good enough to get you started and have some fun with ruby (and welcome at SO by the way)!
("execute" the file like this: ruby task_list_program.rb).
class List
class Task
attr_reader :description
def initialize(description)
#description = description
end
end
attr_reader :all_tasks
if __FILE__ == $PROGRAM_NAME
my_list = List.new
puts 'You have created a new list'
List::Task.new('Make breakfest')
# my_list.add(Task.new('Make breakfest'))
puts 'You added a task'
end
def initialize
#all_tasks = []
end
def add(task)
all_tasks << task
end
end
The first issue what we had in this file, that we try to call Task class before implemented!
You don't correct call Task class my_list.add(Task.new('Make breakfest'))
correct is List::Task.new('Make breakfast')

Uninitialized constant in Ruby

I'm trying to work through this tutorial for Ruby, on CodeAcademy. The following code is giving me the error
"uninitialized constant List::Task (NameError)
from to_do.rb:2:in main"
I understand that this may be related to the List class not being able to access the Task class, but I can't see where this would be breaking down? The code is below.
#list class
class List
attr_reader :all_tasks
if __FILE__ == $PROGRAM_NAME
my_list = List.new
puts 'You have created a new list'
my_list.add(Task.new('Make Breakfast'))
puts 'You have added a task to the Todo List'
end
def initialize
#all_tasks = []
end
def add(task)
#all_tasks << task
end
end
#class task
class Task
attr_reader :description
def task_item(desription)
#new_item = description
end
end
At the moment you run this line
my_list.add(Task.new('Make Breakfast'))
Task hasn't been defined. That happens a few lines later.
Just swap the definitions of the Task and the List class.
When you run that file from the command line as a script, order of declaration is important. If you move the declaration of Task above List you'll find that it will fix the error in question.

How to display the attribute of an object as a string in Ruby

I have two classes List and Task:
class List
attr_reader :all_tasks
def initialize
#all_tasks = []
end
def add (task)
#all_tasks << task
end
def show
all_tasks
end
end
class Task
attr_reader :description
def initialize (description)
#description = description
end
end
And the following code:
breakfast = Task.new("Make Breakfast")
my_list = List.new
my_list.add(breakfast)
my_list.add(Task.new("Wash the dishes!"))
my_list.add("Send Birthday Gift to Mom")
puts "Your task list:"
puts my_list.show
Output:
Your task list:
#<Task:0x00007fd9e4849ed0>
#<Task:0x00007fd9e4849e30>
Send Birthday Gift to Mom
I want to be able to show the tasks of the to-do list as a string and in the same time have the Task instances as objects inside the array. How do I do that?
Considering the code in your question, it would suffice to simply redefine method to_s of Task.
class Task
attr_reader :description
def initialize (description)
#description = description
end
def to_s
"Task: #{description}"
end
end
Output
Your task list:
Task: Make Breakfast
Task: Wash the dishes!
Send Birthday Gift to Mom
You call add with Task instances:
my_list.add(Task.new("Wash the dishes!"))
and with String instances:
my_list.add("Send Birthday Gift to Mom")
Having a mixture of both, Task and String instance in one array makes it harder to work with. Unless you really want or need this, I would change add, so it converts string arguments to Task instances:
class List
# ...
def add(task)
task = Task.new(task) unless task.is_a? Task
#all_tasks << task
end
end
is_a? checks whether task is (already) a Task. If not, it is passed as an argument to Task.new which returns such instance. This ensures, that #all_tasks only contains Task instances.
Your current implementation of List#show simply returns all_tasks, i.e. an array. Although puts is able to print arrays ...
If called with an array argument, writes each element on a new line.
... I would change show to return a formatted string:
class List
# ...
def show
all_tasks.map { |task| "[ ] #{task.description}" }.join("\n")
end
end
map returns a new array with a string for each task instance. Each string contains the corresponding task's description, prefixed by [ ] which should resemble a little check box. join then concatenates these string elements using "\n" (newline) as a separator.
Output:
Your task list:
[ ] Make Breakfast
[ ] Wash the dishes!
[ ] Send Birthday Gift to Mom

RUBY - How to call a method from another class

I'm new to ruby and programming in general and I'm having some issues getting methods to work from another class. The method I'm trying to get to work is new_employee and its option 2 if you run the business.rb
business.rb file contains class Business
class Business
attr_accessor :name
def run
self.welcome
end
def welcome
while true
puts "Welcome to Team Green Turf! What do you want to do today?"
puts "1. Add new customer"
puts "2. Add new employee"
puts "3. View current revenue"
choice = gets.chomp.to_i
case choice
when 1
puts "hello!"
when 2
puts new_employee()
when 3
exit
end
end
end
end
team_green_turf = Business.new
team_green_turf.run
---------------------------------
employees.rb file
require_relative 'business'
class Employees
attr_accessor :name
def initialize(name)
#name = name
end
def new_employee(name)
puts "What is the employees name?"
name = gets.chomp
e1 = Employees.new(name)
end
end
There are two main ideas:
You need to ensure that the Ruby interpreter has loaded the file containing the method before you attempt to call it. In your case, you are executing the code at the botton of business.rb before the employees.rb file has been fully loaded, so the Employee class and its methods will not be defined. You could fix this in many different ways, but I suggest moving the last two lines of business.rb into their own file called run.rb and putting require_relative 'business' at the top of run.rb, and putting require_relative 'employees' at the top of business.rb, and removing the require_relative 'business' from the top of employees.rb. (The Employees class does not actually use or depend on the Business class so it doesn't make sense to require it.)
You need to provide the proper prefixes to the method to get it to be called. In your case, you want to call the function from the Business class, you would write Employees.new_employee. You would also need to change new_employee to be a class method by putting self. before its name when you are defining it: def self.new_employee(name)....

Checking an array in Ruby to see if an object.name exists

I am making a todo list program in Ruby. I have declared two classes List and Task. List is initialized with an empty array whilst Task has a name and a status (status is initialized as "incomplete") on creating a new Task. I want to edit the createtask method so I can check the task array of the List to see if a task already exists and if it does I don't want to create a new task. How would I do this?
class Task
attr_accessor :name, :status
def initialize(name, status="incomplete")
#name = name
#status = status
end
def to_s
"#{name.capitalize}: #{status.capitalize}"
end
end
class List
attr_accessor :tasksarray
def initialize
#tasksarray = []
end
def create_task(name)
new_task = Task.new(name)
tasksarray.push(new_task)
puts "New task #{new_task.name} has been added with status #{new_task.status}"
end
A method that can help you out here is detect (it's also called find, same method, different name) in the Enumerable mixin.
Use it to pass each element of your list to a block, it will find the first element for which the block returns true.
If no element is found, it returns nil.
In your case, you want to check whether an element has the same name as the one you're giving:
existing_task = #tasksarray.detect { |task| task.name == name }
if existing_task
puts "Not adding new task, found existing task with status #{ existing_task.status }"
else
new_task = Task.new(name)
#tasksarray.push(new_task)
puts "New task #{new_task.name} has been added with status #{new_task.status}"
end
You could to the same with Enumerable#any?
The comment you left on your question suggests that you only want a single list of tasks. You can do that by creating a class List, but you won't be creating instances of that class. Instead, the class itself will hold the list of tasks in a class instance variable. Consider the following.
Code
class Task
attr_accessor :name, :status
def initialize(name, status)
#name = name
#status = status
end
end
class List
singleton_class.send(:attr_reader, :tasks)
#tasks = []
def self.create_task(name, status="incomplete")
return nil if tasks.any? { |task| task.name == name }
new_task = Task.new(name, status)
tasks << new_task
new_task
end
end
Notice that I've removed the puts statements. That makes the methods more robust. We can provide an explanation in a separate method:
def explain(name, task)
if task
puts "New task '#{name}' with status '#{task.status}' has been added"
else
puts "'#{name}' is already in the list of tasks"
end
end
Examples
name = "Take out the papers"
task = List.create_task(name, "will do soon")
#=> #<Task:0x007ffa2a161058 #name="Take out the papers", #status="will do soon">
explain(name, task)
# New task 'Take out the papers' with status 'will do soon' has been added
name = "and the trash"
task = List.create_task(name)
#=> #<Task:0x007ffa2a149f20 #name="and the trash", #status="incomplete">
explain(name, task)
# New task 'and the trash' with status 'incomplete' has been added
name = "Take out the papers"
task = List.create_task(name)
#=> nil
explain(name, task)
# 'Take out the papers' is already in the list of tasks
List.tasks
#=> [#<Task:0x007ffa2a161058 #name="Take out the papers", #status="will do soon">,
# #<Task:0x007ffa2a149f20 #name="and the trash", #status="incomplete">]
List.tasks[1].name
#=> "and the trash"
Discussion
Note that, since we will not be creating instances of List, that class has no initialize method. The line:
singleton_class.send(:attr_reader, :tasks)
sends the method Module#attr_reader with argument :tasks to List's singleton class, thereby creating a getter for #tasks. Three other ways this is commonly done are:
singleton_class.class_eval{attr_reader :tasks}
and
class << self
attr_reader :tasks
end
and
module A
attr_reader :tasks
end
class Klass
extend A
end
Improving what we have
Since we are neither creating instances of List nor subclassing that class, we could make List a module, which would draw attention to the fact that we are not making use of the properties that distinguish a class from a module.
Rather than having instance variables #name and #status, it might makes sense to have a single instance variable that is a hash, that we might call #task. Among other things, that would make it easier if later we wished to add additional task attributes.
To determine if a task is in the list of tasks, we need to step through an array. If there are many tasks, that would be relatively inefficient. We could speed that up considerably by making the list a hash, whose keys are names of tasks and whose values are the task instances. That way, we only have to check to see if the hash has a key name, which is very fast.1.
In sum, let's do the following:
Replace the instance variables #name and #status in Tasks with a hash.
Replace the instance variable #task with a hash.
Make List a module rather than a class.
Revised code
class Task
attr_accessor :task
def initialize(name, status)
#task = { name: name, status: status }
end
end
module List
singleton_class.send(:attr_reader, :tasks)
#tasks = {}
def self.create_task(name, status = 'incomplete')
return nil if tasks.key?(name)
task = Task.new(name, status)
#tasks[name] = task
task
end
end
def explain(name, task)
if task
puts "New task '#{name}' with status '#{task.task[:status]}' has been added"
else
puts "'#{name}' is already in the list of tasks"
end
end
Examples
name = "Take out the papers"
task = List.create_task(name, "real soon now")
#=> #<Task:0x007f802305cf30 #task={:name=>"Take out the papers",
# :status=>"real soon now"}>
explain(name, task)
# New task 'Take out the papers' with status 'real soon now' has been added
name = "and the trash"
task = List.create_task(name)
#=> #<Task:0x007f8023045088 #task={:name=>"and the trash",
# :status=>"incomplete"}>
explain(name, task)
# New task 'and the trash' with status 'incomplete' has been added
name = "Take out the papers"
task = List.create_task(name)
#=> nil
explain(name, task)
# 'Take out the papers' is already in the list of tasks
List.tasks
#=> {"Take out the papers"=>#<Task:0x007f802305cf30
# #task={:name=>"Take out the papers", :status=>"real soon now"}>,
# "and the trash"=>#<Task:0x007f8023045088
# #task={:name=>"and the trash", :status=>"incomplete"}>}
List.tasks["Take out the papers"].task[:status]
#=> "real soon now"
1 This has the drawback of having the name of each task in two places (in the list hash and in the corresponding task instance variable (hash). In general, such duplication is not good programming practice. More experienced coders could probably offer advice here.

Resources