beerdb-models 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gemtest +0 -0
- data/HISTORY.md +3 -0
- data/Manifest.txt +25 -0
- data/README.md +97 -0
- data/Rakefile +31 -0
- data/lib/beerdb/deleter.rb +27 -0
- data/lib/beerdb/models.rb +103 -0
- data/lib/beerdb/models/beer.rb +201 -0
- data/lib/beerdb/models/brand.rb +62 -0
- data/lib/beerdb/models/brewery.rb +256 -0
- data/lib/beerdb/models/forward.rb +46 -0
- data/lib/beerdb/models/tag.rb +13 -0
- data/lib/beerdb/models/world/city.rb +14 -0
- data/lib/beerdb/models/world/country.rb +14 -0
- data/lib/beerdb/models/world/region.rb +14 -0
- data/lib/beerdb/reader.rb +219 -0
- data/lib/beerdb/reader_file.rb +62 -0
- data/lib/beerdb/reader_zip.rb +112 -0
- data/lib/beerdb/schema.rb +195 -0
- data/lib/beerdb/serializers/beer.rb +45 -0
- data/lib/beerdb/serializers/brewery.rb +46 -0
- data/lib/beerdb/stats.rb +27 -0
- data/lib/beerdb/version.rb +21 -0
- data/test/helper.rb +63 -0
- data/test/test_fixture_matchers.rb +85 -0
- data/test/test_values.rb +240 -0
- metadata +118 -0
@@ -0,0 +1,62 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module BeerDb
|
4
|
+
|
5
|
+
## todo: "old" classic reader - rename to FileReader ?? why? why not?
|
6
|
+
|
7
|
+
class Reader < ReaderBase
|
8
|
+
|
9
|
+
def initialize( include_path, opts = {} )
|
10
|
+
@include_path = include_path
|
11
|
+
end
|
12
|
+
|
13
|
+
def create_fixture_reader( name )
|
14
|
+
path = "#{@include_path}/#{name}.txt"
|
15
|
+
|
16
|
+
logger.info "parsing data (setup) '#{name}' (#{path})..."
|
17
|
+
|
18
|
+
FixtureReader.from_file( path )
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_beers_reader( name, more_attribs={} )
|
22
|
+
real_name = name_to_real_name( name )
|
23
|
+
|
24
|
+
path = "#{@include_path}/#{real_name}.txt"
|
25
|
+
|
26
|
+
logger.info "parsing data (beers) '#{name}' (#{path})..."
|
27
|
+
|
28
|
+
ValuesReader.from_file( path, more_attribs )
|
29
|
+
## ValuesReaderV2.new( name, @include_path, more_attribs )
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_breweries_reader( name, more_attribs={} )
|
33
|
+
real_name = name_to_real_name( name )
|
34
|
+
|
35
|
+
path = "#{@include_path}/#{real_name}.txt"
|
36
|
+
|
37
|
+
logger.info "parsing data (breweries) '#{name}' (#{path})..."
|
38
|
+
|
39
|
+
ValuesReader.from_file( path, more_attribs )
|
40
|
+
## ValuesReaderV2.new( name, @include_path, more_attribs )
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def name_to_real_name( name )
|
46
|
+
# map name to real_name path
|
47
|
+
# name might include !/ for virtual path (gets cut off)
|
48
|
+
# e.g. at-austria!/w-wien/beers becomse w-wien/beers
|
49
|
+
pos = name.index( '!/')
|
50
|
+
if pos.nil?
|
51
|
+
name # not found; real path is the same as name
|
52
|
+
else
|
53
|
+
# cut off everything until !/ e.g.
|
54
|
+
# at-austria!/w-wien/beers becomes
|
55
|
+
# w-wien/beers
|
56
|
+
name[ (pos+2)..-1 ]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end # class Reader
|
61
|
+
end # module BeerDb
|
62
|
+
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module BeerDb
|
4
|
+
|
5
|
+
|
6
|
+
class ZipReader < ReaderBase
|
7
|
+
|
8
|
+
def initialize( name, include_path, opts = {} )
|
9
|
+
|
10
|
+
## todo/fix: make include_path an opts (included in opts?) - why? why not??
|
11
|
+
|
12
|
+
path = "#{include_path}/#{name}.zip"
|
13
|
+
## todo: check if zip exists
|
14
|
+
|
15
|
+
@zip_file = Zip::File.open( path ) ## NOTE: do NOT create if file is missing; let it crash
|
16
|
+
|
17
|
+
### allow prefix (path) in name
|
18
|
+
### e.g. assume all files relative to setup manifest
|
19
|
+
## e.g. at-austria-master/setups/all.txt or
|
20
|
+
## be-belgium-master/setups/all.txt
|
21
|
+
## for
|
22
|
+
## setups/all.txt
|
23
|
+
###
|
24
|
+
## will get (re)set w/ fixture/setup reader
|
25
|
+
##
|
26
|
+
## todo/fix: change/rename to @relative_path ?? - why? why not?
|
27
|
+
@zip_prefix = ''
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
def close
|
32
|
+
## todo/check: add a close method - why? why not ???
|
33
|
+
@zip_file.close
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def create_fixture_reader( name )
|
38
|
+
## e.g. pass in => setups/all or setups/test etc. e.g. w/o .txt extension
|
39
|
+
query = "**/#{name}.txt"
|
40
|
+
|
41
|
+
## note: returns an array of Zip::Entry
|
42
|
+
candidates = @zip_file.glob( query )
|
43
|
+
pp candidates
|
44
|
+
|
45
|
+
## use first candidates entry as match
|
46
|
+
## todo/fix: issue warning if more than one entries/matches!!
|
47
|
+
|
48
|
+
## get fullpath e.g. at-austria-master/setups/all.txt
|
49
|
+
path = candidates[0].name
|
50
|
+
logger.debug " zip entry path >>#{path}<<"
|
51
|
+
|
52
|
+
## cut-off at-austria-master/ NOTE: includes trailing slash (if present)
|
53
|
+
## logger.debug " path.size #{path.size} >>#{path}<<"
|
54
|
+
## logger.debug " name.size #{name.size+4} >>#{name}<<"
|
55
|
+
|
56
|
+
## note: add +4 for extension (.txt)
|
57
|
+
@zip_prefix = path[ 0...(path.size-(name.size+4)) ]
|
58
|
+
logger.debug " zip entry prefix >>#{@zip_prefix}<<"
|
59
|
+
|
60
|
+
logger.info "parsing data in zip '#{name}' (#{path})..."
|
61
|
+
|
62
|
+
FixtureReader.from_zip( @zip_file, path )
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def create_beers_reader( name, more_attribs={} )
|
67
|
+
path = name_to_zip_entry_path( name )
|
68
|
+
|
69
|
+
logger.debug "parsing data (beers) in zip '#{name}' (#{path})..."
|
70
|
+
|
71
|
+
ValuesReader.from_zip( @zip_file, path, more_attribs )
|
72
|
+
end
|
73
|
+
|
74
|
+
def create_breweries_reader( name, more_attribs={} )
|
75
|
+
path = name_to_zip_entry_path( name )
|
76
|
+
|
77
|
+
logger.debug "parsing data (breweries) in zip '#{name}' (#{path})..."
|
78
|
+
|
79
|
+
ValuesReader.from_zip( @zip_file, path, more_attribs )
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def path_to_real_path( path )
|
85
|
+
# map name to name_real_path
|
86
|
+
# name might include !/ for virtual path (gets cut off)
|
87
|
+
# e.g. at-austria!/w-wien/beers becomse w-wien/beers
|
88
|
+
pos = path.index( '!/')
|
89
|
+
if pos.nil?
|
90
|
+
path # not found; real path is the same as name
|
91
|
+
else
|
92
|
+
# cut off everything until !/ e.g.
|
93
|
+
# at-austria!/w-wien/beers becomes
|
94
|
+
# w-wien/beers
|
95
|
+
path[ (pos+2)..-1 ]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def name_to_zip_entry_path( name )
|
100
|
+
path = "#{name}.txt"
|
101
|
+
|
102
|
+
real_path = path_to_real_path( path )
|
103
|
+
|
104
|
+
# NOTE: add possible zip entry prefix path
|
105
|
+
# (if present includes trailing slash e.g. /)
|
106
|
+
entry_path = "#{@zip_prefix}#{real_path}"
|
107
|
+
entry_path
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
end # class ZipReader
|
112
|
+
end # module BeerDb
|
@@ -0,0 +1,195 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module BeerDb
|
4
|
+
|
5
|
+
|
6
|
+
class CreateDb < ActiveRecord::Migration
|
7
|
+
|
8
|
+
def up
|
9
|
+
|
10
|
+
create_table :beers do |t|
|
11
|
+
t.string :key, :null => false # import/export key
|
12
|
+
t.string :title, :null => false
|
13
|
+
t.string :synonyms # comma separated list of synonyms
|
14
|
+
|
15
|
+
t.string :web # optional url link (e.g. )
|
16
|
+
t.integer :since # optional year (e.g. 1896)
|
17
|
+
|
18
|
+
# t.boolean :bottle, :null => false, :default => false # Flaschenbier
|
19
|
+
# t.boolean :draft, :null => false, :default => false # Fassbier
|
20
|
+
## todo: check seasonal is it proper english?
|
21
|
+
t.boolean :seasonal, :null => false, :default => false # all year or just eg. Festbier/Oktoberfest Special
|
22
|
+
t.boolean :limited, :null => false, :default => false # one year or season only
|
23
|
+
## todo: add microbrew/brewpub flag?
|
24
|
+
#### t.boolean :brewpub, :null => false, :default => false
|
25
|
+
|
26
|
+
## add t.boolean :lite flag ??
|
27
|
+
t.decimal :kcal # kcal/100ml e.g. 45.0 kcal/100ml
|
28
|
+
|
29
|
+
## check: why decimal and not float?
|
30
|
+
t.decimal :abv # Alcohol by volume (abbreviated as ABV, abv, or alc/vol) e.g. 4.9 %
|
31
|
+
t.decimal :og # malt extract (original gravity) in plato
|
32
|
+
t.integer :srm # color in srm
|
33
|
+
t.integer :ibu # bitterness in ibu
|
34
|
+
|
35
|
+
### fix/todo: add bitterness field
|
36
|
+
|
37
|
+
# renamed - old field names
|
38
|
+
## t.decimal :plato # stammwuerze / gravity in plato scale (e.g. °P) e.g. 12.6° - todo: use a different field name e.g. just p or gravity?
|
39
|
+
## t.integer :color # beer color in Standard Reference Method (SRM)
|
40
|
+
|
41
|
+
# see en.wikipedia.org/wiki/Plato_scale#Colour
|
42
|
+
|
43
|
+
# SRM/Lovibond | Example | Beer color | EBC
|
44
|
+
# ---------------------------------------------------------------
|
45
|
+
# 2 | Pale lager, Witbier, Pilsener, Berliner Weisse | #F8F753 | 4
|
46
|
+
# 3 | Maibock, Blonde Ale | #F6F513 | 6
|
47
|
+
# 4 | Weissbier | #ECE61A | 8
|
48
|
+
# 6 | American Pale Ale, India Pale Ale | #D5BC26 | 12
|
49
|
+
# 8 | Weissbier, Saison | #BF923B | 16
|
50
|
+
# 10 | English Bitter, ESB | #BF813A | 20
|
51
|
+
# 13 | Biere de Garde, Double IPA | #BC6733 | 26
|
52
|
+
# 17 | Dark lager, Vienna lager, Marzen, Amber Ale | #8D4C32 | 33
|
53
|
+
# 20 | Brown Ale, Bock, Dunkel, Dunkelweizen | #5D341A | 39
|
54
|
+
# 24 | Irish Dry Stout, Doppelbock, Porter | #261716 | 47
|
55
|
+
# 29 | Stout | #0F0B0A | 57
|
56
|
+
# 35 | Foreign Stout, Baltic Porter | #080707 | 69
|
57
|
+
# 40+ | Imperial Stout | #030403 | 79
|
58
|
+
|
59
|
+
t.references :brewery # optional (for now)
|
60
|
+
t.references :brand # optional (for now)
|
61
|
+
|
62
|
+
|
63
|
+
## todo: add categories e.g. (A/B/C, 1/2/3, main/major/minor ??)
|
64
|
+
# - A-grade /1st class/ tier1 / main beer brand/bestseller/flagship ?
|
65
|
+
# - B-grade /2nd class/ tier2 / regular, major, - todo: find better names?
|
66
|
+
# - C-grade /3nd class/ tier3/ / speciality, minor ?
|
67
|
+
|
68
|
+
# use stars in .txt e.g. # ***/**/*/- => 1/2/3/4
|
69
|
+
t.integer :grade, :null => false, :default => 4
|
70
|
+
|
71
|
+
t.string :txt # source ref
|
72
|
+
t.boolean :txt_auto, :null => false, :default => false # inline? got auto-added?
|
73
|
+
|
74
|
+
|
75
|
+
t.references :country, :null => false
|
76
|
+
t.references :region # optional
|
77
|
+
t.references :city # optional
|
78
|
+
|
79
|
+
t.timestamps
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
create_table :brands do |t| # beer families (sharing same name e.g. brand)
|
84
|
+
t.string :key, :null => false # import/export key
|
85
|
+
t.string :title, :null => false
|
86
|
+
t.string :synonyms # comma separated list of synonyms
|
87
|
+
t.string :web # optional web page (e.g. www.ottakringer.at)
|
88
|
+
t.string :wiki # optional wiki(pedia page)
|
89
|
+
t.integer :since
|
90
|
+
|
91
|
+
## scope of brand (global/intern'l/national/regional/local) ??
|
92
|
+
t.boolean :global, :null => false, :default => false
|
93
|
+
t.boolean :internl, :null => false, :default => false
|
94
|
+
t.boolean :national, :null => false, :default => false
|
95
|
+
t.boolean :regional, :null => false, :default => false
|
96
|
+
t.boolean :local, :null => false, :default => false
|
97
|
+
|
98
|
+
# t.integer :brand_grade # 1/2/3/4/5 (global/intern'l/national/regional/local)
|
99
|
+
|
100
|
+
# use stars in .txt e.g. # ***/**/*/- => 1/2/3/4
|
101
|
+
t.integer :grade, :null => false, :default => 4
|
102
|
+
# -- todo: add plus 1 for brewery w/ *** ??
|
103
|
+
|
104
|
+
t.string :txt # source ref
|
105
|
+
t.boolean :txt_auto, :null => false, :default => false # inline? got auto-added?
|
106
|
+
|
107
|
+
|
108
|
+
t.references :brewery # optional (for now)
|
109
|
+
|
110
|
+
t.references :country, :null => false
|
111
|
+
t.references :region # optional
|
112
|
+
t.references :city # optional
|
113
|
+
|
114
|
+
t.timestamps
|
115
|
+
end
|
116
|
+
|
117
|
+
create_table :breweries do |t|
|
118
|
+
t.string :key, :null => false # import/export key
|
119
|
+
t.string :title, :null => false
|
120
|
+
t.string :synonyms # comma separated list of synonyms
|
121
|
+
t.string :address
|
122
|
+
t.integer :since
|
123
|
+
### fix: rename back to founded or use opened/closed
|
124
|
+
## fix: add flag for ca./about boolean opened_guess / opened_est / opened_??
|
125
|
+
## ca. / about 1010 marker e.g t.boolean : opened_est (for estimate) or similar!!!
|
126
|
+
## renamed to founded to since
|
127
|
+
## t.integer :founded # year founded/established - todo/fix: rename to since?
|
128
|
+
t.integer :closed # optional; year brewery closed
|
129
|
+
|
130
|
+
## todo: add optional parent brewery (owned_by) ???
|
131
|
+
|
132
|
+
t.boolean :brewpub, :null => false, :default => false
|
133
|
+
t.boolean :prod_m, :null => false, :default => false # prod medium (mid-size/regional brewery)
|
134
|
+
# e.g. > 15_000 barrels (us)
|
135
|
+
t.boolean :prod_l, :null => false, :default => false # prod large
|
136
|
+
# e.g. > 500_000 hl (at), > 6_000_000 barrels (us)
|
137
|
+
|
138
|
+
t.integer :prod # (estimated) annual production/capacity in hl (1hl=100l) e.g. megabrewery 2_000_000, microbrewery 1_000 hl; brewbup 500 hl etc.
|
139
|
+
t.integer :prod_grade # 1/2/3/4/5/6/7/8/9/10/11
|
140
|
+
|
141
|
+
# grade - classified using annual production (capacity) in hl
|
142
|
+
# < 1_000 hl => 11
|
143
|
+
# < 3_000 hl => 10
|
144
|
+
# < 5_000 hl => 9
|
145
|
+
# < 10_000 hl => 8
|
146
|
+
# < 50_000 hl => 7
|
147
|
+
# < 100_000 hl => 6
|
148
|
+
# < 200_000 hl => 5
|
149
|
+
# < 500_000 hl => 4
|
150
|
+
# < 1_000_000 hl => 3
|
151
|
+
# < 2_000_000 hl => 2
|
152
|
+
# > 2_000_000 hl => 1
|
153
|
+
|
154
|
+
|
155
|
+
# use stars in .txt e.g. # ***/**/*/- => 1/2/3/4
|
156
|
+
t.integer :grade, :null => false, :default => 4
|
157
|
+
|
158
|
+
|
159
|
+
t.string :txt # source ref
|
160
|
+
t.boolean :txt_auto, :null => false, :default => false # inline? got auto-added?
|
161
|
+
|
162
|
+
t.string :web # optional web page (e.g. www.ottakringer.at)
|
163
|
+
t.string :wikipedia # optional wiki(pedia page)
|
164
|
+
|
165
|
+
t.boolean :indie # independent brewery (flag)
|
166
|
+
|
167
|
+
# for convenience (easy queries) use flags for top beer multinationals (-- later use just tags? more flexible)
|
168
|
+
t.boolean :abinbev # owned by AB InBev / Anheuser-Busch InBev (and Grupo Modelo)
|
169
|
+
t.boolean :sabmiller # owned by SAB Miller (in US cooperates w/ Molson Coors using MillerCoors venture)
|
170
|
+
t.boolean :heineken # owned by Heineken
|
171
|
+
t.boolean :carlsberg # owned by Carlsberg
|
172
|
+
t.boolean :molsoncoors # owned by Molson Coors
|
173
|
+
t.boolean :diageo # owned by Diageo (e.g. Guiness, Kilkenny,...)
|
174
|
+
|
175
|
+
|
176
|
+
# todo: add t.references :parent # for parent brewery
|
177
|
+
# (or better use has many parents w/ percentage of ownership; might not be 100%)
|
178
|
+
|
179
|
+
t.references :country, :null => false
|
180
|
+
t.references :region # optional
|
181
|
+
t.references :city # optional
|
182
|
+
|
183
|
+
t.timestamps
|
184
|
+
end
|
185
|
+
|
186
|
+
end # method up
|
187
|
+
|
188
|
+
def down
|
189
|
+
raise ActiveRecord::IrreversibleMigration
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
end # class CreateDb
|
194
|
+
|
195
|
+
end # module BeerDb
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module BeerDb::Model
|
4
|
+
|
5
|
+
class BeerSerializer
|
6
|
+
|
7
|
+
def initialize( beer )
|
8
|
+
@beer = beer
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :beer
|
12
|
+
|
13
|
+
def as_json
|
14
|
+
brewery = {}
|
15
|
+
if beer.brewery.present?
|
16
|
+
brewery = { key: beer.brewery.key,
|
17
|
+
title: beer.brewery.title }
|
18
|
+
end
|
19
|
+
|
20
|
+
tags = []
|
21
|
+
if beer.tags.present?
|
22
|
+
beer.tags.each { |tag| tags << tag.key }
|
23
|
+
end
|
24
|
+
|
25
|
+
country = {
|
26
|
+
key: beer.country.key,
|
27
|
+
title: beer.country.title
|
28
|
+
}
|
29
|
+
|
30
|
+
data = { key: beer.key,
|
31
|
+
title: beer.title,
|
32
|
+
synonyms: beer.synonyms,
|
33
|
+
abv: beer.abv,
|
34
|
+
srm: beer.srm,
|
35
|
+
og: beer.og,
|
36
|
+
tags: tags,
|
37
|
+
brewery: brewery,
|
38
|
+
country: country }
|
39
|
+
|
40
|
+
data.to_json
|
41
|
+
end
|
42
|
+
|
43
|
+
end # class BeerSerializer
|
44
|
+
|
45
|
+
end # module BeerDb::Model
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module BeerDb::Model
|
4
|
+
|
5
|
+
class BrewerySerializer
|
6
|
+
|
7
|
+
def initialize( brewery )
|
8
|
+
@brewery = brewery
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :brewery
|
12
|
+
|
13
|
+
def as_json
|
14
|
+
|
15
|
+
beers = []
|
16
|
+
brewery.beers.each do |b|
|
17
|
+
beers << { key: b.key, title: b.title }
|
18
|
+
end
|
19
|
+
|
20
|
+
tags = []
|
21
|
+
if brewery.tags.present?
|
22
|
+
brewery.tags.each { |tag| tags << tag.key }
|
23
|
+
end
|
24
|
+
|
25
|
+
country = {
|
26
|
+
key: brewery.country.key,
|
27
|
+
title: brewery.country.title
|
28
|
+
}
|
29
|
+
|
30
|
+
data = { key: brewery.key,
|
31
|
+
title: brewery.title,
|
32
|
+
synonyms: brewery.synonyms,
|
33
|
+
since: brewery.since,
|
34
|
+
address: brewery.address,
|
35
|
+
web: brewery.web,
|
36
|
+
prod: brewery.prod, # (estimated) annual production in hl e.g. 2_000 hl
|
37
|
+
tags: tags,
|
38
|
+
beers: beers,
|
39
|
+
country: country }
|
40
|
+
|
41
|
+
data.to_json
|
42
|
+
end
|
43
|
+
|
44
|
+
end # class BrewerySerializer
|
45
|
+
|
46
|
+
end # module BeerDb::Model
|