3
\$\begingroup\$

I started writing this program to get a better understanding of different aspects of Ruby. I tried to follow the ruby style guide here.

What it's supposed to do is take the exercises/tasks you give it, and record those tasks. It uses a record of those tasks to automatically create/update the header row of a .csv file for the month, and to automatically create a row for every day that information (repetitions of a given exercise) is inputted. I know this program may seem a little useless, after all, many of us own excel, and currently it just gets input from gets, but I was thinking I might do more with it in the future.

I wrote two versions:

Version one:

# Requires
require "date"
require "csv"

# Functions
def standardize(string)
  # 'Standardizes' strings so that "word word" becomes "Word-Word"
  new_string = ""
  string_parts = string.strip.split
  string_parts.each do |part|
    part.capitalize!
    new_string << part + "-"
  end
  return new_string[0..-2]
end

def ensure_file(file_path)
  # Checks if a file exists and creates it if it doesn't exist
  if !File.file?(file_path)
    file = File.new(file_path, "w")
    file.close
  end
end

def overwrite_csv(path, csv_table)
  # Overwrites a CSV file
  # Made this a function so I can change how I do it throughout the file
  # from one place
  CSV.open(path, "w") do |csv_file|
    csv_table.each { |row| csv_file << row }
  end
end

# Constants
KNOWN_EXERCISES = "known_exercises.txt"
MONTH_FILE = Date::MONTHNAMES[Date.today.month] + ".csv"

# Gets an array of known exercises
ensure_file(KNOWN_EXERCISES)
exercises = []
File.foreach(KNOWN_EXERCISES) do |exercise|
  exercise.strip!
  exercise != "" ? exercises << exercise.strip : nil
end

# Gets information from the user
print "Exercise: "
exercise = standardize(gets.strip)
print "Repetitions: "
reps = gets.strip

# Checks if the exercise is in the known exercises file, writes it in if not
if !exercises.include?(exercise)
  File.open(KNOWN_EXERCISES, "a") do |known_exercises|
    known_exercises.write("\n" + exercise)
  end
  exercises << exercise
end

# Creates/modifies the .CSV file if necessary
current_day = Date.today.month.to_s + "/" + Date.today.day.to_s
if !File.file?(MONTH_FILE)
  CSV.open(MONTH_FILE, "w") do |csv_file|
    header_row = ["Day"] + exercises
    csv_file << header_row
  end
end

# Writes any new exercises to the file on disk
records = CSV.read(MONTH_FILE, headers:true, return_headers:true, converters: :numeric)
if !records[0].include?(exercise)
  records[0] << exercise
  overwrite_csv(MONTH_FILE, records)
end

# Enters user inputted data into the records in memory to edit
records = CSV.read(MONTH_FILE, headers:true, return_headers:true, converters: :numeric)
exercise_col = records.headers.index(exercise)
if !records["Day"].include?(current_day)
  data = [current_day]
  data[exercise_col] = reps
  records << data
elsif records["Day"].include?(current_day)
  cur_day_row_i = records["Day"].index(current_day) # Index of the current day's row
  records[cur_day_row_i][exercise] = reps
end

# Overwrites file on disk with edits in memory
overwrite_csv(MONTH_FILE, records)

Version two:

# Requires
require "date"
require "csv"

# Classes
class CSVFile
  attr_accessor :path, :new_file
  alias new_file? new_file

  def initialize(path)
    @path = path
    @new_file = false

    # Creates file if necessary
    if !File.file?(@path)
      @new_file = true
      create_file(path)
    end

    yield(self) if block_given?
  end

  def overwrite_table(csv_table)
    # Overwrites a CSV file with data from a Table object
    CSV.open(@path, "w") do |csv_file|
      csv_table.each { |row| csv_file << row }
    end
  end

  def overwrite(csv)
    # Overwrites a CSV file with anything that can be appended to CSV.open
    CSV.open(@path, "w") do |csv_file|
      csv_file << csv
    end
  end

  def append(csv)
    # Overwrites the whole csv file to append a line
    records = CSV.read(@path, headers:true, return_headers:true)
    records << csv
    overwrite_table(records)
  end

  def read(**args)
    # CSV.read but without having to specify path
    CSV.read(@path, args)
  end

  alias << append

  def to_s
    CSV.read(@path).to_s
  end
end

# Functions
def create_file(file_path)
  # Creates a blank file at file_path
  file = File.new(file_path, "w")
  file.close
end

def standardize(string)
  # 'Standardizes' strings so that "word word" becomes "Word-Word"
  new_string = ""
  string_parts = string.strip.split
  string_parts.each do |part|
    part.capitalize!
    new_string << part + "-"
  end
  new_string[0..-2]
end

# Constants
KNOWN_EXERCISES = "known_exercises.txt"
RECORDS = Date::MONTHNAMES[Date.today.month] + ".csv"

# Creates exercises file if necessary
create_file(KNOWN_EXERCISES) if !File.file?(KNOWN_EXERCISES)

# Gets an array of known exercises
exercises = []
File.foreach(KNOWN_EXERCISES) do |exercise|
  exercise.strip!
  exercises << exercise if exercise != ""
end

# Gets information from the user
print "Exercise: "
exercise = standardize(gets.strip)
print "Repetitions: "
reps = gets.strip

# Checks if the exercise is in the known exercises file, writes it in if not
if !exercises.include?(exercise)
  File.open(KNOWN_EXERCISES, "a") do |known_exercises|
    known_exercises.write("\n" + exercise)
  end
  exercises << exercise
end

# Creates/modifies the .CSV file
current_day = Date.today.month.to_s + "/" + Date.today.day.to_s
CSVFile.new(RECORDS) do |csv_file|
  # Creates the header row if necessary
  if csv_file.new_file?
    header_row = ["Day"] + exercises
    csv_file << header_row
  end

  # Enters user inputted data into the records in memory to edit
  records = csv_file.read(headers:true, return_headers:true)

  # Edits the header row when necessary
  if !records[0].include?(exercise)
    records[0] << exercise
  end

  # Edits the other rows when necessary
  if !records["Day"].include?(current_day)
    exercise_col = records.headers.index(exercise)
    data = [current_day]
    data[exercise_col] = reps
    records << data
  elsif records["Day"].include?(current_day)
    cur_day_row_i = records["Day"].index(current_day) # Index of the current day's row
    records[cur_day_row_i][exercise] = reps
  end

  # Overwrites file on disk with edits in memory
  csv_file.overwrite_table(records)
end

So if you entered room cleaning for the exercise, and 1 for the reps on June 11, you would get a .csv file that looked like:

Day,Room-Cleaning
6/11,1

And the next month, automatically the header row would look like:

Day,Room-Cleaning

Without you having to tell the program that that is one of the tasks you want to add.

I have a slight background in Python, if that helps in explaining any mistakes I may have made.

\$\endgroup\$
  • \$\begingroup\$ A little style nitpicking, personally I think it looks better to do this when using the ! operator if !(some command). \$\endgroup\$ – 13aal Jun 12 '16 at 18:22
  • \$\begingroup\$ Where does this store the files btw..? \$\endgroup\$ – 13aal Jun 12 '16 at 18:30
  • 1
    \$\begingroup\$ @13aal This program stores the files in a "known_exercises.txt" and "[Month].csv" format in the directory of the script. Those values are specified in the constants near the middle of the script. Also, I kind of like the not operator on conditionals without the parentheses, which I like to save for more complex conditionals to use when necessary. Even then, if I have to use too many parentheses in one if statement, it may or may not be an indication that I am able to refactor that code to look much better. \$\endgroup\$ – Thonanth Siddef Jun 12 '16 at 23:25
  • \$\begingroup\$ Yeah I ran the script and saw, all around I'd say it looks pretty good, nicely done man \$\endgroup\$ – 13aal Jun 13 '16 at 1:48
  • 1
    \$\begingroup\$ @13aal Thanks! Character limit is preventing me from just thanking you, so take these extra words too!!! \$\endgroup\$ – Thonanth Siddef Jun 14 '16 at 4:41
3
\$\begingroup\$

Sorry I don't have time to review the full program, but here's a much simpler replacement for the standardize method. Note strip is a redundant if you're then going to be splitting on white space:

def standardize(string)
  # 'Standardizes' strings so that "word word" becomes "Word-Word"
  string.split.map(&:capitalize).join('-')
end
\$\endgroup\$
  • \$\begingroup\$ I'm curious to know how that works :0, so I'll see about researching it. Thanks for the feedback! \$\endgroup\$ – Thonanth Siddef Jun 12 '16 at 23:18
  • \$\begingroup\$ @ThonanthSiddef joins are extremely helpful. I've started using them more often. \$\endgroup\$ – Carcigenicate Jun 13 '16 at 12:39
  • \$\begingroup\$ @Carcigenicate I've already started my next mini project, so I'll look into joins soon and see if they might have any efficacy in executing the task at hand. They probably will from looking at this answer \$\endgroup\$ – Thonanth Siddef Jun 14 '16 at 4:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.