ratebeer 0.0.8 → 0.1.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -1
- data/lib/ratebeer.rb +6 -7
- data/lib/ratebeer/beer.rb +133 -96
- data/lib/ratebeer/brewery.rb +123 -110
- data/lib/ratebeer/location.rb +30 -20
- data/lib/ratebeer/scraping.rb +6 -3
- data/lib/ratebeer/search.rb +36 -13
- data/lib/ratebeer/style.rb +41 -28
- data/lib/ratebeer/urls.rb +5 -1
- data/spec/lib/ratebeer/beer_spec.rb +15 -15
- data/spec/lib/ratebeer/brewery_spec.rb +25 -18
- data/spec/lib/ratebeer/region_spec.rb +5 -7
- data/spec/lib/ratebeer/search_spec.rb +26 -27
- data/spec/lib/ratebeer_spec.rb +17 -16
- metadata +3 -3
data/lib/ratebeer/location.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
require_relative
|
2
|
-
require_relative
|
3
|
-
require_relative
|
1
|
+
require_relative 'brewery'
|
2
|
+
require_relative 'style'
|
3
|
+
require_relative 'urls'
|
4
4
|
|
5
5
|
module RateBeer
|
6
6
|
class Location
|
@@ -25,41 +25,52 @@ module RateBeer
|
|
25
25
|
#
|
26
26
|
def initialize(id, location_type: nil, name: nil, **options)
|
27
27
|
super
|
28
|
-
if location_type.nil? || !
|
29
|
-
raise ArgumentError.new(
|
30
|
-
|
28
|
+
if location_type.nil? || ![:country, :region].include?(location_type)
|
29
|
+
raise ArgumentError.new('location_type must be supplied and must be '\
|
30
|
+
'either country or region')
|
31
31
|
end
|
32
32
|
@location_type = location_type
|
33
33
|
end
|
34
34
|
|
35
|
+
def doc
|
36
|
+
unless instance_variable_defined?('@doc')
|
37
|
+
@doc = noko_doc(url)
|
38
|
+
validate_location
|
39
|
+
end
|
40
|
+
@doc
|
41
|
+
end
|
42
|
+
|
43
|
+
def heading
|
44
|
+
@heading ||= doc.at_css('.col-lg-9')
|
45
|
+
end
|
46
|
+
|
35
47
|
private
|
36
48
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
def retrieve_details
|
43
|
-
doc = noko_doc(url)
|
44
|
-
heading = doc.css('.col-lg-9').first
|
45
|
-
brewery_info = doc.css('#tabs table')
|
49
|
+
def validate_location
|
50
|
+
if @doc.at_css('h1').text.include? 'n/a'
|
51
|
+
raise PageNotFoundError.new("#{self.class.name} not found - #{id}")
|
52
|
+
end
|
53
|
+
end
|
46
54
|
|
55
|
+
def scrape_name
|
47
56
|
@name = heading.at_css('h1')
|
48
57
|
.text
|
49
58
|
.split('Breweries')
|
50
59
|
.first
|
51
60
|
.strip
|
52
|
-
|
53
|
-
raise PageNotFoundError.new("#{self.class.name} not found - #{id}")
|
54
|
-
end
|
61
|
+
end
|
55
62
|
|
63
|
+
def scrape_num_breweries
|
56
64
|
@num_breweries = heading.at_css('li.active')
|
57
65
|
.text
|
58
66
|
.scan(/Active \((\d*)\)/)
|
59
67
|
.first
|
60
68
|
.first
|
61
69
|
.to_i
|
70
|
+
end
|
62
71
|
|
72
|
+
def scrape_breweries
|
73
|
+
brewery_info = doc.css('#tabs table')
|
63
74
|
@breweries = brewery_info.flat_map.with_index do |tbl, i|
|
64
75
|
status = i == 0 ? 'Active' : 'Out of Business'
|
65
76
|
|
@@ -83,7 +94,6 @@ module RateBeer
|
|
83
94
|
status: status)
|
84
95
|
end
|
85
96
|
end
|
86
|
-
nil
|
87
97
|
end
|
88
98
|
|
89
99
|
# Return URL for page containing information on this location.
|
@@ -97,7 +107,7 @@ module RateBeer
|
|
97
107
|
when :region
|
98
108
|
URI.join(BASE_URL, region_url(id))
|
99
109
|
else
|
100
|
-
raise "invalid location type: #{@location_type
|
110
|
+
raise "invalid location type: #{@location_type}"
|
101
111
|
end
|
102
112
|
end
|
103
113
|
end
|
data/lib/ratebeer/scraping.rb
CHANGED
@@ -16,9 +16,7 @@ module RateBeer
|
|
16
16
|
def self.included(base)
|
17
17
|
base.data_keys.each do |attr|
|
18
18
|
define_method(attr) do
|
19
|
-
unless instance_variable_defined?("@#{attr}")
|
20
|
-
retrieve_details
|
21
|
-
end
|
19
|
+
send("scrape_#{attr}") unless instance_variable_defined?("@#{attr}")
|
22
20
|
instance_variable_get("@#{attr}")
|
23
21
|
end
|
24
22
|
end
|
@@ -60,6 +58,11 @@ module RateBeer
|
|
60
58
|
end
|
61
59
|
end
|
62
60
|
|
61
|
+
# Extracts an ID# from an a element containing a link to an entity.
|
62
|
+
def id_from_link(node)
|
63
|
+
node.attribute('href').value.split('/').last.to_i
|
64
|
+
end
|
65
|
+
|
63
66
|
# Return full details of the scraped entity in a Hash.
|
64
67
|
#
|
65
68
|
def full_details
|
data/lib/ratebeer/search.rb
CHANGED
@@ -6,7 +6,6 @@ require_relative 'scraping'
|
|
6
6
|
require_relative 'urls'
|
7
7
|
|
8
8
|
module RateBeer
|
9
|
-
|
10
9
|
# Stop I18N from enforcing locale, to avoid error message
|
11
10
|
I18n.enforce_available_locales = false
|
12
11
|
|
@@ -29,7 +28,7 @@ module RateBeer
|
|
29
28
|
# a search.
|
30
29
|
#
|
31
30
|
def search(query)
|
32
|
-
s =
|
31
|
+
s = new(query)
|
33
32
|
{ beers: s.beers,
|
34
33
|
breweries: s.breweries }
|
35
34
|
end
|
@@ -41,8 +40,9 @@ module RateBeer
|
|
41
40
|
#
|
42
41
|
# @param [String] query Term to use to search RateBeer
|
43
42
|
#
|
44
|
-
def initialize(query)
|
43
|
+
def initialize(query, scrape_beer_brewers = false)
|
45
44
|
self.query = query
|
45
|
+
@scrape_breweries = scrape_beer_brewers
|
46
46
|
end
|
47
47
|
|
48
48
|
# Setter for query instance variable.
|
@@ -52,6 +52,10 @@ module RateBeer
|
|
52
52
|
@query = fix_query_param(qry)
|
53
53
|
end
|
54
54
|
|
55
|
+
def ==(other)
|
56
|
+
query == other.query
|
57
|
+
end
|
58
|
+
|
55
59
|
def inspect
|
56
60
|
num_beers = @beers && @beers.count || 0
|
57
61
|
num_breweries = @breweries && @breweries.count || 0
|
@@ -71,7 +75,6 @@ module RateBeer
|
|
71
75
|
#
|
72
76
|
def run_search
|
73
77
|
@beers, @breweries = nil
|
74
|
-
doc = post_request(URI.join(BASE_URL, SEARCH_URL), post_params)
|
75
78
|
tables = doc.css('h2').map(&:text).zip(doc.css('table'))
|
76
79
|
beers, breweries = nil
|
77
80
|
tables.each do |(heading, table)|
|
@@ -84,24 +87,44 @@ module RateBeer
|
|
84
87
|
end
|
85
88
|
|
86
89
|
# RateBeer is inconsistent with searching for IPAs. If IPA is in the name
|
87
|
-
# of the beer, replace IPA with India Pale Ale, and add the additional
|
90
|
+
# of the beer, replace IPA with India Pale Ale, and add the additional
|
88
91
|
# results to these results.
|
89
|
-
if query.downcase.include?(
|
90
|
-
alt_query = query.downcase.gsub(
|
92
|
+
if query.downcase.include?(' ipa')
|
93
|
+
alt_query = query.downcase.gsub(' ipa', ' india pale ale')
|
91
94
|
extra_beers = self.class.new(alt_query).run_search.beers
|
92
95
|
@beers = ((@beers || []) + (extra_beers || [])).uniq
|
93
96
|
end
|
94
|
-
|
97
|
+
self
|
95
98
|
end
|
96
99
|
|
97
100
|
alias retrieve_details run_search
|
98
101
|
|
99
102
|
private
|
103
|
+
|
104
|
+
def doc
|
105
|
+
@doc ||= post_request(URI.join(BASE_URL, SEARCH_URL), post_params)
|
106
|
+
end
|
107
|
+
|
108
|
+
def scrape_beers
|
109
|
+
unless instance_variable_defined?('@beers')
|
110
|
+
run_search
|
111
|
+
@beers = @beers.sort_by(&:id)
|
112
|
+
end
|
113
|
+
@beers
|
114
|
+
end
|
115
|
+
|
116
|
+
def scrape_breweries
|
117
|
+
unless instance_variable_defined?('@breweries')
|
118
|
+
run_search
|
119
|
+
@breweries = @breweries.sort_by(&:id)
|
120
|
+
end
|
121
|
+
@breweries
|
122
|
+
end
|
100
123
|
|
101
124
|
# Generate parameters to use in POST request.
|
102
125
|
#
|
103
126
|
def post_params
|
104
|
-
{
|
127
|
+
{ 'BeerName' => @query }
|
105
128
|
end
|
106
129
|
|
107
130
|
# Process breweries table returned in search.
|
@@ -118,9 +141,9 @@ module RateBeer
|
|
118
141
|
def process_breweries_table(table)
|
119
142
|
table.css('tr').map do |row|
|
120
143
|
result = [:id, :name, :location, :url].zip([nil]).to_h
|
121
|
-
result[:name], result[:location] = row.element_children.map
|
122
|
-
fix_characters(x.text)
|
123
|
-
|
144
|
+
result[:name], result[:location] = row.element_children.map do |x|
|
145
|
+
fix_characters(x.text)
|
146
|
+
end
|
124
147
|
result[:url] = row.at_css('a')['href']
|
125
148
|
result[:id] = result[:url].split('/').last.to_i
|
126
149
|
Brewery.new(result[:id], name: result[:name])
|
@@ -168,7 +191,7 @@ module RateBeer
|
|
168
191
|
result[:url] = row.at_css('a')['href']
|
169
192
|
result[:id] = result[:url].split('/').last.to_i
|
170
193
|
b = Beer.new(result[:id], name: result[:name])
|
171
|
-
b.brewery.name
|
194
|
+
b.brewery.name if @scrape_beer_brewers
|
172
195
|
b
|
173
196
|
end
|
174
197
|
|
data/lib/ratebeer/style.rb
CHANGED
@@ -24,16 +24,16 @@ module RateBeer
|
|
24
24
|
|
25
25
|
# Scrape all styles.
|
26
26
|
#
|
27
|
-
# RateBeer provides a styles landing page, with links through to info on
|
28
|
-
# each style listed thereon. This method scrapes style info with links
|
27
|
+
# RateBeer provides a styles landing page, with links through to info on
|
28
|
+
# each style listed thereon. This method scrapes style info with links
|
29
29
|
# to the more detailed pages.
|
30
30
|
#
|
31
|
-
# @param [Boolean] hidden_styles Flag for whether to include hidden
|
31
|
+
# @param [Boolean] hidden_styles Flag for whether to include hidden
|
32
32
|
# styles.
|
33
|
-
# @return [Array<RateBeer::Style>] List of styles with links etc. to
|
33
|
+
# @return [Array<RateBeer::Style>] List of styles with links etc. to
|
34
34
|
# detailed pages
|
35
35
|
#
|
36
|
-
def all_styles(include_hidden=false)
|
36
|
+
def all_styles(include_hidden = false)
|
37
37
|
doc = Scraping.noko_doc(URI.join(BASE_URL, '/beerstyles/'))
|
38
38
|
root = doc.at_css('div.container-fluid table')
|
39
39
|
|
@@ -41,15 +41,15 @@ module RateBeer
|
|
41
41
|
style_node = root.css('.styleGroup')
|
42
42
|
|
43
43
|
styles = style_node.flat_map.with_index do |list, i|
|
44
|
-
list.css('a').map do |x|
|
44
|
+
list.css('a').map do |x|
|
45
45
|
category = categories[i]
|
46
|
-
Style.new(x['href'].split('/').last.to_i, name: x.text).tap
|
47
|
-
s.category = category
|
48
|
-
|
46
|
+
Style.new(x['href'].split('/').last.to_i, name: x.text).tap do |s|
|
47
|
+
s.category = category
|
48
|
+
end
|
49
49
|
end
|
50
50
|
end
|
51
51
|
if include_hidden
|
52
|
-
styles
|
52
|
+
styles + hidden_styles
|
53
53
|
else
|
54
54
|
styles
|
55
55
|
end
|
@@ -57,13 +57,13 @@ module RateBeer
|
|
57
57
|
|
58
58
|
# Scrape hidden style information
|
59
59
|
#
|
60
|
-
# RateBeer has a number of styles not accessible from the "beerstyles"
|
60
|
+
# RateBeer has a number of styles not accessible from the "beerstyles"
|
61
61
|
# landing page. This method scrapes these.
|
62
62
|
#
|
63
63
|
# @return [Array<Hash>] List of hidden styles
|
64
64
|
#
|
65
65
|
def hidden_styles
|
66
|
-
hidden_ids = [40, 41, 57, 59, 66, 67, 68, 69, 70,
|
66
|
+
hidden_ids = [40, 41, 57, 59, 66, 67, 68, 69, 70,
|
67
67
|
75, 83, 99, 104, 106, 116, 119, 120]
|
68
68
|
hidden_ids.map do |id|
|
69
69
|
Style.new(id)
|
@@ -73,32 +73,45 @@ module RateBeer
|
|
73
73
|
|
74
74
|
private
|
75
75
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
#
|
81
|
-
def retrieve_details
|
82
|
-
doc = noko_doc(URI.join(BASE_URL, style_url(id)))
|
83
|
-
root = doc.at_css('.container-fluid')
|
84
|
-
beer_list = noko_doc(URI.join(BASE_URL, style_beers_url(id)))
|
85
|
-
|
86
|
-
if !root.nil?
|
87
|
-
@name = root.at_css('h1').text.strip
|
88
|
-
else
|
89
|
-
raise PageNotFoundError.new("style not found - ##{id}")
|
76
|
+
def doc
|
77
|
+
unless instance_variable_defined?('@doc')
|
78
|
+
@doc = noko_doc(URI.join(BASE_URL, style_url(id)))
|
79
|
+
validate_style
|
90
80
|
end
|
81
|
+
@doc
|
82
|
+
end
|
83
|
+
|
84
|
+
def validate_style
|
85
|
+
raise PageNotFoundError.new("style not found - ##{id}") if root.nil?
|
86
|
+
end
|
87
|
+
|
88
|
+
def root
|
89
|
+
@root ||= doc.at_css('.container-fluid')
|
90
|
+
end
|
91
91
|
|
92
|
+
def beer_list
|
93
|
+
@beer_list = noko_doc(URI.join(BASE_URL, style_beers_url(id)))
|
94
|
+
end
|
95
|
+
|
96
|
+
def scrape_name
|
97
|
+
@name = root.at_css('h1').text.strip
|
98
|
+
end
|
99
|
+
|
100
|
+
def scrape_description
|
92
101
|
@description = root.at_css('#styleDescription').text
|
102
|
+
end
|
103
|
+
|
104
|
+
def scrape_glassware
|
93
105
|
@glassware = root.css('.glassblurb').map { |x| x.text.strip }
|
106
|
+
end
|
94
107
|
|
108
|
+
def scrape_beers
|
95
109
|
@beers = beer_list.css('tr').drop(1).map do |row|
|
96
110
|
cells = row.css('td')
|
97
111
|
url = cells[1].at_css('a')['href']
|
98
|
-
[cells[0].text.to_i, Beer.new(url.split('/').last,
|
112
|
+
[cells[0].text.to_i, Beer.new(url.split('/').last,
|
99
113
|
name: fix_characters(cells[1].text))]
|
100
114
|
end.to_h
|
101
|
-
nil
|
102
115
|
end
|
103
116
|
end
|
104
117
|
end
|
data/lib/ratebeer/urls.rb
CHANGED
@@ -19,11 +19,15 @@ module RateBeer
|
|
19
19
|
end
|
20
20
|
|
21
21
|
# Return URL to info page for brewery with id
|
22
|
-
#
|
23
22
|
def brewery_url(id)
|
24
23
|
"/brewers/a/#{id}/"
|
25
24
|
end
|
26
25
|
|
26
|
+
# Return URL to beer listing page for brewery with id.
|
27
|
+
def brewery_beers_url(id)
|
28
|
+
"/Ratings/Beer/ShowBrewerBeers.asp?BrewerID=#{id}"
|
29
|
+
end
|
30
|
+
|
27
31
|
# Return URL to info page for country with id
|
28
32
|
def country_url(id)
|
29
33
|
"/breweries/a/0/#{id}/"
|
@@ -3,43 +3,43 @@ require 'spec_helper'
|
|
3
3
|
describe RateBeer::Beer do
|
4
4
|
before :all do
|
5
5
|
@valid = RateBeer::Beer.new(1411) # ID for Tennents Lager (sorry...)
|
6
|
-
@retired = RateBeer::Beer.new(
|
7
|
-
@with_name = RateBeer::Beer.new(422, name:
|
6
|
+
@retired = RateBeer::Beer.new(213_225) # ID for BrewDog Vice Bier
|
7
|
+
@with_name = RateBeer::Beer.new(422, name: 'Stone IPA')
|
8
8
|
end
|
9
9
|
|
10
|
-
describe
|
11
|
-
it
|
10
|
+
describe '#new' do
|
11
|
+
it 'creates a beer instance' do
|
12
12
|
expect(@valid).to be_a RateBeer::Beer
|
13
13
|
end
|
14
14
|
|
15
|
-
it
|
15
|
+
it 'requires an ID# as parameter' do
|
16
16
|
expect { RateBeer::Beer.new }.to raise_error(ArgumentError)
|
17
17
|
end
|
18
18
|
|
19
|
-
it
|
19
|
+
it 'accepts a name parameter' do
|
20
20
|
expect(@with_name).to be_a RateBeer::Beer
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
-
describe
|
25
|
-
it
|
26
|
-
expect(@valid.name).to eq
|
24
|
+
describe '#name' do
|
25
|
+
it 'retrieves name details from RateBeer if not present' do
|
26
|
+
expect(@valid.name).to eq 'Tennents Lager'
|
27
27
|
end
|
28
28
|
|
29
|
-
it
|
30
|
-
expect(@with_name.name).to eq
|
29
|
+
it 'uses name details if passed as parameter' do
|
30
|
+
expect(@with_name.name).to eq 'Stone IPA'
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
describe
|
35
|
-
it
|
34
|
+
describe '#retired' do
|
35
|
+
it 'states that beer is retired when this is the case' do
|
36
36
|
expect(@valid.retired).to be false
|
37
37
|
expect(@retired.retired).to be true
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
-
describe
|
42
|
-
it
|
41
|
+
describe '#full_details' do
|
42
|
+
it 'returns full information about the beer' do
|
43
43
|
expect(@valid.full_details).to include(:id,
|
44
44
|
:name,
|
45
45
|
:brewery,
|
@@ -3,51 +3,58 @@ require 'spec_helper'
|
|
3
3
|
describe RateBeer::Brewery do
|
4
4
|
before :all do
|
5
5
|
@valid = RateBeer::Brewery.new(8534) # ID for BrewDog
|
6
|
-
@with_name = RateBeer::Brewery.new(1069, name:
|
6
|
+
@with_name = RateBeer::Brewery.new(1069, name: 'Cantillon Brewery')
|
7
7
|
end
|
8
8
|
|
9
|
-
describe
|
10
|
-
it
|
9
|
+
describe '#new' do
|
10
|
+
it 'creates a brewery instance' do
|
11
11
|
expect(@valid).to be_a RateBeer::Brewery
|
12
12
|
end
|
13
13
|
|
14
|
-
it
|
14
|
+
it 'requires an ID# as parameter' do
|
15
15
|
expect { RateBeer::Brewery.new }.to raise_error(ArgumentError)
|
16
16
|
end
|
17
17
|
|
18
|
-
it
|
18
|
+
it 'accepts a name parameter' do
|
19
19
|
expect(@with_name).to be_a RateBeer::Brewery
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
describe
|
24
|
-
it
|
25
|
-
expect(@valid.name).to eq
|
23
|
+
describe '#name' do
|
24
|
+
it 'retrieves name from RateBeer if not passed as parameter' do
|
25
|
+
expect(@valid.name).to eq 'BrewDog'
|
26
26
|
end
|
27
27
|
|
28
|
-
it
|
29
|
-
expect(@with_name.name).to eq
|
28
|
+
it 'uses name details if passed as parameter' do
|
29
|
+
expect(@with_name.name).to eq 'Cantillon Brewery'
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
-
describe
|
34
|
-
it
|
33
|
+
describe '#beers' do
|
34
|
+
it 'returns a non-empty array of beers produced by the brewery' do
|
35
35
|
expect(@valid.beers).to_not be_empty
|
36
36
|
end
|
37
37
|
|
38
|
-
it
|
38
|
+
it 'returns an array of beer instances' do
|
39
39
|
@valid.beers.each { |b| expect(b).to be_a RateBeer::Beer }
|
40
40
|
end
|
41
41
|
|
42
|
-
it
|
43
|
-
beers = [
|
44
|
-
|
42
|
+
it 'returns a list of beers produced by brewery' do
|
43
|
+
beers = [215_065,
|
44
|
+
98_242,
|
45
|
+
172_237,
|
46
|
+
76_701,
|
47
|
+
178_585,
|
48
|
+
162_521,
|
49
|
+
87_321,
|
50
|
+
118_987,
|
51
|
+
119_594].map { |id| RateBeer::Beer.new(id) }
|
45
52
|
expect(@valid.beers).to include(*beers)
|
46
53
|
end
|
47
54
|
end
|
48
55
|
|
49
|
-
describe
|
50
|
-
it
|
56
|
+
describe '#full_details' do
|
57
|
+
it 'returns full information about the brewery' do
|
51
58
|
expect(@valid.full_details).to include(:id,
|
52
59
|
:name,
|
53
60
|
:url,
|