DEV Community

Cover image for Wine Spectator's Top 100 Wines - Ruby CLI Project
Katherine Kavourakis
Katherine Kavourakis

Posted on

2

Wine Spectator's Top 100 Wines - Ruby CLI Project

For the first project in Flatiron School's Software Engineering Bootcamp, students are asked to build a Command Line Interface (CLI) to an external data source. The CLI should be composed of an Object Oriented Ruby application, and demonstrate knowledge gained during the Ruby Module of the program.

For my project, I scraped data from Wine Spectator's list of Top 100 Wines of 2020 and created an app that allows users to interact with the information provided.


PLAN

Being that this was my first full-scale project build, I spent a considerable amount of time in the planning phase. I studied the requirements, watched live build videos, and explored code from other projects to visualize how I wanted to structure my own.

REQUIREMENTS

  • Provide a CLI
  • CLI application must provide access to data from a web page
  • The data provided must go one level deep - where a user can make a choice and receive detailed information on their selection
  • Use strong Object Oriented design patterns - data should be stored in a collection of objects, not hashes

OUTLINE

The goal of the Top 100 Wines CLI App is to output a numbered list of wines by rank, allow the user to select a wine that they're interested in, provide the details of that wine, and then either loop back to the list or exit the program based on user input.

Image description


BUILD

After creating the skeleton of my application (bin files, gemspec, environment, etc.), I focused on the structure and responsibilities of my Class files.

SCRAPER CLASS

The Scraper Class is responsible for scraping data from the Wine Spectator website.

class TopWines::Scraper
def self.scrape_wines
doc = Nokogiri::HTML(open("https://top100.winespectator.com/lists/"))
wine_block = doc.css("tbody tr")
wine_block.each do |block|
wine = TopWines::Wine.new
wine.rank = block.css(".rank").text
wine.winery = block.css("span.sort-text").text
wine.name = block.css("div.table-name span.wineName").children[1].text.strip
wine.vintage = block.css(".vintage").text
wine.score = block.css(".score").text
wine.price = block.css(".price").text
wine.full_description = block.css("div.tabel-note").children[0].text.strip.chomp(' —')
end
end
end
view raw scraper hosted with ❤ by GitHub

WINE CLASS

The Wine Class is responsible for creating and storing wine objects.

class TopWines::Wine
attr_accessor :rank, :winery, :name, :vintage, :score, :price, :full_description
@@all = []
def initialize(rank = nil, winery = nil, name = nil, vintage = nil, score = nil, price = nil, full_description = nil)
@rank = rank
@winery = winery
@name = name
@vintage = vintage
@score = score
@price = price
@full_description = full_description
@@all << self
end
def self.all
@@all
end
def self.find(id)
self.all[id-1]
end
end
view raw wine hosted with ❤ by GitHub

CLI CLASS

The CLI Class is responsible for all operational functionality. In addition to listing the wines numerically by rank, I implemented a method asking the user which group of wines they would like to view in intervals of 20. I also utilized the Colorize gem to add some visual depth to the program.

class TopWines::CLI
def call
TopWines::Scraper.scrape_wines
puts "\nWelcome to Wine Spectator's Top 100 Wines of 2020!".white.on_black
@input = ""
until @input == "exit"
start
select_wine
next_move
end
end
def start
puts "\nPlease select the group of wines you would like to see: \n1-20 \n21-40 \n41-60 \n61-80 \n81-100".black.on_light_white
input = gets.strip.to_i
print_wines(input)
select_wine
next_move
end
def select_wine
puts "\nPlease select the corresponding number of the wine of your choice for more details.".black.on_light_white
input = gets.strip
wine = TopWines::Wine.find(input.to_i)
print_wine(wine)
end
def next_move
puts "\nWould you like to view another wine? Enter Y or N".black.on_light_white
@input = gets.strip.downcase
if @input == "y"
start
elsif @input == "n"
puts "\nCheers!".white.on_black
exit
elsif @input == "exit"
puts "\nCheers!".white.on_black
exit
else
puts "\nI'm not sure what you mean. Keep exploring wines, or type 'exit' to leave.".black.on_light_white
next_move
end
end
def print_wines(from_number)
puts "\nWINES RANKED #{from_number} - #{from_number+19}".black.on_light_white
TopWines::Wine.all[from_number-1, 20].each.with_index(from_number) do |wine, index|
puts "\n#{index}. #{wine.winery} - #{wine.name}"
end
end
def print_wine(wine)
puts "\n#{wine.rank}. #{wine.winery.upcase} - #{wine.name.upcase}".black.on_light_white
puts "RANK: #{wine.rank}"
puts "WINERY: #{wine.winery}"
puts "NAME: #{wine.name}"
puts "VINTAGE: #{wine.vintage}"
puts "SCORE: #{wine.score}"
puts "PRICE: #{wine.price}"
puts "FULL DESCRIPTION: #{wine.full_description}"
end
end
view raw cli hosted with ❤ by GitHub

EXECUTE

The video below demos the app at runtime.


[cover photo by Mathilde Langevin on Unsplash]

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more

Top comments (0)

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more