allrecipes 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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