Last active
July 17, 2017 22:35
-
-
Save luizfonseca/1876b56f02fa9a048d8566b9e68d8eb0 to your computer and use it in GitHub Desktop.
Cookbook Day II
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Requiring it because Rake tests controllers | |
# individually | |
require_relative 'view' | |
require_relative 'recipe' | |
require_relative 'parser' | |
class Controller | |
def initialize(cookbook) | |
@cookbook = cookbook | |
@view = View.new | |
# We are using a Parser class now, to allow | |
# users to import/search recipes based on | |
# ingredients | |
@parser = Parser.new | |
end | |
# CRUD: Read | |
# List recipes | |
def list | |
show_recipes | |
end | |
# CRUD: Create | |
def create | |
name = @view.ask_for_name | |
description = @view.ask_for_description | |
# Instantiating a new Recipe instance, so | |
# we can talk to it (.name, .description and so on) | |
recipe = Recipe.new(name, description) | |
# Adding it/Synchronizing it with the repo | |
@cookbook.add_recipe(recipe) | |
end | |
# This method is being called by the Router file | |
# see router.rb | |
def import | |
# Get user input | |
ingredient = @view.ask_user_about_ingredient | |
# Fetch data from a specific website | |
import_ingredient_recipes(ingredient) | |
end | |
# CRUD: Delete | |
def destroy | |
show_recipes # => calling the private method | |
recipe_index = @view.delete_recipe | |
@cookbook.remove_recipe(recipe_index) | |
end | |
private | |
# defining a method that has a common behavior | |
# so we can reuse it inside other methods | |
def show_recipes | |
recipes = @cookbook.all | |
@view.list_recipes(recipes) | |
end | |
# NOTE: We are making it private because only the controller can call it! | |
def import_ingredient_recipes(ingredient) | |
# Call the Parser method to fetch new recipes based on the given | |
# ingredient. Note that this method returns Recipe instances, so its | |
# easier for us to talk to them (.name, .description, .cooking_time) | |
results = @parser.search_recipes(ingredient) | |
# Display the results we just go from the Parser class | |
# see parser.rb | |
@view.display_import_results(results) | |
# Getting the user input as a Fixnum/Integer | |
# So we can match with the index in the results | |
recipe_index = @view.ask_for_which_recipe_to_import | |
# Call for save the imported recipe | |
@cookbook.add_recipe(results[recipe_index]) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This class is the repository, | |
# where we store things and define methods | |
# related to storing/retrieving | |
require 'csv' | |
require_relative 'recipe' | |
class Cookbook | |
# This method initializes with a path | |
# to a csv file (see router.rb) | |
def initialize(filepath) | |
@recipes = [] | |
@filepath = filepath | |
# At the start, we get all recipes from CSV | |
# and transform them to Recipe objects | |
# So listing them works just fine | |
CSV.foreach(@filepath) do |row| | |
# Make the CSV synchronize with the @recipes array | |
# So we can type "1" and list all the recipes from the | |
# CSV file | |
@recipes << Recipe.new(row[0], row[1], row[2], row[3]) | |
end | |
end | |
# Just return all recipes (instance method) | |
def all | |
@recipes | |
end | |
# Add a recipe to the Recipes Array | |
def add_recipe(recipe) | |
@recipes << recipe | |
# This method saves the recipe to the CSV | |
# after we add it to the array | |
write_recipes # reusing a common instruction | |
end | |
# Remove a recipe from the Recipes Array | |
# and update the CSV afterwards | |
def remove_recipe(recipe_index) | |
@recipes.delete_at(recipe_index) | |
write_recipes # reusing a common instruction | |
end | |
private | |
# Common instruction: it updates the CSV | |
# | |
def write_recipes | |
CSV.open(@filepath, 'wb') do |csv| | |
@recipes.each do |recipe| | |
# UPDATE: we add new fields to the initialize | |
# now that we have more of them | |
# See line 19, we update it as well | |
csv << [recipe.name, recipe.description, recipe.cooking_time, recipe.difficulty] | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# We need to require Recipe because we are using | |
# it on this file | |
require_relative "recipe" | |
# Using nokogiri and openning urls require these | |
# 2 libraries | |
require "nokogiri" | |
require "open-uri" | |
# This class deals with getting recipes from | |
# The website Letscookfrench. We are using Nokogiri, which | |
# allows us to walk over the HTML document (and its elements) | |
class Parser | |
# In order to use this method, we can do | |
# parser = Parser.new -> parser.search_recipes('strawberry') | |
def search_recipes(ingredient) | |
# Initialing an empty array before the loop, so we | |
# can store the results there | |
results = [] | |
# I am storing this outside because we can than change this for another search | |
# afterwards | |
url = "http://www.letscookfrench.com/recipes/find-recipe.aspx?s=#{ingredient}&type=all" | |
webpage = Nokogiri::HTML(open(url), nil, 'utf-8') | |
# We got the entire document on the variable called WEBPAGE | |
# We can then use .search on it, asking for specific elements | |
# See nokogiri documentation, it's kinda tricky, but you will get | |
# the hang of it | |
webpage.search(".m_contenu_resultat").each do |element| | |
# We ask for each element based on INSPECT on the browser | |
# .search is chainable, so you can .search().search() until | |
# you find something you need | |
title = element.search('.m_titre_resultat a').text | |
description = element.search('.m_texte_resultat').text | |
cooking_time = element.search('.m_detail_time div ~ div').text | |
# This input comes as Vegan - Very Easy - Moderate | |
# So we use .split('-') so we can get then separated, and then | |
# we get the 3rd item from it (comes after Very Easy in the example above) | |
difficulty = element.search('.m_detail_recette').text.split("-")[2].strip | |
# Add a new instance of Recipe to the results array | |
# You see, we dont add strings or numbers etc | |
# We add Recipe instances so we can deal with them just as | |
# simple. We can then call all the Recipe methods on other places | |
results << Recipe.new(title, description, cooking_time, difficulty) | |
end | |
# Just returning the results (multiple Recipe) | |
return results | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Recipe | |
# define that :name and :description, cooking_time, difficulty | |
# can be read, but no written | |
attr_reader :name, :description, :cooking_time, :difficulty | |
def initialize(name, description, cooking_time, difficulty) | |
@name = name | |
@description = description | |
@cooking_time = cooking_time | |
@difficulty = difficulty | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Router | |
def initialize(controller) | |
@controller = controller | |
@running = true | |
end | |
def run | |
puts "Welcome to the Cookbook!" | |
puts " -- " | |
while @running | |
display_tasks | |
action = gets.chomp.to_i | |
print `clear` | |
route_action(action) | |
end | |
end | |
private | |
def route_action(action) | |
case action | |
when 1 then @controller.list | |
when 2 then @controller.create | |
when 3 then @controller.destroy | |
when 4 then @controller.import | |
when 5 then stop | |
else | |
puts "Please press 1, 2, 3 or 4" | |
end | |
end | |
def stop | |
@running = false | |
end | |
def display_tasks | |
puts "" | |
puts "What do you want to do next?" | |
puts "1 - List all recipes" | |
puts "2 - Create a new recipe" | |
puts "3 - Destroy a recipe" | |
puts "4 - Import recipes from [Marmiton|LetsCookFrench]" | |
puts "5 - Stop and exit the program" | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This class is responsible for only: | |
# Displaying (puts) and | |
# Receiving (gets) user inputs/outputs | |
# ITS only related to recipes controller | |
class View | |
def list_recipes(recipes) | |
if recipes.empty? | |
puts "You have no recipes!!" | |
else | |
recipes.each_with_index do |recipe, index| | |
puts "#{index + 1} - #{recipe.name} (#{recipe.description})" | |
end | |
end | |
end | |
def ask_for_name | |
puts "What's the name of your recipe?" | |
print "> " | |
gets.chomp | |
end | |
def ask_for_description | |
puts "What's the description of your recipe?" | |
print "> " | |
gets.chomp | |
end | |
def delete_recipe | |
puts "What recipe do you want to delete?" | |
print "> " | |
gets.chomp.to_i - 1 | |
end | |
def ask_user_about_ingredient | |
puts "What ingredient would you like a recipe for?" | |
print "> " | |
return gets.chomp | |
end | |
def display_import_results(recipes) | |
puts "#{recipes.size} results found!" | |
recipes.each_with_index do |recipe, index| | |
puts "#{index + 1}. #{recipe.name} | #{recipe.cooking_time}" | |
end | |
end | |
def ask_for_which_recipe_to_import | |
puts "Please type a number to choose which recipe to import" | |
print "> " | |
return gets.chomp.to_i - 1 | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment