allrecipes 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e3495f1afe0f65623bc263f180444a4ddfe556b8
4
+ data.tar.gz: 2926ce536d61bb64dc702877426cfcc8798fdc81
5
+ SHA512:
6
+ metadata.gz: 97d5e36c2cabe1fb1199af8de21f9ab5286947465a61855e46a4b8b32b11f881714a7a3bb216ffac4755894f0eb552fdf06dde33a6b7a0f02b675d38ac5e3210
7
+ data.tar.gz: 17ee339ff6056235e139bc4907d9910032965ad39286a7f9c72738481b90a679049d04d762586ce47c54b28c7d75aef331aa3b597f11a03d9164f1228a147a74
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ spec/fixtures/vcr_cassettes/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in allrecipes.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 shivamd
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,148 @@
1
+ # Allrecipes
2
+
3
+ A Ruby interface to the Allrecipes API.
4
+ Allrecipes doesn't have an API, the requests are done using mechanize.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'allrecipes'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install allrecipes
19
+
20
+ ## Usage
21
+
22
+ **Initialize scraper**
23
+ ```ruby
24
+ recipes = Allrecipes.new
25
+ ```
26
+
27
+ **Get all recipes**
28
+ ```ruby
29
+ recipes.all #default sorted by popularity, limit 20.
30
+ ```
31
+
32
+ **Get recipes by course**
33
+ ```ruby
34
+ recipes.course("breakfast") #default sorted by popularity, limit 20.
35
+ ```
36
+ List of courses:
37
+ ```ruby
38
+ [ "appetizer", "bread", "breakfast", "dessert", "drink","fruit", "herbs", "main", "salad", "soup", "meat", "pasta", "seafood", "wholegrains"]
39
+ ```
40
+
41
+ **Get recipes by region**
42
+ ```ruby
43
+ recipes.region("european") #default sorted by popularity, limit 20.
44
+ ```
45
+
46
+ Specific options available
47
+ ```ruby
48
+ recipes.region("french") #default sorted by popularity, limit 20.
49
+ ```
50
+
51
+ List of regions available:
52
+ ```ruby
53
+ [
54
+ "asian"
55
+ "chinese"
56
+ "thai"
57
+ "japanese"
58
+ "filipino"
59
+ "korean"
60
+ "vietnamese"
61
+ "malaysian"
62
+ "pakistani"
63
+ "indonesian"
64
+ "iranian"
65
+ "bangladeshi"
66
+ "indian"
67
+
68
+ "african"
69
+ "african_main"
70
+ "moroccan"
71
+ "south_african"
72
+ "egyptian"
73
+
74
+ "australian"
75
+
76
+ "canadian"
77
+ "vancouver"
78
+ "toronto"
79
+ "quebec"
80
+
81
+ "european"
82
+ "italian"
83
+ "uk"
84
+ "french"
85
+ "greek"
86
+ "eastern_european"
87
+ "german"
88
+ "scandinavian"
89
+ "spanish"
90
+ "portuguese"
91
+ "dutch"
92
+ "austrian"
93
+ "swiss"
94
+ "belgian"
95
+
96
+ "latin_american"
97
+ "mexican"
98
+ "caribbean"
99
+ "south_american"
100
+
101
+ "middle_eastern"
102
+ "lebanese"
103
+ "turkish"
104
+ "israeli"
105
+ ]
106
+ ```
107
+
108
+ **Get recipes by ingredient**
109
+ ```ruby
110
+ recipes.ingredient("apples") #default sorted by relevance, limit 21.
111
+ ```
112
+
113
+ **Sample response**
114
+ ```ruby
115
+ [
116
+ {
117
+ :name => "Lasagna",
118
+ :image => "http://images.media-allrecipes.com/userphotos/250x250/00/03/24/32427.jpg",
119
+ :servings => 12,
120
+ :ingredients =>
121
+ [
122
+ {
123
+ "quantity" => 1.0,
124
+ "unit" => "pound",
125
+ "name" => "ground beef"
126
+ }
127
+ ],
128
+ :directions =>
129
+ [
130
+ {
131
+ "Bring a large pot of lightly salted water to a boil"
132
+ }
133
+ ]
134
+ }
135
+ ]
136
+ ```
137
+
138
+ ## Todo
139
+
140
+ 1. Filters on search (offset, limit & sort_by)
141
+ 2. Complex search with queries such as multiple ingredients, course, time etc.
142
+ 3. Add prep time & cook time to results
143
+ 4. Add nutrional info to results.
144
+ 5. Add rating to results
145
+ 6. Add user photos for recipe to results
146
+ 7. Able to get popular recipes of the day.
147
+ 8. Able to insert recipe URL directly.
148
+ 9. Able to add page URL directly.
@@ -0,0 +1,4 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec)
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'allrecipes/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.add_dependency "mechanize"
8
+ spec.add_dependency "nokogiri"
9
+ spec.add_development_dependency "bundler", "~> 1.6"
10
+ spec.add_development_dependency "rake"
11
+ spec.add_development_dependency "rspec"
12
+ spec.add_development_dependency "webmock"
13
+ spec.add_development_dependency "vcr"
14
+ spec.name = "allrecipes"
15
+ spec.version = Allrecipes::VERSION
16
+ spec.authors = ["shivamd"]
17
+ spec.email = ["daryananis@gmail.com"]
18
+ spec.summary = %q{A Ruby wrapper for allrecipes.com}
19
+ spec.description = %q{A Ruby wrapper for allrecipes.com. A simple interface to scrape recipes from allrecipes.com}
20
+ spec.homepage = "https://github.com/shivamd/allrecipes"
21
+ spec.license = "MIT"
22
+ spec.files = `git ls-files -z`.split("\x0")
23
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
24
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
25
+ spec.require_paths = ["lib"]
26
+ end
@@ -0,0 +1,8 @@
1
+ require "allrecipes/version"
2
+ require "allrecipes/url_helper"
3
+ require "allrecipes/main"
4
+ require "allrecipes/page_parser"
5
+ require "allrecipes/recipe_parser"
6
+ require 'nokogiri'
7
+ require 'mechanize'
8
+
@@ -0,0 +1,47 @@
1
+ class Allrecipes
2
+ $URL= "http://allrecipes.com"
3
+ include URLHelper
4
+
5
+ def initialize
6
+ @agent = Mechanize.new
7
+ end
8
+
9
+ def all(options={})
10
+ page = @agent.get $URL + "/recipes" + "?page=#{options[:page]}&st=#{search_sort_parameter(options[:sort_by])}"
11
+ PageParser.new(page, options).recipes
12
+ end
13
+
14
+ def course(course_type, options={})
15
+ begin
16
+ course_url = get_course_url(course_type) + "?page=#{options[:page]}&st=#{search_sort_parameter(options[:sort_by])}"
17
+ page = @agent.get(course_url)
18
+ PageParser.new(page, options).recipes
19
+ rescue Exception
20
+ raise "Course type doesn't exist"
21
+ end
22
+ end
23
+
24
+ def region(region_type, options={})
25
+ begin
26
+ region_url = get_region_url(region_type) + "?page=#{options[:page]}&st=#{search_sort_parameter(options[:sort_by])}"
27
+ page = @agent.get(region_url)
28
+ PageParser.new(page, options).recipes
29
+ rescue Exception
30
+ raise "Region doesn't exist"
31
+ end
32
+ end
33
+
34
+ def ingredient(name, options={})
35
+ begin
36
+ url = $URL + "/search/?wt=#{name}&page=#{options[:page]}&sb=#{ingredient_sort_parameter(options[:sort_by])}"
37
+ page = @agent.get(url)
38
+ options = options.merge({type: "ingredient"})
39
+ PageParser.new(page, options).recipes
40
+ rescue Exception
41
+ raise "Could not find recipes that include this ingredient"
42
+ end
43
+ end
44
+
45
+
46
+ end
47
+
@@ -0,0 +1,54 @@
1
+ class PageParser
2
+
3
+ def initialize(page, options={})
4
+ @page = page
5
+ @recipes = []
6
+ @options = options
7
+ @limit = options[:limit]
8
+ get_recipes
9
+ end
10
+
11
+ def recipe_info_class
12
+ ".recipe-info"
13
+ end
14
+
15
+ def title_class
16
+ ".title"
17
+ end
18
+
19
+ def recipe_info
20
+ if @options[:type] == "ingredient"
21
+ @page.search(recipe_info_class)
22
+ else
23
+ recipes_grid.search(recipe_info_class)
24
+ end
25
+ end
26
+
27
+ def filtered_recipes
28
+ @limit ? recipe_info.take(@limit) : recipe_info
29
+ end
30
+
31
+ def recipes_grid_class
32
+ ".grid-view"
33
+ end
34
+
35
+ def recipes_grid
36
+ @page.search(recipes_grid_class)[-1]
37
+ end
38
+
39
+ def recipe_link(info)
40
+ info.search(title_class)[0].attributes["href"].value
41
+ end
42
+
43
+ def get_recipes
44
+ filtered_recipes.each do |info|
45
+ recipe_link = recipe_link(info)
46
+ @recipes << RecipeParser.new(recipe_link).recipe
47
+ end
48
+ end
49
+
50
+ def recipes
51
+ @recipes
52
+ end
53
+
54
+ end
@@ -0,0 +1,103 @@
1
+ class RecipeParser
2
+ $BASE_URL = "http://allrecipes.com"
3
+
4
+ def initialize(page)
5
+ @page = agent.get $BASE_URL + page
6
+ @directions = []
7
+ @ingredients = []
8
+ get_ingredients
9
+ get_directions
10
+ end
11
+
12
+ def agent
13
+ Mechanize.new
14
+ end
15
+
16
+ def name
17
+ @page.search(name_id).inner_text
18
+ end
19
+
20
+ def name_id
21
+ "#itemTitle"
22
+ end
23
+
24
+ def image
25
+ @page.search(image_id).first.attributes["src"].value
26
+ end
27
+
28
+ def image_id
29
+ "#imgPhoto"
30
+ end
31
+
32
+ def servings
33
+ @page.search(servings_id).inner_text.gsub(" servings", "").to_i
34
+ end
35
+
36
+ def servings_id
37
+ "#lblYield"
38
+ end
39
+
40
+ def directions_class
41
+ ".directions ol li span"
42
+ end
43
+
44
+ def directions_list
45
+ @page.search(directions_class)
46
+ end
47
+
48
+ def get_directions
49
+ directions_list.each do |direction|
50
+ @directions << direction.text
51
+ end
52
+ end
53
+
54
+ def ingredients_list
55
+ @page.search(ingredients_class)
56
+ end
57
+
58
+ def ingredients_class
59
+ ".fl-ing"
60
+ end
61
+
62
+ def get_ingredients
63
+ ingredients_list.each do |ingredient|
64
+ amount = amount(ingredient)
65
+ ingredient_name= ingredient_name(ingredient)
66
+ add_ingredient_to_list(amount, ingredient_name)
67
+ end
68
+ end
69
+
70
+ def add_ingredient_to_list(amount, ingredient_name)
71
+ if amount && ingredient_name #some recipes have empty ingredients
72
+ quantity,unit = amount.text.split(" ")
73
+ @ingredients << { "quantity" => quantity.to_f, "unit"=>unit, "name" => ingredient_name.text }
74
+ end
75
+ end
76
+
77
+ def amount(ingredient)
78
+ ingredient.search(ingredient_amount_class).children[0]
79
+ end
80
+
81
+ def ingredient_amount_class
82
+ ".ingredient-amount"
83
+ end
84
+
85
+ def ingredient_name(ingredient)
86
+ ingredient.search(ingredient_name_class).children[0]
87
+ end
88
+
89
+ def ingredient_name_class
90
+ ".ingredient-name"
91
+ end
92
+
93
+ def recipe
94
+ {
95
+ name: name,
96
+ image: image,
97
+ servings: servings,
98
+ ingredients: @ingredients,
99
+ directions: @directions
100
+ }
101
+ end
102
+
103
+ end
@@ -0,0 +1,114 @@
1
+ module URLHelper
2
+
3
+ def get_course_url(course_type)
4
+ begin
5
+ "http://allrecipes.com/recipes/" + courses[course_type]
6
+ rescue Exception => e
7
+ puts e.message
8
+ puts e.backtrace.inspect
9
+ end
10
+ end
11
+
12
+ def courses
13
+ #course type and matching URL based on allrecipes.com
14
+ {
15
+ "bread"=> "bread",
16
+ "appetizer"=> "appetizers-and-snacks",
17
+ "breakfast"=> "breakfast-and-brunch",
18
+ "dessert"=> "desserts",
19
+ "drink"=> "drinks",
20
+ "main"=> "main-dish",
21
+ "salad"=> "salad",
22
+ "side"=> "side-dish",
23
+ "soup"=> "soups-stews-and-chili",
24
+ "fruit"=> "fruits-and-vegetables",
25
+ "herbs"=> "herbs-and-spices",
26
+ "meat"=> "meat-and-poultry",
27
+ "pasta"=> "pasta",
28
+ "seafood"=> "seafood",
29
+ "wholegrains"=> "whole-grains"
30
+ }
31
+ end
32
+
33
+ def get_region_url(region_type)
34
+ begin
35
+ "http://allrecipes.com/recipes/world-cuisine/" + regions[region_type]
36
+ rescue Exception => e
37
+ puts e.message
38
+ puts e.backtrace.inspect
39
+ end
40
+ end
41
+
42
+ def regions
43
+ {
44
+ "asian"=> "asian",
45
+ "chinese" => "asian/chinese",
46
+ "thai" => "asian/thai",
47
+ "japanese" => "asian/japanese",
48
+ "filipino" => "asian/filipino",
49
+ "korean" => "asian/korean",
50
+ "vietnamese" => "asian/vietnamese",
51
+ "malaysian" => "asian/malaysian",
52
+ "pakistani" => "asian/pakistani",
53
+ "indonesian" => "asian/indonesian",
54
+ "iranian" => "asian/iranian",
55
+ "bangladeshi" => "asian/bangladeshi",
56
+ "indian" => "asian/indian",
57
+
58
+ "african"=> "african",
59
+ "african_main"=> "african/main-dishes",
60
+ "moroccan"=> "african/moroccan",
61
+ "south_african"=> "african/south-african",
62
+ "egyptian"=> "african/egyptian",
63
+
64
+ "australian"=> "australian-and-new-zealander",
65
+
66
+ "canadian"=> "canadian",
67
+ "vancouver" => "canadian/vancouver",
68
+ "toronto" => "canadian/toronto",
69
+ "quebec" => "canadian/quebec-recipes",
70
+
71
+ "european"=> "european",
72
+ "italian"=> "european/italian",
73
+ "uk"=> "european/uk-and-ireland",
74
+ "french"=> "european/french",
75
+ "greek"=> "european/greek",
76
+ "eastern_european"=> "european/eastern-european",
77
+ "german"=> "european/german",
78
+ "scandinavian"=> "european/scandinavian",
79
+ "spanish"=> "european/spanish",
80
+ "portuguese"=> "european/portuguese",
81
+ "dutch"=> "european/dutch",
82
+ "austrian"=> "european/austrian",
83
+ "swiss"=> "european/swiss",
84
+ "belgian"=> "european/belgian",
85
+
86
+ "latin_american"=> "latin-american",
87
+ "mexican" => "latin-american/mexican",
88
+ "caribbean" => "latin-american/caribbean",
89
+ "south_american" => "latin-american/south-american",
90
+
91
+ "middle_eastern"=> "middle-eastern",
92
+ "lebanese" => "middle-eastern/lebanese",
93
+ "turkish" => "middle-eastern/turkish",
94
+ "israeli" => "middle-eastern/israeli",
95
+
96
+ }
97
+ end
98
+
99
+ def search_sort_parameter(sort_type)
100
+ case sort_type
101
+ when "date" then "n"
102
+ when "title" then "t"
103
+ else "p"
104
+ end
105
+ end
106
+
107
+ def ingredient_sort_parameter(sort_type)
108
+ case sort_type
109
+ when "rating" then "ra"
110
+ when "popularity" then "p"
111
+ else "re"
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,3 @@
1
+ class Allrecipes
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,124 @@
1
+ require 'spec_helper'
2
+ describe Allrecipes do
3
+ subject { Allrecipes.new }
4
+
5
+ describe "#all" do
6
+ context "without options" do
7
+ before do
8
+ VCR.use_cassette "all" do
9
+ @recipes = subject.all
10
+ end
11
+ end
12
+ it "requests the correct url" do
13
+ expect(a_request(:get, "http://allrecipes.com/recipes?page=&st=p")).to have_been_made
14
+ end
15
+ it "count should be 20" do
16
+ expect(@recipes.count).to eq 20
17
+ end
18
+ end
19
+ end
20
+
21
+ describe "#course" do
22
+
23
+ context "without options" do
24
+
25
+ before do
26
+ VCR.use_cassette "course_1" do
27
+ @recipes = subject.course("bread")
28
+ end
29
+ end
30
+
31
+ it "requests the correct url" do
32
+ expect(a_request(:get, "http://allrecipes.com/recipes/bread?page=&st=p")).to have_been_made
33
+ end
34
+
35
+ it "should return 20 recipes" do
36
+ expect(@recipes.count).to eq 20
37
+ end
38
+
39
+ end
40
+
41
+ context "non existent course" do
42
+ let(:nonexistent_course) { -> { subject.course("nonexistent") } }
43
+ it "should give appropriate error message" do
44
+ expect(nonexistent_course).to raise_error("Course type doesn't exist")
45
+ end
46
+ end
47
+
48
+ end
49
+
50
+ describe "#region" do
51
+ context "without options main region" do
52
+ before do
53
+ VCR.use_cassette "region" do
54
+ @recipes = subject.region("asian")
55
+ end
56
+ end
57
+
58
+ it "requests the correct url" do
59
+ expect(a_request(:get, "http://allrecipes.com/recipes/world-cuisine/asian?page=&st=p")).to have_been_made
60
+ end
61
+
62
+ it "should return 20 recipes" do
63
+ expect(@recipes.count).to eq 20
64
+ end
65
+
66
+ end
67
+
68
+ context "without options sub region" do
69
+
70
+ before do
71
+ VCR.use_cassette "sub_region" do
72
+ @recipes = subject.region("chinese")
73
+ end
74
+ end
75
+
76
+ it "requests the correct url" do
77
+ expect(a_request(:get, "http://allrecipes.com/recipes/world-cuisine/asian/chinese?page=&st=p")).to have_been_made
78
+ end
79
+
80
+ it "should return 20 recipes" do
81
+ expect(@recipes.count).to eq 20
82
+ end
83
+
84
+ end
85
+
86
+ context "non existent region" do
87
+ let(:nonexistent_region) { -> { subject.region("nonexistent") } }
88
+ it "should give appropriate error message" do
89
+ expect(nonexistent_region).to raise_error("Region doesn't exist")
90
+ end
91
+ end
92
+ end
93
+
94
+ describe "#ingredient" do
95
+ context "without options" do
96
+ before do
97
+ VCR.use_cassette "ingredients" do
98
+ @recipes = subject.ingredient("apples")
99
+ end
100
+ end
101
+
102
+ it "requests the correct url" do
103
+ expect(a_request(:get, "http://allrecipes.com/search/?wt=apples&page=&sb=re")).to have_been_made
104
+ end
105
+
106
+ it "should return 21 recipes" do
107
+ expect(@recipes.count).to eq 21
108
+ end
109
+
110
+ its "recipes should include apple" do
111
+ expect(@recipes.first[:ingredients].select{ |ingredient| ingredient["name"].include?("apples") }.count).to eq 1
112
+ end
113
+
114
+ end
115
+
116
+ context "no recipes for ingredient" do
117
+ let(:nonexistent_ingredient) { -> { subject.ingredient("nonexistent") } }
118
+ it "should give appropriate error message" do
119
+ expect(nonexistent_ingredient).to raise_error("Could not find recipes that include this ingredient")
120
+ end
121
+ end
122
+ end
123
+
124
+ end
@@ -0,0 +1,128 @@
1
+ {
2
+ {
3
+ "name": "Ravioli",
4
+ "image": "http://placehold.it/350x150",
5
+ servings: 5,
6
+ ingredients: [],
7
+ directions: []
8
+ },
9
+ {
10
+ "name": "Ravioli",
11
+ "image": "http://placehold.it/350x150",
12
+ servings: 5,
13
+ ingredients: [],
14
+ directions: []
15
+ },
16
+ {
17
+ "name": "Ravioli",
18
+ "image": "http://placehold.it/350x150",
19
+ servings: 5,
20
+ ingredients: [],
21
+ directions: []
22
+ },
23
+ {
24
+ "name": "Ravioli",
25
+ "image": "http://placehold.it/350x150",
26
+ servings: 5,
27
+ ingredients: [],
28
+ directions: []
29
+ },
30
+ {
31
+ "name": "Ravioli",
32
+ "image": "http://placehold.it/350x150",
33
+ servings: 5,
34
+ ingredients: [],
35
+ directions: []
36
+ },
37
+ {
38
+ "name": "Ravioli",
39
+ "image": "http://placehold.it/350x150",
40
+ servings: 5,
41
+ ingredients: [],
42
+ directions: []
43
+ },
44
+ {
45
+ "name": "Ravioli",
46
+ "image": "http://placehold.it/350x150",
47
+ servings: 5,
48
+ ingredients: [],
49
+ directions: []
50
+ },
51
+ {
52
+ "name": "Ravioli",
53
+ "image": "http://placehold.it/350x150",
54
+ servings: 5,
55
+ ingredients: [],
56
+ directions: []
57
+ },
58
+ {
59
+ "name": "Ravioli",
60
+ "image": "http://placehold.it/350x150",
61
+ servings: 5,
62
+ ingredients: [],
63
+ directions: []
64
+ },
65
+ {
66
+ "name": "Ravioli",
67
+ "image": "http://placehold.it/350x150",
68
+ servings: 5,
69
+ ingredients: [],
70
+ directions: []
71
+ },
72
+ {
73
+ "name": "Ravioli",
74
+ "image": "http://placehold.it/350x150",
75
+ servings: 5,
76
+ ingredients: [],
77
+ directions: []
78
+ },
79
+ {
80
+ "name": "Ravioli",
81
+ "image": "http://placehold.it/350x150",
82
+ servings: 5,
83
+ ingredients: [],
84
+ directions: []
85
+ },
86
+ {
87
+ "name": "Ravioli",
88
+ "image": "http://placehold.it/350x150",
89
+ servings: 5,
90
+ ingredients: [],
91
+ directions: []
92
+ },
93
+ {
94
+ "name": "Ravioli",
95
+ "image": "http://placehold.it/350x150",
96
+ servings: 5,
97
+ ingredients: [],
98
+ directions: []
99
+ },
100
+ {
101
+ "name": "Ravioli",
102
+ "image": "http://placehold.it/350x150",
103
+ servings: 5,
104
+ ingredients: [],
105
+ directions: []
106
+ },
107
+ {
108
+ "name": "Ravioli",
109
+ "image": "http://placehold.it/350x150",
110
+ servings: 5,
111
+ ingredients: [],
112
+ directions: []
113
+ },
114
+ {
115
+ "name": "Ravioli",
116
+ "image": "http://placehold.it/350x150",
117
+ servings: 5,
118
+ ingredients: [],
119
+ directions: []
120
+ },
121
+ {
122
+ "name": "Ravioli",
123
+ "image": "http://placehold.it/350x150",
124
+ servings: 5,
125
+ ingredients: [],
126
+ directions: []
127
+ }
128
+ }
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+ describe PageParser do
3
+ before do
4
+ VCR.use_cassette "page" do
5
+ url = "http://allrecipes.com/recipes"
6
+ page = Mechanize.new.get(url)
7
+ @parser = PageParser.new(page)
8
+ end
9
+ end
10
+
11
+ it "should have the right amount of recipes" do
12
+ expect(@parser.recipes.count).to eq 20
13
+ end
14
+
15
+ end
@@ -0,0 +1,230 @@
1
+ require 'spec_helper'
2
+ describe Allrecipes do
3
+ subject { Allrecipes.new }
4
+ describe "options" do
5
+ context "limit" do
6
+ describe "valid limit" do
7
+ before do
8
+ VCR.use_cassette "valid_limit" do
9
+ @all_recipes = subject.all({limit: 10})
10
+ @region_recipes = subject.region("asian", {limit: 10})
11
+ @course_recipes = subject.course("bread", {limit: 10})
12
+ @ingredient_recipes = subject.ingredient("apples", {limit: 10})
13
+ end
14
+ end
15
+
16
+ it "should restrict recipes to 10" do
17
+ expect(@all_recipes.count).to eq 10
18
+ expect(@region_recipes.count).to eq 10
19
+ expect(@course_recipes.count).to eq 10
20
+ expect(@ingredient_recipes.count).to eq 10
21
+ end
22
+ end
23
+ end
24
+
25
+ context "page" do
26
+ describe "valid page" do
27
+ before do
28
+ VCR.use_cassette "valid_pages" do
29
+ @all_recipes = subject.all({page: 2})
30
+ @all_recipes_initial = subject.all
31
+
32
+ @region_recipes = subject.region("asian", {page: 3})
33
+ @region_recipes_initial = subject.region("asian")
34
+
35
+ @course_recipes = subject.course("bread", {page: 4})
36
+ @course_recipes_initial = subject.course("bread")
37
+
38
+ @ingredient_recipes = subject.ingredient("apples", {page: 5})
39
+ @ingredient_recipes_intiial = subject.ingredient("apples")
40
+ end
41
+ end
42
+
43
+ it "should call the correct urls" do
44
+ expect(a_request(:get, "http://allrecipes.com/recipes?page=2&st=p")).to have_been_made
45
+ expect(a_request(:get, "http://allrecipes.com/recipes/world-cuisine/asian?page=3&st=p")).to have_been_made
46
+ expect(a_request(:get, "http://allrecipes.com/recipes/bread?page=4&st=p")).to have_been_made
47
+ expect(a_request(:get, "http://allrecipes.com/search/?wt=apples&page=5&sb=re")).to have_been_made
48
+ end
49
+
50
+ its "results should be different from the initial page" do
51
+ expect(@all_recipes).to_not eq @all_recipes_initial
52
+ expect(@region_recipes).to_not eq @region_recipes_initial
53
+ expect(@course_recipes).to_not eq @course_recipes_initial
54
+ expect(@ingredient_recipes).to_not eq @ingredient_recipes_initial
55
+ end
56
+ end
57
+
58
+ describe "invalid page" do
59
+ before do
60
+ VCR.use_cassette "invalid page" do
61
+ @all_recipes = subject.all({ page: 10000 })
62
+ @region_recipes = subject.region("asian", { page: 10000 })
63
+ @course_recipes = subject.course("bread", { page: 10000 })
64
+ @ingredient_recipes = subject.ingredient("apples", { page: 10000 })
65
+ end
66
+ end
67
+
68
+ it "should call correct url" do
69
+ expect(a_request(:get, "http://allrecipes.com/recipes?page=10000&st=p")).to have_been_made
70
+ expect(a_request(:get, "http://allrecipes.com/recipes/world-cuisine/asian?page=10000&st=p")).to have_been_made
71
+ expect(a_request(:get, "http://allrecipes.com/recipes/bread?page=10000&st=p")).to have_been_made
72
+ expect(a_request(:get, "http://allrecipes.com/search/?wt=apples&page=10000&sb=re")).to have_been_made
73
+ end
74
+
75
+ it "should return an empty array" do
76
+ expect(@all_recipes).to eq []
77
+ expect(@region_recipes).to eq []
78
+ expect(@course_recipes).to eq []
79
+ expect(@ingredient_recipes).to eq []
80
+ end
81
+ end
82
+ end
83
+
84
+ context "sort by" do
85
+ describe "valid sort" do
86
+ before do
87
+ VCR.use_cassette "default search" do
88
+ @all_recipes_initial = subject.all
89
+ @region_recipes_initial = subject.region("asian")
90
+ @course_recipes_initial = subject.course("bread")
91
+ @ingredient_recipes_initial = subject.ingredient("apples")
92
+ end
93
+ end
94
+ context "popularity" do
95
+ before do
96
+ VCR.use_cassette "popularity_sort" do
97
+ @all_recipes = subject.all({ sort_by: "popularity" })
98
+
99
+ @region_recipes = subject.region("asian", { sort_by: "popularity"})
100
+ @course_recipes = subject.course("bread", { sort_by: "popularity"})
101
+ @ingredient_recipes = subject.ingredient("apples", { sort_by: "popularity"})
102
+ end
103
+ end
104
+
105
+ it "should call the correct URLs" do
106
+ expect(a_request(:get, "http://allrecipes.com/recipes?page=&st=p")).to have_been_made.times(2)
107
+ expect(a_request(:get, "http://allrecipes.com/recipes/world-cuisine/asian?page=&st=p")).to have_been_made.times(2)
108
+ expect(a_request(:get, "http://allrecipes.com/recipes/bread?page=&st=p")).to have_been_made.times(2)
109
+ expect(a_request(:get, "http://allrecipes.com/search/?wt=apples&page=&sb=re")).to have_been_made
110
+
111
+ end
112
+
113
+ it "should have the correct count of recipes" do
114
+ expect(@all_recipes.count).to eq 20
115
+ expect(@region_recipes.count).to eq 20
116
+ expect(@course_recipes.count).to eq 20
117
+ expect(@ingredient_recipes.count).to eq 21
118
+ end
119
+
120
+ its "default search should be popularity" do
121
+ expect(@all_recipes).to eq @all_recipes_initial
122
+ expect(@region_recipes).to eq @region_recipes_initial
123
+ expect(@course_recipes).to eq @course_recipes_initial
124
+ end
125
+
126
+ it "should search for recipes based on ingredients sorted by popularity" do
127
+ expect(@ingredient_recipes).to_not eq @ingredient_recipes_initial
128
+ end
129
+ end
130
+
131
+ context "date" do
132
+ before do
133
+ VCR.use_cassette "date_sort" do
134
+ @all_recipes = subject.all({ sort_by: "date" })
135
+
136
+ @region_recipes = subject.region("asian", { sort_by: "date"})
137
+ @course_recipes = subject.course("bread", { sort_by: "date"})
138
+ end
139
+ end
140
+
141
+ it "should call the correct URLs" do
142
+ expect(a_request(:get, "http://allrecipes.com/recipes?page=&st=n")).to have_been_made
143
+ expect(a_request(:get, "http://allrecipes.com/recipes/world-cuisine/asian?page=&st=n")).to have_been_made
144
+ expect(a_request(:get, "http://allrecipes.com/recipes/bread?page=&st=n")).to have_been_made
145
+ end
146
+
147
+ it "should have the correct count of recipes" do
148
+ expect(@all_recipes.count).to eq 20
149
+ expect(@region_recipes.count).to eq 20
150
+ expect(@course_recipes.count).to eq 20
151
+ end
152
+
153
+ it "should be different than default search" do
154
+ expect(@all_recipes).to_not eq @all_recipes_initial
155
+ expect(@region_recipes).to_not eq @region_recipes_initial
156
+ expect(@course_recipes).to_not eq @course_recipes_initial
157
+ end
158
+ end
159
+ context "title" do
160
+ before do
161
+ VCR.use_cassette "title_sort" do
162
+ @all_recipes = subject.all({ sort_by: "title" })
163
+
164
+ @region_recipes = subject.region("asian", { sort_by: "title"})
165
+ @course_recipes = subject.course("bread", { sort_by: "title"})
166
+ end
167
+ end
168
+
169
+ it "should call the correct URLs" do
170
+ expect(a_request(:get, "http://allrecipes.com/recipes?page=&st=t")).to have_been_made
171
+ expect(a_request(:get, "http://allrecipes.com/recipes/world-cuisine/asian?page=&st=t")).to have_been_made
172
+ expect(a_request(:get, "http://allrecipes.com/recipes/bread?page=&st=t")).to have_been_made
173
+ end
174
+
175
+ it "should have the correct count of recipes" do
176
+ expect(@all_recipes.count).to eq 20
177
+ expect(@region_recipes.count).to eq 20
178
+ expect(@course_recipes.count).to eq 20
179
+ end
180
+
181
+ it "should be different than default search" do
182
+ expect(@all_recipes).to_not eq @all_recipes_initial
183
+ expect(@region_recipes).to_not eq @region_recipes_initial
184
+ expect(@course_recipes).to_not eq @course_recipes_initial
185
+ end
186
+ end
187
+ context "Relevance for ingredient search" do
188
+ before do
189
+ VCR.use_cassette "relevance_sort" do
190
+ @ingredient_recipes = subject.ingredient("apples",{ sort_by: "relevance" })
191
+ end
192
+ end
193
+
194
+ it "should call the correct URL" do
195
+ expect(a_request(:get, "http://allrecipes.com/search/?wt=apples&page=&sb=re")).to have_been_made.times(2)
196
+ end
197
+
198
+ it "should have the correct count of recipes" do
199
+ expect(@ingredient_recipes.count).to eq 21
200
+ end
201
+
202
+ it "should be the same as default search" do
203
+ recipe_names = @ingredient_recipes.map{ |ing| ing[:name] }.sort
204
+ initial_recipe_names = @ingredient_recipes_initial.map{ |ing| ing[:name] }.sort
205
+ expect(recipe_names).to eq initial_recipe_names@ingredient_recipes_initial
206
+ end
207
+ end
208
+ context "Rating sort for ingredient search" do
209
+ before do
210
+ VCR.use_cassette "rating_sort" do
211
+ @ingredient_recipes = subject.ingredient("apples",{ sort_by: "rating" })
212
+ end
213
+ end
214
+
215
+ it "should call the correct URL" do
216
+ expect(a_request(:get, "http://allrecipes.com/search/?wt=apples&page=&sb=ra")).to have_been_made
217
+ end
218
+
219
+ it "should have the correct count of recipes" do
220
+ expect(@ingredient_recipes.count).to eq 21
221
+ end
222
+
223
+ it "should be different from default search" do
224
+ expect(@ingredient_recipes).to_not eq @ingredient_recipes_initial
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+ describe RecipeParser do
3
+ before do
4
+ VCR.use_cassette "recipe" do
5
+ url = "/Recipe/Worlds-Best-Lasagna/"
6
+ @recipe = RecipeParser.new(url).recipe
7
+ end
8
+ end
9
+
10
+ it "should have the right name" do
11
+ expect(@recipe[:name]).to eq "World's Best Lasagna"
12
+ end
13
+
14
+ it "should have the right image url" do
15
+ expect(@recipe[:image]).to eq "http://images.media-allrecipes.com/userphotos/250x250/00/03/24/32427.jpg"
16
+ end
17
+
18
+ it "should have the right amount of servings" do
19
+ expect(@recipe[:servings]).to eq 12
20
+ end
21
+
22
+ context "ingredients" do
23
+
24
+ it "should have the right amount of ingredients" do
25
+ expect(@recipe[:ingredients].count).to eq 21
26
+ end
27
+ it "should have the right quantity" do
28
+ expect(@recipe[:ingredients].first["quantity"]).to eq 1
29
+ end
30
+ it "should have the right unit" do
31
+ expect(@recipe[:ingredients].first["unit"]).to eq "pound"
32
+ end
33
+ it "should have the right name" do
34
+ expect(@recipe[:ingredients].first["name"]).to eq "sweet Italian sausage"
35
+ end
36
+
37
+ end
38
+
39
+ it "should have the right amount of directions" do
40
+ expect(@recipe[:directions].count).to eq 5
41
+ end
42
+
43
+ it "should return a formatted recipe" do
44
+ expected_output = {
45
+ name: @recipe[:name],
46
+ image: @recipe[:image],
47
+ servings: @recipe[:servings],
48
+ ingredients: @recipe[:ingredients],
49
+ directions: @recipe[:directions]
50
+ }
51
+ expect(@recipe).to eq expected_output
52
+ end
53
+ end
@@ -0,0 +1,18 @@
1
+ require 'allrecipes'
2
+ require 'vcr'
3
+ require 'webmock/rspec'
4
+
5
+ VCR.configure do |c|
6
+ c.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
7
+ c.hook_into :webmock
8
+ end
9
+
10
+ def fixture_path
11
+ File.expand_path('../fixtures', __FILE__)
12
+ end
13
+
14
+ def fixture(file)
15
+ File.new(fixture_path + '/' + file)
16
+ end
17
+
18
+
metadata ADDED
@@ -0,0 +1,167 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: allrecipes
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - shivamd
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: mechanize
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: nokogiri
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.6'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: webmock
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: vcr
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: A Ruby wrapper for allrecipes.com. A simple interface to scrape recipes
112
+ from allrecipes.com
113
+ email:
114
+ - daryananis@gmail.com
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - .gitignore
120
+ - Gemfile
121
+ - LICENSE.txt
122
+ - README.md
123
+ - Rakefile
124
+ - allrecipes.gemspec
125
+ - lib/allrecipes.rb
126
+ - lib/allrecipes/main.rb
127
+ - lib/allrecipes/page_parser.rb
128
+ - lib/allrecipes/recipe_parser.rb
129
+ - lib/allrecipes/url_helper.rb
130
+ - lib/allrecipes/version.rb
131
+ - spec/allrecipes_spec.rb
132
+ - spec/fixtures/recipe.json
133
+ - spec/page_parser_spec.rb
134
+ - spec/recipe_filter_spec.rb
135
+ - spec/recipe_parser_spec.rb
136
+ - spec/spec_helper.rb
137
+ homepage: https://github.com/shivamd/allrecipes
138
+ licenses:
139
+ - MIT
140
+ metadata: {}
141
+ post_install_message:
142
+ rdoc_options: []
143
+ require_paths:
144
+ - lib
145
+ required_ruby_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - '>='
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ required_rubygems_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - '>='
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ requirements: []
156
+ rubyforge_project:
157
+ rubygems_version: 2.0.6
158
+ signing_key:
159
+ specification_version: 4
160
+ summary: A Ruby wrapper for allrecipes.com
161
+ test_files:
162
+ - spec/allrecipes_spec.rb
163
+ - spec/fixtures/recipe.json
164
+ - spec/page_parser_spec.rb
165
+ - spec/recipe_filter_spec.rb
166
+ - spec/recipe_parser_spec.rb
167
+ - spec/spec_helper.rb