Uninitialized constant in Ruby - 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.

Related

How to call an class's included module function from a rake?

I am in a task in a rake file.
# gottaRunThis.rake
task [:var] => :environment do |t, args|
Foo::Bar::Bell::this_function(args.var)
#runs Bell::this_function(var) but fails to complete.
# errors out with the words:
# NoMethodError: undefined method `put' for nil:NilClass
# Because Bell needs a #connection variable that doesn't exist in
# it's context when called directly.
end
The thing is, the target module and def are meant to be included in another class.
# mainclass.rb
module Foo
module Bar
class TheMainClass
include Foo::Bar::Bell
def initialize
#site = A_STATIC_SITE_VARIABLE
#connection = self.connection
end
def connection
# Connection info here, too verbose to type up
end
end
end
end
And Bell looks like.
# bell.rb
module Foo
module Bar
module Bell
def do_the_thing(var)
#things happen here
#var converted to something here
response = #connection.put "/some/restful/interface, var_converted
end
end
end
Should I modify Bell so it somehow is including TheMainClass? (And if so, I don't know how?) Or is there some syntax I should use in my rake file like
Foo::Bar::TheMainClass::Bell::do_the_thing(args.var)
#I don't think this works... should it? What would the equivilent but working version look like?
Included methods are available as instance methods, so you can call do_the_thing like this:
main_class = Foo::Bar::TheMainClass.new
main_class.do_the_thing(args.var)

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')

Ruby modules for functions

It is possible to add methods to a class using modules. E.g.,
class Test
include Singleton
end
Is it possible to do the same with methods? E.g.,
class Test
include Singleton
def test
include Instructions_to_add_to_the_method
puts 'done'
end
end
where:
module Instructions_to_add_to_the_method
puts 'hi !'
end
When calling Test.instance.test, I want:
hi !
done
I do not wish to call another method, as it would give me issues with the scope of my variables.
It is possible to add methods to a class using modules. E.g.,
class Test
include Singleton
end
No. This does not "add methods to a class". include simply makes Singleton the superclass of Test. Nothing more. Nothing is being "added to a class". It's just inheritance.
Is it possible to do the same with methods? E.g.,
class Test
include Singleton
def test
include Instructions_to_add_to_the_method
puts 'done'
end
end
There is no method Test#include, so this will simply raise a NoMethodError.
When calling Test.instance.test, I want:
hi !
done
That's what inheritance is for:
class Test
include Singleton
def test
super
puts 'done'
end
end
module Instructions_to_add_to_the_method
def test
puts 'hi'
end
end
class Test
include Instructions_to_add_to_the_method
end
Test.instance.test
# hi
# done
Note that this way of using inheritance in Ruby is a little bit backward. If you really need something like this, you should use a language like Beta, where this is how inheritance works naturally.
A better solution would be something like the Template Method Software Design Pattern which in Ruby can be something as simple as yielding to a block:
class Test
include Singleton
def test
yield
puts 'done'
end
end
Test.instance.test { puts 'hi' }
# hi
# done
or taking a Proc as an argument:
class Test
include Singleton
def test(prc)
prc.()
puts 'done'
end
end
Test.instance.test(-> { puts 'hi' })
# hi
# done
or by calling a hook method:
class Test
include Singleton
def test
extension_hook
puts 'done'
end
def extension_hook; end
end
class HookedTest < Test
def extension_hook
puts 'hi'
end
end
HookedTest.instance.test
# hi
# done
I do not wish to call another method, as it would give me issues with the scope of my variables.
There are no variables in your code, so there can't possibly be any "issues" with them.
This is probably a terrible idea. Messing with your classes at run-time will make problems hard to debug (which version of that method was I calling there?) and your code hard to follow (oh, right, I can't call that method yet because it's not defined yet, I need to call this method first).
Given what you said at the end of your question about variable scoping, I'm almost certain that this won't solve the problem you actually have, and I'd suggest actually posting your actual problem.
That said, the question you asked can be answered by using the included and extended hooks which, unsurprisingly, fire when a module gets included and extended:
module FooModule
def self.included(base)
puts 'FooModule included'
end
def self.extended(base)
puts 'FooModule extended'
end
def new_method
puts 'new method called'
end
end
class Extender
def test_extend
self.extend FooModule
puts 'done'
end
end
class Includer
def test_include
self.class.include FooModule
puts 'done'
end
end
t1 = Extender.new
t2 = Extender.new
t1.test_extend # Prints out "FooModule extended" followed by "done"
t1.new_method # Prints out "new method called"
t2.new_method rescue puts 'error' # Prints out "error" - extend only modifies the instance that calls it
t1 = Includer.new
t2 = Includer.new
t1.test_include # Prints out "FooModule included" followed by "done"
t1.new_method # Prints out "new method called"
t2.new_method # Prints out "new method called" - all past and future instances of Includer have been modified

new Class item displaying as object instead of string

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.

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