winedb 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gemtest +0 -0
- data/Manifest.txt +11 -0
- data/lib/winedb.rb +45 -0
- data/lib/winedb/models/city.rb +11 -0
- data/lib/winedb/models/country.rb +13 -0
- data/lib/winedb/models/forward.rb +39 -0
- data/lib/winedb/models/region.rb +10 -0
- data/lib/winedb/models/tag.rb +12 -0
- data/lib/winedb/models/wine.rb +123 -0
- data/lib/winedb/models/winery.rb +172 -0
- data/lib/winedb/reader.rb +156 -0
- data/lib/winedb/schema.rb +75 -0
- data/lib/winedb/version.rb +1 -1
- data/test/helper.rb +76 -0
- data/test/test_models.rb +70 -0
- metadata +25 -12
data/.gemtest
ADDED
File without changes
|
data/Manifest.txt
CHANGED
@@ -3,4 +3,15 @@ Manifest.txt
|
|
3
3
|
README.md
|
4
4
|
Rakefile
|
5
5
|
lib/winedb.rb
|
6
|
+
lib/winedb/models/city.rb
|
7
|
+
lib/winedb/models/country.rb
|
8
|
+
lib/winedb/models/forward.rb
|
9
|
+
lib/winedb/models/region.rb
|
10
|
+
lib/winedb/models/tag.rb
|
11
|
+
lib/winedb/models/wine.rb
|
12
|
+
lib/winedb/models/winery.rb
|
13
|
+
lib/winedb/reader.rb
|
14
|
+
lib/winedb/schema.rb
|
6
15
|
lib/winedb/version.rb
|
16
|
+
test/helper.rb
|
17
|
+
test/test_models.rb
|
data/lib/winedb.rb
CHANGED
@@ -1,7 +1,28 @@
|
|
1
1
|
|
2
2
|
|
3
|
+
# 3rd party gems / libs
|
4
|
+
|
5
|
+
require 'active_record' ## todo: add sqlite3? etc.
|
6
|
+
|
7
|
+
require 'logutils'
|
8
|
+
require 'textutils'
|
9
|
+
require 'worlddb'
|
10
|
+
|
11
|
+
|
12
|
+
### our own code
|
13
|
+
|
3
14
|
require 'winedb/version' # let it always go first
|
15
|
+
require 'winedb/schema'
|
16
|
+
|
17
|
+
require 'winedb/models/forward'
|
18
|
+
require 'winedb/models/city'
|
19
|
+
require 'winedb/models/country'
|
20
|
+
require 'winedb/models/region'
|
21
|
+
require 'winedb/models/tag'
|
22
|
+
require 'winedb/models/wine'
|
23
|
+
require 'winedb/models/winery'
|
4
24
|
|
25
|
+
require 'winedb/reader'
|
5
26
|
|
6
27
|
module WineDb
|
7
28
|
|
@@ -13,6 +34,30 @@ module WineDb
|
|
13
34
|
"#{File.expand_path( File.dirname(File.dirname(__FILE__)) )}"
|
14
35
|
end
|
15
36
|
|
37
|
+
def self.create
|
38
|
+
CreateDb.new.up
|
39
|
+
|
40
|
+
WineDb::Model::Prop.create!( key: 'db.schema.wine.version', value: VERSION )
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
def self.read( ary, include_path )
|
45
|
+
reader = Reader.new( include_path )
|
46
|
+
ary.each do |name|
|
47
|
+
reader.load( name )
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.read_setup( setup, include_path, opts={} )
|
52
|
+
reader = Reader.new( include_path, opts )
|
53
|
+
reader.load_setup( setup )
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.read_all( include_path, opts={} ) # load all builtins (using plain text reader); helper for convenience
|
57
|
+
read_setup( 'setups/all', include_path, opts )
|
58
|
+
end # method read_all
|
59
|
+
|
60
|
+
|
16
61
|
end # module WineDb
|
17
62
|
|
18
63
|
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module WorldDb
|
2
|
+
module Model
|
3
|
+
|
4
|
+
class City
|
5
|
+
has_many :wines, class_name: 'WineDb::Model::Wine', foreign_key: 'city_id'
|
6
|
+
has_many :wineries, class_name: 'WineDb::Model::Winery', foreign_key: 'city_id'
|
7
|
+
end # class Country
|
8
|
+
|
9
|
+
|
10
|
+
end # module Model
|
11
|
+
end # module WorldDb
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module WorldDb
|
2
|
+
module Model
|
3
|
+
|
4
|
+
|
5
|
+
class Country
|
6
|
+
has_many :wines, class_name: 'WineDb::Model::Wine', foreign_key: 'country_id'
|
7
|
+
has_many :wineries, class_name: 'WineDb::Model::Winery', foreign_key: 'country_id'
|
8
|
+
end # class Country
|
9
|
+
|
10
|
+
|
11
|
+
end # module Model
|
12
|
+
end # module WorldDb
|
13
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
### forward references
|
2
|
+
## require first to resolve circular references
|
3
|
+
|
4
|
+
|
5
|
+
module WineDb
|
6
|
+
module Model
|
7
|
+
|
8
|
+
## todo: why? why not use include WorldDb::Models here???
|
9
|
+
|
10
|
+
Continent = WorldDb::Model::Continent
|
11
|
+
Country = WorldDb::Model::Country
|
12
|
+
Region = WorldDb::Model::Region
|
13
|
+
City = WorldDb::Model::City
|
14
|
+
|
15
|
+
Tag = WorldDb::Model::Tag
|
16
|
+
Tagging = WorldDb::Model::Tagging
|
17
|
+
|
18
|
+
Prop = WorldDb::Model::Prop
|
19
|
+
|
20
|
+
class Wine < ActiveRecord::Base ; end
|
21
|
+
class Winery < ActiveRecord::Base ; end
|
22
|
+
|
23
|
+
end # module Model
|
24
|
+
|
25
|
+
## note: for convenciene (and compatibility) add alias Models for Model namespace
|
26
|
+
## e.g lets you use include WineDb::Models
|
27
|
+
Models = Model
|
28
|
+
|
29
|
+
end # module WineDb
|
30
|
+
|
31
|
+
|
32
|
+
module WorldDb
|
33
|
+
module Model
|
34
|
+
|
35
|
+
Wine = WineDb::Model::Wine
|
36
|
+
Winery = WineDb::Model::Winery
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module WorldDb
|
2
|
+
module Model
|
3
|
+
|
4
|
+
class Region
|
5
|
+
has_many :wines, class_name: 'WineDb::Model::Wine', foreign_key: 'region_id'
|
6
|
+
has_many :wineries, class_name: 'WineDb::Model::Winery', foreign_key: 'region_id'
|
7
|
+
end # class Region
|
8
|
+
|
9
|
+
end # module Model
|
10
|
+
end # module WorldDb
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module WorldDb
|
2
|
+
module Model
|
3
|
+
|
4
|
+
|
5
|
+
class Tag
|
6
|
+
has_many :wines, :through => :taggings, :source => :taggable, source_type: 'WineDb::Model::Wine', class_name: 'WineDb::Model::Wine'
|
7
|
+
has_many :wineries, :through => :taggings, :source => :taggable, source_type: 'WineDb::Model::Winery', class_name: 'WineDb::Model::Winery'
|
8
|
+
end # class Country
|
9
|
+
|
10
|
+
|
11
|
+
end # module Model
|
12
|
+
end # module WorldDb
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module WineDb
|
4
|
+
module Model
|
5
|
+
|
6
|
+
class Wine < ActiveRecord::Base
|
7
|
+
|
8
|
+
extend TextUtils::TagHelper # will add self.find_tags, self.find_tags_in_attribs!, etc.
|
9
|
+
|
10
|
+
# NB: use extend - is_<type>? become class methods e.g. self.is_<type>? for use in
|
11
|
+
# self.create_or_update_from_values
|
12
|
+
extend TextUtils::ValueHelper # e.g. self.is_year?, self.is_region?, self.is_address?, is_taglist? etc.
|
13
|
+
|
14
|
+
belongs_to :country, class_name: 'WorldDb::Model::Country', foreign_key: 'country_id'
|
15
|
+
belongs_to :region, class_name: 'WorldDb::Model::Region', foreign_key: 'region_id'
|
16
|
+
belongs_to :city, class_name: 'WorldDb::Model::City', foreign_key: 'city_id'
|
17
|
+
|
18
|
+
belongs_to :winery, :class_name => 'WineDb::Model::Winery', foreign_key: 'winery_id'
|
19
|
+
|
20
|
+
has_many :taggings, :as => :taggable, class_name: 'WorldDb::Model::Tagging'
|
21
|
+
has_many :tags, :through => :taggings, class_name: 'WorldDb::Model::Tag'
|
22
|
+
|
23
|
+
validates :key, :format => { :with => /^[a-z][a-z0-9]+$/, :message => 'expected two or more lowercase letters a-z or 0-9 digits' }
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
def self.create_or_update_from_values( values, more_attribs={} )
|
28
|
+
|
29
|
+
attribs, more_values = find_key_n_title( values )
|
30
|
+
attribs = attribs.merge( more_attribs )
|
31
|
+
|
32
|
+
# check for optional values
|
33
|
+
Wine.create_or_update_from_attribs( attribs, more_values )
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def self.create_or_update_from_attribs( attribs, values )
|
38
|
+
|
39
|
+
# fix: add/configure logger for ActiveRecord!!!
|
40
|
+
logger = LogKernel::Logger.root
|
41
|
+
|
42
|
+
value_tag_keys = []
|
43
|
+
|
44
|
+
### check for "default" tags - that is, if present attribs[:tags] remove from hash
|
45
|
+
value_tag_keys += find_tags_in_attribs!( attribs )
|
46
|
+
|
47
|
+
## check for optional values
|
48
|
+
values.each_with_index do |value,index|
|
49
|
+
if match_country(value) do |country|
|
50
|
+
attribs[ :country_id ] = country.id
|
51
|
+
end
|
52
|
+
elsif match_region_for_country(value, attribs[:country_id]) do |region|
|
53
|
+
attribs[ :region_id ] = region.id
|
54
|
+
end
|
55
|
+
elsif match_city(value) do |city|
|
56
|
+
if city.present?
|
57
|
+
attribs[ :city_id ] = city.id
|
58
|
+
else
|
59
|
+
## todo/fix: add strict mode flag - fail w/ exit 1 in strict mode
|
60
|
+
logger.warn "city with key #{value[5..-1]} missing for beer #{attribs[:key]}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
elsif match_year( value ) do |num| # founded/established year e.g. 1776
|
64
|
+
attribs[ :since ] = num
|
65
|
+
end
|
66
|
+
elsif match_website( value ) do |website| # check for url/internet address e.g. www.ottakringer.at
|
67
|
+
attribs[ :web ] = website
|
68
|
+
end
|
69
|
+
elsif match_abv( value ) do |num| # abv (alcohol by volume)
|
70
|
+
# nb: also allows leading < e.g. <0.5%
|
71
|
+
attribs[ :abv ] = num
|
72
|
+
end
|
73
|
+
elsif (values.size==(index+1)) && is_taglist?( value ) # tags must be last entry
|
74
|
+
logger.debug " found tags: >>#{value}<<"
|
75
|
+
value_tag_keys += find_tags( value )
|
76
|
+
else
|
77
|
+
# issue warning: unknown type for value
|
78
|
+
logger.warn "unknown type for value >#{value}< - key #{attribs[:key]}"
|
79
|
+
end
|
80
|
+
end # each value
|
81
|
+
|
82
|
+
# rec = Wine.find_by_key_and_country_id( attribs[ :key ], attribs[ :country_id] )
|
83
|
+
rec = Wine.find_by_key( attribs[ :key ] )
|
84
|
+
|
85
|
+
if rec.present?
|
86
|
+
logger.debug "update Wine #{rec.id}-#{rec.key}:"
|
87
|
+
else
|
88
|
+
logger.debug "create Wine:"
|
89
|
+
rec = Wine.new
|
90
|
+
end
|
91
|
+
|
92
|
+
logger.debug attribs.to_json
|
93
|
+
|
94
|
+
rec.update_attributes!( attribs )
|
95
|
+
|
96
|
+
##################
|
97
|
+
# add taggings
|
98
|
+
|
99
|
+
if value_tag_keys.size > 0
|
100
|
+
|
101
|
+
value_tag_keys.uniq! # remove duplicates
|
102
|
+
logger.debug " adding #{value_tag_keys.size} taggings: >>#{value_tag_keys.join('|')}<<..."
|
103
|
+
|
104
|
+
### fix/todo: check tag_ids and only update diff (add/remove ids)
|
105
|
+
|
106
|
+
value_tag_keys.each do |key|
|
107
|
+
tag = Tag.find_by_key( key )
|
108
|
+
if tag.nil? # create tag if it doesn't exit
|
109
|
+
logger.debug " creating tag >#{key}<"
|
110
|
+
tag = Tag.create!( key: key )
|
111
|
+
end
|
112
|
+
rec.tags << tag
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
rec # NB: return created or updated obj
|
117
|
+
|
118
|
+
end # method create_or_update_from_values
|
119
|
+
|
120
|
+
end # class Wine
|
121
|
+
|
122
|
+
end # module Model
|
123
|
+
end # module WineDb
|
@@ -0,0 +1,172 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module WineDb
|
4
|
+
module Model
|
5
|
+
|
6
|
+
class Winery < ActiveRecord::Base
|
7
|
+
|
8
|
+
extend TextUtils::TagHelper # will add self.find_tags, self.find_tags_in_attribs!, etc.
|
9
|
+
|
10
|
+
# NB: use extend - is_<type>? become class methods e.g. self.is_<type>? for use in
|
11
|
+
# self.create_or_update_from_values
|
12
|
+
extend TextUtils::ValueHelper # e.g. self.is_year?, self.is_region?, is_address?, is_taglist? etc.
|
13
|
+
extend TextUtils::AddressHelper # e.g self.normalize_addr, self.find_city_in_addr, etc.
|
14
|
+
|
15
|
+
self.table_name = 'wineries'
|
16
|
+
|
17
|
+
belongs_to :country, class_name: 'WorldDb::Model::Country', foreign_key: 'country_id'
|
18
|
+
belongs_to :region, class_name: 'WorldDb::Model::Region', foreign_key: 'region_id'
|
19
|
+
belongs_to :city, class_name: 'WorldDb::Model::City', foreign_key: 'city_id'
|
20
|
+
|
21
|
+
has_many :wines, class_name: 'WineDb::Model::Wine', foreign_key: 'wine_id'
|
22
|
+
|
23
|
+
has_many :taggings, :as => :taggable, class_name: 'WorldDb::Model::Tagging'
|
24
|
+
has_many :tags, :through => :taggings, class_name: 'WorldDb::Model::Tag'
|
25
|
+
|
26
|
+
validates :key, :format => { :with => /^[a-z][a-z0-9]+$/, :message => 'expected two or more lowercase letters a-z or 0-9 digits' }
|
27
|
+
|
28
|
+
|
29
|
+
def self.create_or_update_from_values( values, more_attribs={} )
|
30
|
+
attribs, more_values = find_key_n_title( values )
|
31
|
+
attribs = attribs.merge( more_attribs )
|
32
|
+
|
33
|
+
# check for optional values
|
34
|
+
Winery.create_or_update_from_attribs( attribs, more_values )
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def self.create_or_update_from_attribs( new_attributes, values )
|
39
|
+
|
40
|
+
## fix: add/configure logger for ActiveRecord!!!
|
41
|
+
logger = LogKernel::Logger.root
|
42
|
+
|
43
|
+
value_tag_keys = []
|
44
|
+
|
45
|
+
## check for grades (e.g. ***/**/*) in titles (will add new_attributes[:grade] to hash)
|
46
|
+
## if grade missing; set default to 4; lets us update overwrite 1,2,3 values on update
|
47
|
+
new_attributes[ :grade ] ||= 4
|
48
|
+
|
49
|
+
### check for "default" tags - that is, if present new_attributes[:tags] remove from hash
|
50
|
+
value_tag_keys += find_tags_in_attribs!( new_attributes )
|
51
|
+
|
52
|
+
## check for optional values
|
53
|
+
values.each_with_index do |value,index|
|
54
|
+
if match_country(value) do |country|
|
55
|
+
new_attributes[ :country_id ] = country.id
|
56
|
+
end
|
57
|
+
elsif match_region_for_country(value,new_attributes[:country_id]) do |region|
|
58
|
+
new_attributes[ :region_id ] = region.id
|
59
|
+
end
|
60
|
+
elsif match_city(value) do |city|
|
61
|
+
if city.present?
|
62
|
+
new_attributes[ :city_id ] = city.id
|
63
|
+
else
|
64
|
+
## todo/fix: add strict mode flag - fail w/ exit 1 in strict mode
|
65
|
+
logger.warn "city with key #{value[5..-1]} missing - for winery #{new_attributes[:key]}"
|
66
|
+
end
|
67
|
+
|
68
|
+
## for easy queries: cache region_id (from city)
|
69
|
+
# - check if city w/ region if yes, use it for winery too
|
70
|
+
if city.present? && city.region.present?
|
71
|
+
new_attributes[ :region_id ] = city.region.id
|
72
|
+
end
|
73
|
+
end
|
74
|
+
elsif match_year( value ) do |num| # founded/established year e.g. 1776
|
75
|
+
new_attributes[ :since ] = num
|
76
|
+
end
|
77
|
+
elsif match_website( value ) do |website| # check for url/internet address e.g. www.ottakringer.at
|
78
|
+
# fix: support more url format (e.g. w/o www. - look for .com .country code etc.)
|
79
|
+
new_attributes[ :web ] = website
|
80
|
+
end
|
81
|
+
elsif is_address?( value ) # if value includes // assume address e.g. 3970 Weitra // Sparkasseplatz 160
|
82
|
+
new_attributes[ :address ] = normalize_addr( value )
|
83
|
+
elsif (values.size==(index+1)) && is_taglist?( value ) # tags must be last entry
|
84
|
+
logger.debug " found tags: >>#{value}<<"
|
85
|
+
value_tag_keys += find_tags( value )
|
86
|
+
else
|
87
|
+
# issue warning: unknown type for value
|
88
|
+
logger.warn "unknown type for value >#{value}< - key #{new_attributes[:key]}"
|
89
|
+
end
|
90
|
+
end # each value
|
91
|
+
|
92
|
+
## todo: check better style using self.find_by_key?? why? why not?
|
93
|
+
rec = Winery.find_by_key( new_attributes[ :key ] )
|
94
|
+
|
95
|
+
if rec.present?
|
96
|
+
logger.debug "update Winery #{rec.id}-#{rec.key}:"
|
97
|
+
else
|
98
|
+
logger.debug "create Winery:"
|
99
|
+
rec = Winery.new
|
100
|
+
end
|
101
|
+
|
102
|
+
logger.debug new_attributes.to_json
|
103
|
+
|
104
|
+
rec.update_attributes!( new_attributes )
|
105
|
+
|
106
|
+
|
107
|
+
##############################
|
108
|
+
# auto-add city if not present and country n region present
|
109
|
+
|
110
|
+
if new_attributes[:city_id].blank? &&
|
111
|
+
new_attributes[:country_id].present? &&
|
112
|
+
new_attributes[:region_id].present?
|
113
|
+
|
114
|
+
country_key = rec.country.key
|
115
|
+
|
116
|
+
if country_key == 'at' || country_key == 'de'
|
117
|
+
|
118
|
+
## todo: how to handle nil/empty address lines?
|
119
|
+
|
120
|
+
city_title = find_city_in_addr( new_attributes[:address], country_key )
|
121
|
+
|
122
|
+
if city_title.present?
|
123
|
+
|
124
|
+
city_values = [city_title]
|
125
|
+
city_attributes = {
|
126
|
+
country_id: rec.country_id,
|
127
|
+
region_id: rec.region_id
|
128
|
+
}
|
129
|
+
# todo: add convenience helper create_or_update_from_title
|
130
|
+
city = City.create_or_update_from_values( city_values, city_attributes )
|
131
|
+
|
132
|
+
### fix/todo: set new autoadd flag too?
|
133
|
+
## e.g. check if updated? e.g. timestamp created <> updated otherwise assume created?
|
134
|
+
|
135
|
+
## now at last add city_id to winery!
|
136
|
+
rec.city_id = city.id
|
137
|
+
rec.save!
|
138
|
+
else
|
139
|
+
logger.warn "auto-add city for #{new_attributes[:key]} (#{country_key}) >>#{new_attributes[:address]}<< failed; no city title found"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
##################
|
145
|
+
## add taggings
|
146
|
+
|
147
|
+
if value_tag_keys.size > 0
|
148
|
+
|
149
|
+
value_tag_keys.uniq! # remove duplicates
|
150
|
+
logger.debug " adding #{value_tag_keys.size} taggings: >>#{value_tag_keys.join('|')}<<..."
|
151
|
+
|
152
|
+
### fix/todo: check tag_ids and only update diff (add/remove ids)
|
153
|
+
|
154
|
+
value_tag_keys.each do |key|
|
155
|
+
tag = Tag.find_by_key( key )
|
156
|
+
if tag.nil? # create tag if it doesn't exit
|
157
|
+
logger.debug " creating tag >#{key}<"
|
158
|
+
tag = Tag.create!( key: key )
|
159
|
+
end
|
160
|
+
rec.tags << tag
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
rec # NB: return created or updated obj
|
165
|
+
|
166
|
+
end # method create_or_update_from_values
|
167
|
+
|
168
|
+
|
169
|
+
end # class Winery
|
170
|
+
|
171
|
+
end # module Model
|
172
|
+
end # module WineDb
|
@@ -0,0 +1,156 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module WineDb
|
4
|
+
|
5
|
+
|
6
|
+
module Matcher
|
7
|
+
|
8
|
+
def match_wines_for_country( name, &blk )
|
9
|
+
match_xxx_for_country( name, 'wines', &blk )
|
10
|
+
end
|
11
|
+
|
12
|
+
# def match_wines_for_country_n_region( name, &blk )
|
13
|
+
# match_xxx_for_country_n_region( name, 'wines', &blk )
|
14
|
+
# end
|
15
|
+
|
16
|
+
def match_wineries_for_country( name, &blk )
|
17
|
+
match_xxx_for_country( name, 'wineries', &blk )
|
18
|
+
end
|
19
|
+
|
20
|
+
# def match_wineries_for_country_n_region( name, &blk )
|
21
|
+
# match_xxx_for_country_n_region( name, 'wineries', &blk )
|
22
|
+
# end
|
23
|
+
|
24
|
+
end # module Matcher
|
25
|
+
|
26
|
+
|
27
|
+
class Reader
|
28
|
+
|
29
|
+
include LogUtils::Logging
|
30
|
+
|
31
|
+
include WineDb::Models
|
32
|
+
|
33
|
+
include WorldDb::Matcher ## fix: move to WineDb::Matcher module ??? - cleaner?? why? why not?
|
34
|
+
include WineDb::Matcher # lets us use match_teams_for_country etc.
|
35
|
+
|
36
|
+
attr_reader :include_path
|
37
|
+
|
38
|
+
|
39
|
+
def initialize( include_path, opts = {} )
|
40
|
+
@include_path = include_path
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
def load_setup( name )
|
45
|
+
path = "#{include_path}/#{name}.yml"
|
46
|
+
|
47
|
+
logger.info "parsing data '#{name}' (#{path})..."
|
48
|
+
|
49
|
+
reader = FixtureReader.new( path )
|
50
|
+
|
51
|
+
reader.each do |fixture_name|
|
52
|
+
load( fixture_name )
|
53
|
+
end
|
54
|
+
end # method load_setup
|
55
|
+
|
56
|
+
|
57
|
+
def load( name )
|
58
|
+
|
59
|
+
if match_wines_for_country( name ) do |country_key|
|
60
|
+
load_wines_for_country( country_key, name )
|
61
|
+
end
|
62
|
+
elsif match_wineries_for_country( name ) do |country_key|
|
63
|
+
load_wineries_for_country( country_key, name )
|
64
|
+
end
|
65
|
+
else
|
66
|
+
logger.error "unknown wine.db fixture type >#{name}<"
|
67
|
+
# todo/fix: exit w/ error
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
def load_wines_for_country( country_key, name, more_attribs={} )
|
73
|
+
country = Country.find_by_key!( country_key )
|
74
|
+
logger.debug "Country #{country.key} >#{country.title} (#{country.code})<"
|
75
|
+
|
76
|
+
more_attribs[ :country_id ] = country.id
|
77
|
+
|
78
|
+
more_attribs[ :txt ] = name # store source ref
|
79
|
+
|
80
|
+
load_wines_worker( name, more_attribs )
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
def load_wines_worker( name, more_attribs={} )
|
85
|
+
reader = ValuesReaderV2.new( name, include_path, more_attribs )
|
86
|
+
|
87
|
+
### todo: cleanup - check if [] works for build_title...
|
88
|
+
# better cleaner way ???
|
89
|
+
if more_attribs[:region_id].present?
|
90
|
+
known_wineries_source = Winery.where( region_id: more_attribs[:region_id] )
|
91
|
+
elsif more_attribs[:country_id].present?
|
92
|
+
known_wineries_source = Winery.where( country_id: more_attribs[:country_id] )
|
93
|
+
else
|
94
|
+
logger.warn "no region or country specified; use empty winery ary for header mapper"
|
95
|
+
known_wineries_source = []
|
96
|
+
end
|
97
|
+
|
98
|
+
known_wineries = TextUtils.build_title_table_for( known_wineries_source )
|
99
|
+
|
100
|
+
reader.each_line do |new_attributes, values|
|
101
|
+
|
102
|
+
## note: check for header attrib; if present remove
|
103
|
+
### todo: cleanup code later
|
104
|
+
## fix: add to new_attributes hash instead of values ary
|
105
|
+
## - fix: match_winery() move region,city code out of values loop for reuse at the end
|
106
|
+
if new_attributes[:header].present?
|
107
|
+
winery_line = new_attributes[:header].dup # note: make sure we make a copy; will use in-place string ops
|
108
|
+
new_attributes.delete(:header) ## note: do NOT forget to remove from hash!
|
109
|
+
|
110
|
+
logger.debug " trying to find winery in line >#{winery_line}<"
|
111
|
+
## todo: check what map_titles_for! returns (nothing ???)
|
112
|
+
TextUtils.map_titles_for!( 'winery', winery_line, known_wineries )
|
113
|
+
winery_key = TextUtils.find_key_for!( 'winery', winery_line )
|
114
|
+
logger.debug " winery_key = >#{winery_key}<"
|
115
|
+
unless winery_key.nil?
|
116
|
+
## bingo! add winery_id upfront, that is, as first value in ary
|
117
|
+
values = values.unshift "winery:#{winery_key}"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
Wine.create_or_update_from_attribs( new_attributes, values )
|
122
|
+
end # each_line
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
def load_wineries_for_country( country_key, name, more_attribs={} )
|
127
|
+
country = Country.find_by_key!( country_key )
|
128
|
+
logger.debug "Country #{country.key} >#{country.title} (#{country.code})<"
|
129
|
+
|
130
|
+
more_attribs[ :country_id ] = country.id
|
131
|
+
|
132
|
+
more_attribs[ :txt ] = name # store source ref
|
133
|
+
|
134
|
+
load_wineries_worker( name, more_attribs )
|
135
|
+
end
|
136
|
+
|
137
|
+
def load_wineries_worker( name, more_attribs={} )
|
138
|
+
reader = ValuesReaderV2.new( name, include_path, more_attribs )
|
139
|
+
|
140
|
+
reader.each_line do |new_attributes, values|
|
141
|
+
|
142
|
+
#######
|
143
|
+
# fix: move to (inside)
|
144
|
+
# Winery.create_or_update_from_attribs ||||
|
145
|
+
## note: group header not used for now; do NOT forget to remove from hash!
|
146
|
+
if new_attributes[:header].present?
|
147
|
+
logger.warn "removing unused group header #{new_attributes[:header]}"
|
148
|
+
new_attributes.delete(:header) ## note: do NOT forget to remove from hash!
|
149
|
+
end
|
150
|
+
|
151
|
+
Winery.create_or_update_from_attribs( new_attributes, values )
|
152
|
+
end # each_line
|
153
|
+
end
|
154
|
+
|
155
|
+
end # class Reader
|
156
|
+
end # module WineDb
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module WineDb
|
4
|
+
|
5
|
+
class CreateDb < ActiveRecord::Migration
|
6
|
+
|
7
|
+
|
8
|
+
def up
|
9
|
+
|
10
|
+
create_table :wines 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
|
+
## check: why decimal and not float?
|
19
|
+
t.decimal :abv # Alcohol by volume (abbreviated as ABV, abv, or alc/vol) e.g. 4.9 %
|
20
|
+
|
21
|
+
t.references :winery # optional (for now)
|
22
|
+
|
23
|
+
|
24
|
+
t.string :txt # source ref
|
25
|
+
t.boolean :txt_auto, null: false, default: false # inline? got auto-added?
|
26
|
+
|
27
|
+
|
28
|
+
t.references :country, null: false
|
29
|
+
t.references :region # optional
|
30
|
+
t.references :city # optional
|
31
|
+
|
32
|
+
t.timestamps
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
create_table :wineries do |t|
|
37
|
+
t.string :key, null: false # import/export key
|
38
|
+
t.string :title, null: false
|
39
|
+
t.string :synonyms # comma separated list of synonyms
|
40
|
+
t.string :address
|
41
|
+
t.integer :since
|
42
|
+
## renamed to founded to since
|
43
|
+
## t.integer :founded # year founded/established - todo/fix: rename to since?
|
44
|
+
t.integer :closed # optional; year winery closed
|
45
|
+
|
46
|
+
t.integer :area # in ha e.g. 8 ha # Weingarten/rebflaeche
|
47
|
+
|
48
|
+
# use stars in .txt e.g. # ***/**/*/- => 1/2/3/4
|
49
|
+
t.integer :grade, null: false, default: 4
|
50
|
+
|
51
|
+
|
52
|
+
t.string :txt # source ref
|
53
|
+
t.boolean :txt_auto, null: false, default: false # inline? got auto-added?
|
54
|
+
|
55
|
+
t.string :web # optional web page (e.g. www.ottakringer.at)
|
56
|
+
t.string :wikipedia # optional wiki(pedia page)
|
57
|
+
|
58
|
+
|
59
|
+
t.references :country, null: false
|
60
|
+
t.references :region # optional
|
61
|
+
t.references :city # optional
|
62
|
+
|
63
|
+
t.timestamps
|
64
|
+
end
|
65
|
+
|
66
|
+
end # method up
|
67
|
+
|
68
|
+
def down
|
69
|
+
raise ActiveRecord::IrreversibleMigration
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
end # class CreateDb
|
74
|
+
|
75
|
+
end # module WineDb
|
data/lib/winedb/version.rb
CHANGED
data/test/helper.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
## $:.unshift(File.dirname(__FILE__))
|
4
|
+
|
5
|
+
## minitest setup
|
6
|
+
|
7
|
+
# require 'minitest/unit'
|
8
|
+
require 'minitest/autorun'
|
9
|
+
|
10
|
+
# include MiniTest::Unit # lets us use TestCase instead of MiniTest::Unit::TestCase
|
11
|
+
|
12
|
+
|
13
|
+
# ruby stdlibs
|
14
|
+
|
15
|
+
require 'json'
|
16
|
+
require 'uri'
|
17
|
+
require 'pp'
|
18
|
+
|
19
|
+
# ruby gems
|
20
|
+
|
21
|
+
require 'active_record'
|
22
|
+
|
23
|
+
# our own code
|
24
|
+
|
25
|
+
require 'winedb'
|
26
|
+
require 'logutils/db' # NB: explict require required for LogDb (not automatic)
|
27
|
+
|
28
|
+
Country = WorldDb::Model::Country
|
29
|
+
Region = WorldDb::Model::Region
|
30
|
+
City = WorldDb::Model::City
|
31
|
+
|
32
|
+
## todo: get all models aliases (e.g. from console script)
|
33
|
+
|
34
|
+
Wine = WineDb::Model::Wine
|
35
|
+
Winery = WineDb::Model::Winery
|
36
|
+
|
37
|
+
|
38
|
+
def setup_in_memory_db
|
39
|
+
# Database Setup & Config
|
40
|
+
|
41
|
+
db_config = {
|
42
|
+
adapter: 'sqlite3',
|
43
|
+
database: ':memory:'
|
44
|
+
}
|
45
|
+
|
46
|
+
pp db_config
|
47
|
+
|
48
|
+
ActiveRecord::Base.logger = Logger.new( STDOUT )
|
49
|
+
## ActiveRecord::Base.colorize_logging = false - no longer exists - check new api/config setting?
|
50
|
+
|
51
|
+
## NB: every connect will create a new empty in memory db
|
52
|
+
ActiveRecord::Base.establish_connection( db_config )
|
53
|
+
|
54
|
+
|
55
|
+
## build schema
|
56
|
+
|
57
|
+
LogDb.create
|
58
|
+
WorldDb.create
|
59
|
+
WineDb.create
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
def fillup_in_memory_db
|
64
|
+
## add some counties
|
65
|
+
|
66
|
+
at = Country.create!( key: 'at', title: 'Austria', code: 'AUT', pop: 0, area: 0 )
|
67
|
+
n = Region.create!( key: 'n', title: 'Niederösterreich', country_id: at.id )
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
setup_in_memory_db()
|
72
|
+
fillup_in_memory_db()
|
73
|
+
|
74
|
+
AT = Country.find_by_key!( 'at' )
|
75
|
+
N = Region.find_by_key!( 'n' )
|
76
|
+
|
data/test/test_models.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
###
|
4
|
+
# to run use
|
5
|
+
# ruby -I ./lib -I ./test test/test_helper.rb
|
6
|
+
# or better
|
7
|
+
# rake test
|
8
|
+
|
9
|
+
require 'helper'
|
10
|
+
|
11
|
+
|
12
|
+
class TestModels < MiniTest::Unit::TestCase
|
13
|
+
|
14
|
+
def test_load_wine_values
|
15
|
+
|
16
|
+
key = 'gruenerveltlinerspiegel'
|
17
|
+
|
18
|
+
values = [
|
19
|
+
'Grüner Veltliner Spiegel',
|
20
|
+
'12.3 %',
|
21
|
+
'gv'
|
22
|
+
]
|
23
|
+
|
24
|
+
more_attribs = {
|
25
|
+
country_id: AT.id
|
26
|
+
}
|
27
|
+
|
28
|
+
wine = Wine.create_or_update_from_values( values, more_attribs )
|
29
|
+
|
30
|
+
wine2 = Wine.find_by_key!( key )
|
31
|
+
assert_equal wine.id, wine2.id
|
32
|
+
|
33
|
+
assert_equal wine.title, values[0]
|
34
|
+
assert_equal wine.country_id, AT.id
|
35
|
+
assert_equal wine.country.title, AT.title
|
36
|
+
assert_equal wine.abv, 12.3
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_load_winery_values
|
40
|
+
|
41
|
+
key = 'antonbauer'
|
42
|
+
|
43
|
+
values = [
|
44
|
+
key,
|
45
|
+
'Anton Bauer (1971)',
|
46
|
+
'www.antonbauer.at',
|
47
|
+
'Neufang 42 // 3483 Feuersbrunn',
|
48
|
+
'25 ha', ### todo: make sure it will not get matched as tag
|
49
|
+
'tag'
|
50
|
+
]
|
51
|
+
|
52
|
+
more_attribs = {
|
53
|
+
country_id: AT.id
|
54
|
+
}
|
55
|
+
|
56
|
+
wy = Winery.create_or_update_from_values( values, more_attribs )
|
57
|
+
|
58
|
+
wy2 = Winery.find_by_key!( key )
|
59
|
+
assert_equal wy.id, wy2.id
|
60
|
+
|
61
|
+
assert_equal wy.title, values[1]
|
62
|
+
assert_equal wy.country_id, AT.id
|
63
|
+
assert_equal wy.country.title, AT.title
|
64
|
+
assert_equal wy.web, 'www.antonbauer.at'
|
65
|
+
assert_equal wy.address, 'Neufang 42 // 3483 Feuersbrunn'
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
end # class TestModels
|
70
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: winedb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -13,7 +13,7 @@ date: 2014-03-01 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|
16
|
-
requirement: &
|
16
|
+
requirement: &71681830 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '3.2'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *71681830
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: worlddb
|
27
|
-
requirement: &
|
27
|
+
requirement: &71681430 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '1.7'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *71681430
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: gli
|
38
|
-
requirement: &
|
38
|
+
requirement: &71681160 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: 2.5.6
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *71681160
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rdoc
|
49
|
-
requirement: &
|
49
|
+
requirement: &71680880 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '3.10'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *71680880
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: hoe
|
60
|
-
requirement: &
|
60
|
+
requirement: &71680600 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ~>
|
@@ -65,7 +65,7 @@ dependencies:
|
|
65
65
|
version: '3.3'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *71680600
|
69
69
|
description: winedb - wine.db command line tool
|
70
70
|
email: winedb@googlegroups.com
|
71
71
|
executables: []
|
@@ -78,7 +78,19 @@ files:
|
|
78
78
|
- README.md
|
79
79
|
- Rakefile
|
80
80
|
- lib/winedb.rb
|
81
|
+
- lib/winedb/models/city.rb
|
82
|
+
- lib/winedb/models/country.rb
|
83
|
+
- lib/winedb/models/forward.rb
|
84
|
+
- lib/winedb/models/region.rb
|
85
|
+
- lib/winedb/models/tag.rb
|
86
|
+
- lib/winedb/models/wine.rb
|
87
|
+
- lib/winedb/models/winery.rb
|
88
|
+
- lib/winedb/reader.rb
|
89
|
+
- lib/winedb/schema.rb
|
81
90
|
- lib/winedb/version.rb
|
91
|
+
- test/helper.rb
|
92
|
+
- test/test_models.rb
|
93
|
+
- .gemtest
|
82
94
|
homepage: https://github.com/geraldb/wine.db.ruby
|
83
95
|
licenses:
|
84
96
|
- Public Domain
|
@@ -106,4 +118,5 @@ rubygems_version: 1.8.17
|
|
106
118
|
signing_key:
|
107
119
|
specification_version: 3
|
108
120
|
summary: winedb - wine.db command line tool
|
109
|
-
test_files:
|
121
|
+
test_files:
|
122
|
+
- test/test_models.rb
|