John Keith

Hi, I'm a developer that loves Ruby, Rails, Angular, and iOS.

Build What You Use as a Way to Learn

I can’t remember where I read it, but recently I came across the advice that one of the best ways to practice programming is to recreate the type of applications you use everyday. No need to make them flashy, simply implement their core features in order to achieve the same results.

I decided to give it a try today and build a tiny todo app in Ruby. I wanted to write the app keeping in mind the directives from Sandi’s Metz’s Practical Object-Oriented Design in Ruby.

“Remember that a class should do the smallest possible useful thing. That thing ought to be simple to describe. If the simplest description you can devise uses the word “and,” the class likely has more than one responsibility.​” – Metz

While it ended up that my app only had one class, I tried to follow in the spirit of the quote above by keeping my methods as focused as possible. I also tried to heed her advice to stave off design decisions for as long as possible – an interesting strategy that had me trying to store all completed and incomplete todos in the same file. I quickly realized, though, that this was an inefficient way of doing things, since my completed items list would (hopefully) be a far larger file than my active items list and there is no reason to load that large file every time the app is opened.

There is one little quirk that I can’t seem to shake: when you mark a todo as finished, the item is removed from the activelist.txt file, but is not added to the completelist.txt file until you close the program. I’m sure it has something to do with the file opening modes I selected, but I can’t figure out why I am able to modify and then view the modifications done to activelist.txt while in the app, but not do the same with completelist.txt.

In the end, this was a great way to get practice using the File and IO classes of Ruby and exploring their methods. Not to mention, I get a pretty nifty little command line todo list manager (which I’ll stick in Dropbox for easy access) out of it!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
require 'rbconfig'

class Todo
  def clear_screen
      host_os = RbConfig::CONFIG['host_os']

      case host_os
          when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
              system "cls"
          when /darwin|mac os|linux|solaris|bsd/
              puts "\e[H\e[2J"
      end
  end

  def open_app
      view_all
  end

  def view_all
      clear_screen

      list_read = read_active_list
      
      puts "------INCOMPLETE------\n\n"
      list_read.each do |line|
          puts line
      end
      puts "\n----------------------"

      choose_action
  end

  def read_active_list
      list = File.open("activelist.txt","a+").readlines
  end

  def choose_action
      puts %/
     What would you like to do with your list?
     1. Add a todo
     2. Mark complete
     3. View incomplete
     4. View completed
     5. Exit/

      choice = gets.chomp.downcase

      case choice
          when "1" then input_todo
          when "1." then input_todo
          when "add" then input_todo
          when "add todo" then input_todo
      
          when "2" then mark_done
          when "2." then mark_done
          when "mark" then mark_done
          when "mark done" then mark_done

          when "3" then view_incomplete
          when "3." then view_incomplete
          when "view incomplete" then view_incomplete
          
          when "4" then view_complete
          when "4." then view_complete
          when "view completed" then view_complete

          when "5" then return
          when "5." then return
          when "exit" then return

          else
              p "Not a valid choice"
              choose_action
      end
  end

#ask for todo
  def input_todo
      puts "\nEnter your new todo:"
      todo = gets.chomp
      add_todo(todo)
      view_all
  end

#write method
  def add_todo(todo)
      File.open("activelist.txt", "a") { |f| f.write "#{todo}\n" }
  end

#mark done method
  def mark_done
      clear_screen

      list_read = File.open("activelist.txt","a+").readlines

      puts "------INCOMPLETE------\n\n"
      list_read.each_with_index do |line, index|
          puts "#{index + 1}. " + line
      end
      puts "\n----------------------"

      puts "\nWhich todo do you want to mark complete? (Type the number or zero to cancel)."
      
      choice = gets.chomp.to_i

      File.open("activelist.txt", "w") do |f|
          list_read.each_with_index do |line, index|
              if (index + 1) == choice
                  add_to_complete_list line
              else
                  f.write "#{line}"
              end
          end
      end

      view_all
  end

  def add_to_complete_list line
      File.open("completelist.txt", "a").write "#{line}"
  end

  def view_complete
      clear_screen

      complete_list = File.open("completelist.txt", "a+").readlines

      puts "------COMPLETED------\n\n"
      complete_list.each do |line|
          puts line
      end
      puts "\n---------------------"

      choose_action
  end

  def view_incomplete
      view_all
  end
end

my_todo_list = Todo.new

my_todo_list.open_app

Comments