HiveBrain v1.2.0
Get Started
← Back to all entries
patternrubyMinor

Creating a Wrapper for CSV Data

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
creatingcsvwrapperfordata

Problem

I am trying to complete a Ruby coding exercise. The specifics of the excercise can be found on a the level_up_exercises GitHub repo. This repo also contains the CSV file used in the exercise.

I would like you to comment on how well you think I met the requirements with the exception of

  • loading the African dinosaur data



  • exporting the data to JSON (the reason I have not completed these is because I am pretty sure I can do the first one, and I need to do further homework before I can do the second one)



I have put my code below, but I think a run-down of how it works might make it easier for you to comment on the code.

First, I created a Dinosaur class. Most of the attributes are readable (except for the @period instance variable). I've created a custom Dinosaur#to_s method in order to print the dinosaur object. I've also created Dinosaur#carnivorous, Dinosaur#big, and Dinosaur#small. These methods return Booleans, but I didn't name them with an ending "?" for reasons that might be clear in the implementation of the filter method.

Secondly, I created a Dinodex class. This class holds Dinosaur objects in an array. Because the internal data structure is an array, I've defined Dinodex#<<(Dinosaur). Dinodex#to_s merely aggregates the strings returned calls to Dinosaur#to_s, for all the dinosaurs in the dinodex.

The interesting part about the Dinodex (or at least the one I had the most trouble with), is the Dinodex#filter method. The Dinodex#filter method takes an options hash, where (ideally) the keys are attributes of the Dinosaur class, and the values are what the user is selecting for.

NOTE: I am just realizing now that I am writing this out that the else branch in the Dinodex#filter method that I should check if param is valid. I would do this by using the respond_to?(param) method.

I had a tough time figuring out how to make the Dinodex#filters method recursive, until I realized I can just update what the local

Solution

carnivorous = ["Yes", "Carnivore", "Insectivore", "Piscivore"]


This should be a constant, declared in the Dinosaur class and not in the carnivorous method. Also it doesn't make a lot of sense. If "Yes" means carnivorous, what does "Carnivore" mean? I would change this to

DIETS = ["Carnivore", "Insectivore", "Piscivore"]


I would rewrite Dinodex#filter to use Enumerable#inject. Examine:

def filter(options = {})
  options.inject(@dinosaurs) do |remaining, option|
    remaining.select { |dino| dino.send(option[0]) == option[1]}
  end
end


This requires you to have methods for all your queryables, which you seem to already have by using attr_reader. If you wanted to get really fancy you could modify your options hash to look something like this:

options = [
  [:size, 500, :<],
  [:diet, "Piscivore"] # assume no third parameter means `==`
]


And then do something like this:

def filter(options = [])
  options.inject(@dinosaurs) do |remaining, option|
    remaining.select { |dino| dino.send(option[0]).send(option[2] || :==, option[1])}
  end
end


See a test of this here. Also I just tested it again with the option [[:name, /saur/, :=~]] and it works beautifully as well (returning Stegosaur and Apatosaur but not T-Rex), so that's cool.

That's a bit unreadable, so maybe make each option a hash like {info: :size, value: 500, op: :<}

You can cut down a bunch of repetition in your Dino#to_s method like this:

[:name, :period, :continent, :diet, :weight, :locomotion, :description].each do |stat|
  value = self.send(stat)
  representation << stat.to_s.capitalize.ljust(justification) + value + "\n" if value
end


In class Dinodex, these two methods:

def initialize(dinosaurs = [])
  @dinosaurs = dinosaurs
end

def <<(dino)
  @dinosaurs << dino
end


should both be making sure that everything being inserted into @dinosaurs actually is a dinosaur, or your later methods that try to call methods like continent on will crash.

Your entire Dinodex#to_s method can be shortened to this:

def to_s
  @dinosaurs.map(&:to_s).join("\n") + "\n" + ("-" * 99)
end

Code Snippets

carnivorous = ["Yes", "Carnivore", "Insectivore", "Piscivore"]
DIETS = ["Carnivore", "Insectivore", "Piscivore"]
def filter(options = {})
  options.inject(@dinosaurs) do |remaining, option|
    remaining.select { |dino| dino.send(option[0]) == option[1]}
  end
end
options = [
  [:size, 500, :<],
  [:diet, "Piscivore"] # assume no third parameter means `==`
]
def filter(options = [])
  options.inject(@dinosaurs) do |remaining, option|
    remaining.select { |dino| dino.send(option[0]).send(option[2] || :==, option[1])}
  end
end

Context

StackExchange Code Review Q#90780, answer score: 7

Revisions (0)

No revisions yet.