<file_sep>require 'httparty'


class RecipeSearchWrapper
  @app_id = ENV["APPLICATION_ID"]
  @app_key = ENV["APPLICATION_KEY"]
  @base_url = "https://api.edamam.com/search?"

  class RecipeError < StandardError; end

  def self.search_recipes(query)
    url = @base_url  + "app_id=#{@app_id}&app_key=#{@app_key}" + "&q=#{query}" + "&to=100"

    encoded_uri = URI.encode(url)

    response = HTTParty.get(encoded_uri).parsed_response

    # raise_on_error(response)

    recipes = []

    if response["hits"]
      response["hits"].each do |hit|
        label = hit["recipe"]["label"]
        uri = hit["recipe"]["uri"]
        opts = {}
        opts["image"] = hit["recipe"]["image"]

        recipes << Recipe.new(uri, label, opts)
      end
    end
    return recipes
  end


  def self.find_recipe(uri)

    encoded_uri = URI.encode("https://api.edamam.com/search?app_id=#{@app_id}&app_key=#{@app_key}&r=" + uri)

    response = HTTParty.get(encoded_uri).parsed_response

    # raise_on_error(response)

    unless response.empty?
      uri = response[0]["uri"]
      label = response[0]["label"]
      opts = {}
      opts["image"] = response[0]["image"]
      opts["url"] = response[0]["url"]
      opts["servings"] = response[0]["yield"]
      opts["ingredients"] = response[0]["ingredientLines"]
      opts["health_labels"] = response[0]["healthLabels"]
      opts["calories"] = response[0]["calories"]
      opts["fat"] = response[0]["totalNutrients"]["FAT"]["quantity"]
      opts["saturated_fat"] = response[0]["totalNutrients"]["FASAT"]["quantity"]
      opts["mono_fat"] = response[0]["totalNutrients"]["FAMS"]["quantity"]
      opts["carbs"] = response[0]["totalNutrients"]["CHOCDF"]["quantity"]
      opts["protein"] = response[0]["totalNutrients"]["PROCNT"]["quantity"]
      opts["sodium"] = response[0]["totalNutrients"]["NA"]["quantity"]
      opts["fiber"] = response[0]["totalNutrients"]["FIBTG"]["quantity"]
      opts["cholesterol"] = response[0]["totalNutrients"]["CHOLE"]["quantity"]
      @recipe = Recipe.new(uri, label, opts)
      return @recipe
    else
      RecipeError.new("We couldn't find that recipe. Maybe you could write it?")
    end
  end

  private
  def self.raise_on_error(response)
    unless response["ok"]
      raise RecipeError.new(response["error"])
    end
  end

end
<file_sep>require 'test_helper'

describe "RecipeApiWrapper" do

  describe "search_recipes" do

    it "gives a list of recipes" do
      VCR.use_cassette("recipes") do
        query = "rabbit"
        recipes = RecipeSearchWrapper.search_recipes(query)

        recipes.each do |recipe|
          recipe.must_be_kind_of Recipe
        end
      end
    end
  end

  describe "find_recipe" do
    it "returns a recipe" do
      VCR.use_cassette("find") do
        rabbits = RecipeSearchWrapper.search_recipes("rabbit")
        recipe = rabbits.last

        rabbit_recipe = RecipeSearchWrapper.find_recipe(recipe.uri)

        rabbit_recipe.must_be_kind_of Recipe
      end
    end
  end


end
<file_sep>class Recipe
  attr_reader :uri, :label, :image, :url, :servings, :ingredients, :health_labels, :calories, :fat, :saturated_fat, :mono_fat, :carbs, :protein, :sodium, :fiber, :cholesterol

  def initialize(uri, label, opts = {})

    if label.nil? || label.empty?
      raise ArgumentError.new("The recipe needs a label")
    end

    if uri.nil? || uri.empty?
      raise ArgumentError.new("The recipe needs a URI")
    end

    @uri = uri
    @label = label
    @image = opts["image"]
    @url = opts["url"]
    @servings = opts["servings"]
    @ingredients = opts["ingredients"]
    @health_labels = opts["health_labels"]
    @calories = opts["calories"]
    @fat = opts["fat"]
    @saturated_fat = opts["saturated_fat"]
    @mono_fat = opts["mono_fat"]
    @carbs = opts["carbs"]
    @protein = opts["protein"]
    @sodium = opts["sodium"]
    @fiber = opts["fiber"]
    @cholesterol = opts["cholesterol"]

  end

  def self.from_api(raw_recipe)
    self.new(
      raw_recipe["uri"],
      raw_recipe["label"],
      opts = {}
    )
      opts["image"] = raw_recipe["image"],
      opts["url"] = raw_recipe["url"],
      opts["yield"] = raw_recipe["yield"],
      opts["ingredientLines"] = raw_recipe["ingredientLines"],
      opts["healthLabels"] = raw_recipe["healthLabels"],
      opts["calories"] = raw_recipe["calories"],
      opts["fat"] = raw_recipe["totalNutrients"]["FAT"]["quantity"],
      opts["saturated_fat"] = raw_recipe["totalNutrients"]["FASAT"]["quantity"],
      opts["mono_fat"] = raw_recipe["totalNutrients"]["FAMS"]["quantity"],
      opts["carbs"] = raw_recipe["totalNutrients"]["CHOCDF"]["quantity"],
      opts["protein"] = raw_recipe["totalNutrients"]["PROCNT"]["quantity"],
      opts["sodium"] = raw_recipe["totalNutrients"]["NA"]["quantity"],
      opts["fiber"] = raw_recipe["totalNutrients"]["FIBTG"]["quantity"],
      opts["cholesterol"] = raw_recipe["totalNutrients"]["CHOLE"]["quantity"]

  end

end
<file_sep>require 'test_helper'

describe "recipe" do
  describe "initialize" do
    it "can be created with required values" do
      uri = "test value"
      label = "test recipe label"
      opts = {}
      opts["servings"] = 8

      recipe = Recipe.new(uri, label, opts)

      recipe.label.must_equal label
      recipe.uri.must_equal uri
    end

    it "raises an error if not enough arguments are provided" do
      label = "chicken dinner"
      proc { Recipe.new(label)}.must_raise ArgumentError
    end

    it "accepts optional arguments" do
      label = "Best Label"
      uri = "Uridiculous"
      opts = {
        url: "http:://homestarrunner.com",
        servings: "200"
      }
      recipe = Recipe.new(uri, label, opts)

      recipe.url.must_equal opts["url"]
      recipe.servings.must_equal opts["servings"]

    end
  end
end
<file_sep>require "test_helper"

describe "RecipesController" do
  describe "root" do
    it "shows the root page" do
      get root_path
      must_respond_with :success
    end
  end

  describe "index" do
    it "returns a list of search recipes results" do
      query = "rabbit"
      VCR.use_cassette("recipes") do
        RecipeSearchWrapper.search_recipes(query)

        get query_recipe_path(query: query)
        must_respond_with :success
      end
    end

    it "doesn't return results for a nonsense search term" do
      query = "zzzzzzadsfasdfasdfasdf"
      VCR.use_cassette("recipes") do
        RecipeSearchWrapper.search_recipes(query)

        get query_recipe_path(query: query)
        must_redirect_to root_path
      end
    end
  end

  describe "show" do
    it "displays the recipe page for the selected recipe" do
      VCR.use_cassette("show") do
        uri = "http://www.edamam.com/ontologies/edamam.owl#recipe_b79327d05b8e5b838ad6cfd9576b30b6"
        label = "Chicken Vesuvio"

        recipe = RecipeSearchWrapper.find_recipe(uri)
        recipe.must_be_kind_of Recipe
        get recipe_path(uri: uri, label: label)
        must_respond_with :success
      end
    end

    it "it returns a RecipeError if the recipe uri does not exist" do
      VCR.use_cassette("show") do
        uri = "http://www.edamam.com/ontologies/edamam.owl#recipe_b79327d05b8e5b838ad6cfd9576b30b69999999"

        result = RecipeSearchWrapper.find_recipe(uri)
        result.must_be_kind_of RecipeSearchWrapper::RecipeError
      end
    end

  end


end
<file_sep>
class RecipesController < ApplicationController
  around_action :catch_api_error

  def root
  end

  def index
    @query = params[:query]

    @recipes = RecipeSearchWrapper.search_recipes(@query)
    if @recipes.empty?
      flash[:status] = :failure
      flash[:result_text] = "There are no recipes for #{params[:query]}. Maybe you could write one?"
      redirect_to root_path
    else
      @recipes = @recipes.paginate(:page => params[:page], :per_page => 10)
    end
  end

  def show
    @recipe = RecipeSearchWrapper.find_recipe(params[:uri])

    unless @recipe
      head :not_found
    end
  end

  private
  def catch_api_error
    begin
      # This will run the actual controller action
      # Actually the same yield keyword as in
      # application.html.erb
      yield
    rescue RecipeSearchWrapper::RecipeError => error
      flash[:status] = :failure
      flash[:result_text] = "API call failed: #{error}"
      redirect_back fallback_location: root_path
    end
  end
end
