I have just finished with the first Ruby exercise on Level Up Rails, and wanted to get an idea on how I can refactor the code.
You can find the original exercise on github - including the requirements that I have to implement as well as the CSV data files (there are two).
Requirements
Go check out the CSVs and come back. Done? Cool, I've just got a few features I need:
- I loaded my favorite dinosaurs into a CSV file you'll need to parse. I don't know a lot about African Dinosaurs though, so I downloaded one from The Pirate Bay. It isn't formatted as well as mine, so you'll need to handle both formats.
- I have friends who ask me a lot of questions about dinosaurs (I'm kind of a big deal). Please make sure the dinodex is able to answer these things for me:
- Grab all the dinosaurs that were bipeds.
- Grab all the dinosaurs that were carnivores (fish and insects count).
- Grab dinosaurs for specific periods (no need to differentiate between Early and Late Cretaceous, btw).
- Grab only big (> 2 tons) or small dinosaurs.
- Just to be sure, I'd love to be able to combine criteria at will, even better if I can chain filter calls together.
- For a given dino, I'd like to be able to print all the known facts about that dinosaur. If there are facts missing, please don't print empty values, just skip that heading. Make sure to print Early / Late etc for the periods.
- Also, I'll probably want to print all the dinosaurs in a given collection (after filtering, etc).
My code is as follows:
require 'csv'
# handle filtering on weight
def weight_filter(data, property, value)
if value.downcase == "large"
data.delete_if do |row|
weight = row["weight_in_lbs"]
weight.nil? ? true : weight <= 2000
end
else
data.delete_if do |row|
weight = row["weight_in_lbs"]
weight.nil? ? true : weight > 2000
end
end
end
# properly format the arguments
def format_args(property, value)
# make Insectivores and Piscivores into Carnivores
if value == "Carnivore"
value = Array.new
value << "Carnivore" << "Insectivore" << "Piscivore"
end
return property, value
end
def filter_on(filters = {})
# check arguments
if filters.empty?
message = <<-EOS
Usage: filter_on( {property => value} )
Where property can be:'WALKING' | 'DIET' | 'PERIOD' | 'SIZE'
Example Usage: filter_on({ "WALKING" => "Biped", "DIET" => "Carnivore"})
EOS
message
else
# read data
data = read_data
# filter
filters.each do |property, value|
property = property.downcase
if property == "size"
# special handler for weight
weight_filter(data, property, value)
else
property, value = format_args(property, value)
data.delete_if { |row| !(value.include?(row[property])) }
end
end
# skip headers when printing
no_headers = []
data.each { |row| no_headers << row }
no_headers
end
end
def dinoinfo(dinosaur)
# rationalize argument
dinosaur.capitalize!
single_dino_data = ''
# load data into memory
data_set = read_data
# extract single dinosaur row from data_set
read_data.each do |row|
single_dino_data = row if row["name"] == dinosaur
end
formatted_output = "\n"
# did we find a match?
if single_dino_data.empty?
formatted_output << "\tWe did not find a match for \"#{dinosaur}\" in our Dinodex!\n\n"
else
# format extraction
single_dino_data.each do |property, value|
if !value.nil?
# add colon
property = "#{property}:"
formatted_output << "#{property.upcase.rjust(15)} #{value}\n\n"
end
end
end
formatted_output
end
def read_data
# load dinodex.csv into memory
dinodex = CSV.read('dinodex.csv', headers: true, converters: :numeric,
header_converters: :downcase)
# append information from african dinos
CSV.foreach('african_dinosaur_export.csv', headers: true, converters: :numeric,
header_converters: :downcase) do |row|
formatted_input = []
formatted_input << row["genus"] << row["period"] << nil
# handle carnivore
row["carnivore"] == "Yes" ? formatted_input << "Carnivore" : formatted_input << nil
# continue adding to formatted input
formatted_input << row["weight"] << row["walking"]
# add to dinodex
dinodex << formatted_input
end
dinodex
end
I would love to hear some feedback on what can be done better.