sportdb-catalogs 1.0.0 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +1 -0
- data/Manifest.txt +9 -11
- data/README.md +3 -54
- data/Rakefile +6 -8
- data/lib/sportdb/catalogs/base.rb +154 -0
- data/lib/sportdb/catalogs/city.rb +78 -0
- data/lib/sportdb/catalogs/club.rb +104 -0
- data/lib/sportdb/catalogs/country.rb +147 -0
- data/lib/sportdb/catalogs/event_info.rb +74 -0
- data/lib/sportdb/catalogs/ground.rb +106 -0
- data/lib/sportdb/catalogs/league.rb +170 -0
- data/lib/sportdb/catalogs/national_team.rb +61 -0
- data/lib/sportdb/catalogs/player.rb +125 -0
- data/lib/sportdb/catalogs/version.rb +3 -5
- data/lib/sportdb/catalogs.rb +172 -10
- metadata +35 -47
- data/NOTES.md +0 -5
- data/lib/sportdb/catalogs/catalog.rb +0 -105
- data/lib/sportdb/catalogs/config.rb +0 -25
- data/lib/sportdb/catalogs/wiki_index.rb +0 -81
- data/test/helper.rb +0 -13
- data/test/test_clubs.rb +0 -100
- data/test/test_clubs_history.rb +0 -47
- data/test/test_countries.rb +0 -33
- data/test/test_leagues.rb +0 -38
- data/test/test_national_teams.rb +0 -38
- data/test/test_wiki_index.rb +0 -42
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4615353b8720394e2c0edcd97de249822923718bf9d7703e4ce2f93f4411b372
|
4
|
+
data.tar.gz: ac4536c29d15caf8bf5c3de62539785f17fbad3e6098c5e0d18282c1e5cccb2a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 93dc76f6a2dd70d04a9ebb6700e66bbcd41a98cd517a9f1ed24b6a2ad667806963ffd2fda6991a02600c3aa4d61c45431201b4f350a37b4a080e53bf664866ec
|
7
|
+
data.tar.gz: f0cc52b6590c598ec8ae40abdb7b8238a647617b10825b76d4c910bcd01ca7facde765cf977e0abb807312fa7fa55ffa83a239446962b209c8a747004e43e03a
|
data/CHANGELOG.md
CHANGED
data/Manifest.txt
CHANGED
@@ -1,17 +1,15 @@
|
|
1
1
|
CHANGELOG.md
|
2
2
|
Manifest.txt
|
3
|
-
NOTES.md
|
4
3
|
README.md
|
5
4
|
Rakefile
|
6
5
|
lib/sportdb/catalogs.rb
|
7
|
-
lib/sportdb/catalogs/
|
8
|
-
lib/sportdb/catalogs/
|
6
|
+
lib/sportdb/catalogs/base.rb
|
7
|
+
lib/sportdb/catalogs/city.rb
|
8
|
+
lib/sportdb/catalogs/club.rb
|
9
|
+
lib/sportdb/catalogs/country.rb
|
10
|
+
lib/sportdb/catalogs/event_info.rb
|
11
|
+
lib/sportdb/catalogs/ground.rb
|
12
|
+
lib/sportdb/catalogs/league.rb
|
13
|
+
lib/sportdb/catalogs/national_team.rb
|
14
|
+
lib/sportdb/catalogs/player.rb
|
9
15
|
lib/sportdb/catalogs/version.rb
|
10
|
-
lib/sportdb/catalogs/wiki_index.rb
|
11
|
-
test/helper.rb
|
12
|
-
test/test_clubs.rb
|
13
|
-
test/test_clubs_history.rb
|
14
|
-
test/test_countries.rb
|
15
|
-
test/test_leagues.rb
|
16
|
-
test/test_national_teams.rb
|
17
|
-
test/test_wiki_index.rb
|
data/README.md
CHANGED
@@ -5,12 +5,11 @@
|
|
5
5
|
* bugs :: [github.com/sportdb/sport.db/issues](https://github.com/sportdb/sport.db/issues)
|
6
6
|
* gem :: [rubygems.org/gems/sportdb-catalogs](https://rubygems.org/gems/sportdb-catalogs)
|
7
7
|
* rdoc :: [rubydoc.info/gems/sportdb-catalogs](http://rubydoc.info/gems/sportdb-catalogs)
|
8
|
-
* forum :: [opensport](http://groups.google.com/group/opensport)
|
9
8
|
|
10
9
|
|
11
10
|
|
12
11
|
|
13
|
-
## Usage
|
12
|
+
## Usage
|
14
13
|
|
15
14
|
Let's use the [/clubs datasets](https://github.com/openfootball/clubs)
|
16
15
|
(1500+ football clubs from around the world)
|
@@ -74,55 +73,6 @@ m[0].name; m[0].city; m[0].country
|
|
74
73
|
# ...
|
75
74
|
```
|
76
75
|
|
77
|
-
Let's print all names that have duplicate (more than one) matching club:
|
78
|
-
|
79
|
-
``` ruby
|
80
|
-
CLUBS.mappings.each do |name, clubs|
|
81
|
-
if clubs.size > 1
|
82
|
-
puts "#{clubs.size} matching clubs for `#{name}`:"
|
83
|
-
clubs.each do |club|
|
84
|
-
puts " - #{club.name}, #{club.city}, #{club.country.name} (#{club.country.key})"
|
85
|
-
end
|
86
|
-
puts
|
87
|
-
end
|
88
|
-
end
|
89
|
-
```
|
90
|
-
|
91
|
-
resulting in:
|
92
|
-
|
93
|
-
```
|
94
|
-
2 matching clubs for `valencia`:
|
95
|
-
- Valencia FC, Léogâne, Haiti (ht)
|
96
|
-
- Valencia CF, Valencia, Spain (es)
|
97
|
-
|
98
|
-
2 matching clubs for `apollon`:
|
99
|
-
- Apollon Limassol FC, , Cyprus (cy)
|
100
|
-
- Apollon Smyrnis FC, Athens, Greece (gr)
|
101
|
-
|
102
|
-
3 matching clubs for `arsenal`:
|
103
|
-
- Arsenal FC, London, England (eng)
|
104
|
-
- Arsenal Tula, Tula, Russia (ru)
|
105
|
-
- Arsenal de Sarandí, Sarandí, Argentina (ar)
|
106
|
-
|
107
|
-
2 matching clubs for `liverpool`:
|
108
|
-
- Liverpool FC, Liverpool, England (eng)
|
109
|
-
- Liverpool Montevideo, Montevideo, Uruguay (uy)
|
110
|
-
|
111
|
-
2 matching clubs for `barcelona`:
|
112
|
-
- FC Barcelona, Barcelona, Spain (es)
|
113
|
-
- Barcelona Guayaquil, Guayaquil, Ecuador (ec)
|
114
|
-
|
115
|
-
3 matching clubs for `nacional`:
|
116
|
-
- CD Nacional Madeira, Funchal, Portugal (pt)
|
117
|
-
- Club Nacional, Asunción, Paraguay (py)
|
118
|
-
- Nacional de Montevideo, Montevideo, Uruguay (uy)
|
119
|
-
|
120
|
-
2 matching clubs for `sanjose`:
|
121
|
-
- San Jose Earthquakes, San Jose, United States (us)
|
122
|
-
- Club Deportivo San José, Oruro, Bolivia (bo)
|
123
|
-
|
124
|
-
...
|
125
|
-
```
|
126
76
|
|
127
77
|
That's it.
|
128
78
|
|
@@ -136,6 +86,5 @@ Use it as you please with no restrictions whatsoever.
|
|
136
86
|
|
137
87
|
## Questions? Comments?
|
138
88
|
|
139
|
-
|
140
|
-
[
|
141
|
-
Thanks!
|
89
|
+
Yes, you can. More than welcome.
|
90
|
+
See [Help & Support »](https://github.com/openfootball/help)
|
data/Rakefile
CHANGED
@@ -8,10 +8,10 @@ Hoe.spec 'sportdb-catalogs' do
|
|
8
8
|
self.summary = "sportdb-catalogs - sport.db (search 'n' find) catalogs for countries, leagues, clubs, national teams, and more"
|
9
9
|
self.description = summary
|
10
10
|
|
11
|
-
self.urls =
|
11
|
+
self.urls = { home: 'https://github.com/sportdb/sport.db' }
|
12
12
|
|
13
13
|
self.author = 'Gerald Bauer'
|
14
|
-
self.email = '
|
14
|
+
self.email = 'gerald.bauer@gmail.com'
|
15
15
|
|
16
16
|
# switch extension to .markdown for gihub formatting
|
17
17
|
self.readme_file = 'README.md'
|
@@ -20,15 +20,13 @@ Hoe.spec 'sportdb-catalogs' do
|
|
20
20
|
self.licenses = ['Public Domain']
|
21
21
|
|
22
22
|
self.extra_deps = [
|
23
|
-
['sportdb-formats',
|
24
|
-
##
|
25
|
-
['
|
26
|
-
['footballdb-leagues', '>= 2020.7.7'], ## for (builtin/default) leagues & cups
|
27
|
-
['footballdb-clubs', '>= 2020.7.7'], ## for (builtin/default) clubs
|
23
|
+
['sportdb-formats', '>= 2.0.0'],
|
24
|
+
['sqlite3'], ## add sqlite for "metal" use (no activerecord etc.)
|
25
|
+
['footballdb-data'] ## add builtin default db
|
28
26
|
]
|
29
27
|
|
30
28
|
self.spec_extras = {
|
31
|
-
required_ruby_version: '>=
|
29
|
+
required_ruby_version: '>= 3.1.0'
|
32
30
|
}
|
33
31
|
|
34
32
|
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
module CatalogDb
|
2
|
+
module Metal
|
3
|
+
|
4
|
+
##
|
5
|
+
# change BaseRecord to Record again
|
6
|
+
## AND PlayerRecord to PlayerBase or PlayerDatabase ?
|
7
|
+
## AND (Catalog)Record to CatalogBase or CatalogDatabase - why? why not?
|
8
|
+
|
9
|
+
|
10
|
+
class BaseRecord ## or just use Base or such - why? why not?
|
11
|
+
|
12
|
+
def self.execute( sql )
|
13
|
+
## puts "==> sql query [#{self.name}]"
|
14
|
+
## puts sql
|
15
|
+
database.execute( sql )
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.tablename=(name) @tablename = name; end
|
19
|
+
def self.tablename() @tablename; end
|
20
|
+
|
21
|
+
def self.columns=(names)
|
22
|
+
## note: auto-add table name to qualify
|
23
|
+
@columns = names.map {|name| "#{self.tablename}.#{name}" }
|
24
|
+
@columns
|
25
|
+
end
|
26
|
+
def self.columns() @columns; end
|
27
|
+
|
28
|
+
def self.count
|
29
|
+
sql = "SELECT count(*) FROM #{self.tablename}"
|
30
|
+
rows = execute( sql )
|
31
|
+
rows[0][0] # e.g. returns [[241]]
|
32
|
+
end
|
33
|
+
|
34
|
+
## helpers from country - use a helper module for includes (share with clubs etc.) - why? why not?
|
35
|
+
# include NameHelper
|
36
|
+
extend SportDb::NameHelper
|
37
|
+
## incl. strip_year( name )
|
38
|
+
## has_year?( name)
|
39
|
+
## strip_lang( name )
|
40
|
+
## normalize( name )
|
41
|
+
|
42
|
+
def self._to_bool( value )
|
43
|
+
if value == 0
|
44
|
+
false
|
45
|
+
elsif value == 1
|
46
|
+
true
|
47
|
+
else
|
48
|
+
## use TypeError - why? why not? or if exits ValueError?
|
49
|
+
raise ArgumentError, "0 or 1 expected for bool in sqlite; got #{value}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
##
|
55
|
+
# todo/check - make _country available to all dbs/records? why? why not?
|
56
|
+
|
57
|
+
def self._country( country )
|
58
|
+
if country.is_a?( String ) || country.is_a?( Symbol )
|
59
|
+
# note: query/find country via catalog db
|
60
|
+
rec = Country.find_by_code( country )
|
61
|
+
rec = Country.find_by_name( country ) if rec.nil?
|
62
|
+
if rec.nil?
|
63
|
+
puts "** !!! ERROR !!! - unknown country >#{country}< - no match found, sorry - add to world/countries.txt in config"
|
64
|
+
exit 1
|
65
|
+
end
|
66
|
+
rec
|
67
|
+
else
|
68
|
+
country ## (re)use country struct - no need to run lookup again
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end # class BaseRecord
|
72
|
+
|
73
|
+
|
74
|
+
class PlayerRecord < BaseRecord
|
75
|
+
def self.database
|
76
|
+
### note: only one database for all derived records/tables!!!
|
77
|
+
## thus MUST use @@ and not @!!!!!
|
78
|
+
## todo - change later to built-in database
|
79
|
+
## or download on request???
|
80
|
+
@@db ||= SQLite3::Database.new( './players.db' )
|
81
|
+
@@db
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.database=(path)
|
85
|
+
puts "==> setting (internal) players db to: >#{path}<"
|
86
|
+
@@db = SQLite3::Database.new( path )
|
87
|
+
pp @@db
|
88
|
+
@@db
|
89
|
+
end
|
90
|
+
end # class PlayerRecord
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
##############
|
95
|
+
### todo (fix) / rename to CatalogRecord - why? why not?
|
96
|
+
class Record < BaseRecord
|
97
|
+
## add db alias why? why not?
|
98
|
+
def self.database
|
99
|
+
### note: only one database for all derived records/tables!!!
|
100
|
+
## thus MUST use @@ and not @!!!!!
|
101
|
+
##
|
102
|
+
## default to built-in via footballdb-data gem for now!!!
|
103
|
+
@@db ||= SQLite3::Database.new( './catalog.db' )
|
104
|
+
@@db
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.database=(path)
|
108
|
+
puts "==> setting (internal) catalog db to: >#{path}<"
|
109
|
+
@@db = SQLite3::Database.new( path )
|
110
|
+
pp @@db
|
111
|
+
@@db
|
112
|
+
end
|
113
|
+
|
114
|
+
###########
|
115
|
+
### share common methods for reuse
|
116
|
+
|
117
|
+
def self._to_league( key )
|
118
|
+
League._record( key )
|
119
|
+
end
|
120
|
+
|
121
|
+
def self._to_country( key )
|
122
|
+
# note: use cached record or (faster) key lookup on fallback
|
123
|
+
Country._record( key )
|
124
|
+
end
|
125
|
+
|
126
|
+
def self._to_city( key ) ### rename; use find_by_key / find_by( key: )
|
127
|
+
# note: use cached record or (faster) key lookup on fallback
|
128
|
+
City._record( key )
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
def self._city( city )
|
133
|
+
if city.is_a?( String ) || city.is_a?( Symbol )
|
134
|
+
# note: query/find country via catalog db
|
135
|
+
recs = City.match_by( name: city )
|
136
|
+
if recs.empty?
|
137
|
+
puts "** !!! ERROR !!! - unknown city >#{city}< - no match found, sorry"
|
138
|
+
exit 1
|
139
|
+
elsif recs.size > 1
|
140
|
+
puts "** !!! ERROR !!! - city >#{city}< - too many matches found (#{recs.size}), sorry"
|
141
|
+
pp recs
|
142
|
+
exit 1
|
143
|
+
end
|
144
|
+
recs[0]
|
145
|
+
else
|
146
|
+
city ## (re)use city struct - no need to run lookup again
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end # class Record
|
150
|
+
|
151
|
+
|
152
|
+
|
153
|
+
end # module Metal
|
154
|
+
end # module CatalogDb
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module CatalogDb
|
2
|
+
module Metal
|
3
|
+
|
4
|
+
class City < Record
|
5
|
+
self.tablename = 'cities'
|
6
|
+
|
7
|
+
self.columns = ['key',
|
8
|
+
'name',
|
9
|
+
'alt_names',
|
10
|
+
'country_key', #3
|
11
|
+
]
|
12
|
+
|
13
|
+
def self.cache() @cache ||= Hash.new; end
|
14
|
+
|
15
|
+
def self._record( key ) ## use _record! as name - why? why not?
|
16
|
+
if (rec = cache[ key ])
|
17
|
+
rec ## return cached
|
18
|
+
else ## query and cache and return
|
19
|
+
rows = execute( <<-SQL )
|
20
|
+
SELECT #{self.columns.join(', ')}
|
21
|
+
FROM cities
|
22
|
+
WHERE cities.key = '#{key}'
|
23
|
+
SQL
|
24
|
+
|
25
|
+
## todo/fix: also assert for rows == 1 AND NOT MULTIPLE records - why? why not?
|
26
|
+
if rows.empty?
|
27
|
+
raise ArgumentError, "city record with key #{key} not found"
|
28
|
+
else
|
29
|
+
_build_city( rows[0] )
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self._build_city( row )
|
35
|
+
## note: cache structs by key (do NOT rebuild duplicates; reuse)
|
36
|
+
cache[ row[0] ] ||= begin
|
37
|
+
city = Sports::City.new(
|
38
|
+
key: row[0],
|
39
|
+
name: row[1],
|
40
|
+
## fix - add alt_names here too
|
41
|
+
country: _to_country( row[3] )
|
42
|
+
)
|
43
|
+
city
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def self._find_by_name( name )
|
49
|
+
rows = execute( <<-SQL )
|
50
|
+
SELECT #{self.columns.join(', ')}
|
51
|
+
FROM cities
|
52
|
+
INNER JOIN city_names ON cities.key = city_names.key
|
53
|
+
WHERE city_names.name = '#{name}'
|
54
|
+
SQL
|
55
|
+
rows
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
## match - always returns an array (with one or more matches)
|
60
|
+
def self.match_by( name: )
|
61
|
+
## note: allow passing in of country key too (auto-counvert)
|
62
|
+
## and country struct too
|
63
|
+
## - country assumes / allows the country key or fifa code for now
|
64
|
+
|
65
|
+
## note: ALWAYS unaccent 1st in normalize
|
66
|
+
## - add upstream - why? why not?
|
67
|
+
# note: returns empty array (e.g. []) if no match and NOT nil
|
68
|
+
nameq = normalize( unaccent(name) )
|
69
|
+
|
70
|
+
rows = _find_by_name( nameq )
|
71
|
+
|
72
|
+
rows.map {|row| _build_city( row )}
|
73
|
+
end
|
74
|
+
end # class City
|
75
|
+
|
76
|
+
end # module Metal
|
77
|
+
end # module CatalogDb
|
78
|
+
|
@@ -0,0 +1,104 @@
|
|
1
|
+
module CatalogDb
|
2
|
+
module Metal
|
3
|
+
|
4
|
+
class Club < Record
|
5
|
+
self.tablename = 'clubs'
|
6
|
+
|
7
|
+
self.columns = ['key',
|
8
|
+
'name',
|
9
|
+
'code',
|
10
|
+
'city', #3
|
11
|
+
'district', #4
|
12
|
+
'country_key', #5
|
13
|
+
]
|
14
|
+
|
15
|
+
def self._build_club( row )
|
16
|
+
## note: cache structs by key (do NOT rebuild duplicates; reuse)
|
17
|
+
@cache ||= Hash.new
|
18
|
+
@cache[ row[0] ] ||= begin
|
19
|
+
club = Sports::Club.new(
|
20
|
+
key: row[0],
|
21
|
+
name: row[1],
|
22
|
+
code: row[2] )
|
23
|
+
|
24
|
+
club.city = row[3] ## might be nil
|
25
|
+
club.district = row[4]
|
26
|
+
## note: country for now NOT supported
|
27
|
+
## via keyword on init!!!
|
28
|
+
## fix - why? why not?
|
29
|
+
club.country = row[5] ? _to_country( row[5] ) : nil
|
30
|
+
club
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def self._find_by_name( name )
|
37
|
+
rows = execute( <<-SQL )
|
38
|
+
SELECT #{self.columns.join(', ')}
|
39
|
+
FROM clubs
|
40
|
+
INNER JOIN club_names ON clubs.key = club_names.key
|
41
|
+
WHERE club_names.name = '#{name}'
|
42
|
+
SQL
|
43
|
+
rows
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
def self._find_by_name_and_country( name, country_or_countries )
|
48
|
+
sql = <<-SQL
|
49
|
+
SELECT #{self.columns.join(', ')}
|
50
|
+
FROM clubs
|
51
|
+
INNER JOIN club_names ON clubs.key = club_names.key
|
52
|
+
WHERE club_names.name = '#{name}' AND
|
53
|
+
SQL
|
54
|
+
|
55
|
+
## check if single country or n countries
|
56
|
+
if country_or_countries.is_a?(Array)
|
57
|
+
countries = country_or_countries
|
58
|
+
## add (single) quotes and join with comma
|
59
|
+
## e.g. 'at','de'
|
60
|
+
country_list = countries.map {|country| %Q<'#{country}'>}.join(',')
|
61
|
+
sql << "clubs.country_key in (#{country_list})\n"
|
62
|
+
else ## assume string/symbol (single country key)
|
63
|
+
country = country_or_countries
|
64
|
+
sql << "clubs.country_key = '#{country}'\n"
|
65
|
+
end
|
66
|
+
|
67
|
+
rows = execute( sql )
|
68
|
+
rows
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
|
73
|
+
## match - always returns an array (with one or more matches)
|
74
|
+
def self.match_by( name:,
|
75
|
+
country: nil )
|
76
|
+
## note: allow passing in of country key too (auto-counvert)
|
77
|
+
## and country struct too
|
78
|
+
## - country assumes / allows the country key or fifa code for now
|
79
|
+
|
80
|
+
## note: ALWAYS unaccent 1st in normalize
|
81
|
+
## - add upstream - why? why not?
|
82
|
+
# note: returns empty array (e.g. []) if no match and NOT nil
|
83
|
+
nameq = normalize( unaccent(name) )
|
84
|
+
|
85
|
+
## todo - check for empty array passed in?
|
86
|
+
rows = if country
|
87
|
+
### always wrap country in array
|
88
|
+
countries = country.is_a?(Array) ? country : [country]
|
89
|
+
country_keys = countries.map {|country| _country( country ).key }
|
90
|
+
## unwrap key if array with single country key
|
91
|
+
countryq = country_keys.size == 1 ? country_keys[0] : country_keys
|
92
|
+
|
93
|
+
_find_by_name_and_country( nameq, countryq )
|
94
|
+
else
|
95
|
+
_find_by_name( nameq )
|
96
|
+
end
|
97
|
+
|
98
|
+
rows.map {|row| _build_club( row )}
|
99
|
+
end
|
100
|
+
end # class Club
|
101
|
+
|
102
|
+
end # module Metal
|
103
|
+
end # module CatalogDb
|
104
|
+
|
@@ -0,0 +1,147 @@
|
|
1
|
+
|
2
|
+
module CatalogDb
|
3
|
+
module Metal
|
4
|
+
|
5
|
+
class Country < Record
|
6
|
+
self.tablename = 'countries'
|
7
|
+
|
8
|
+
self.columns = ['key',
|
9
|
+
'name',
|
10
|
+
'code',
|
11
|
+
'tags',
|
12
|
+
'alt_names']
|
13
|
+
|
14
|
+
def self.cache() @cache ||= Hash.new; end
|
15
|
+
|
16
|
+
|
17
|
+
def self._record( key ) ## use _record! as name - why? why not?
|
18
|
+
if (rec = cache[ key ])
|
19
|
+
rec ## return cached
|
20
|
+
else ## query and cache and return
|
21
|
+
rows = execute( <<-SQL )
|
22
|
+
SELECT #{self.columns.join(', ')}
|
23
|
+
FROM countries
|
24
|
+
WHERE countries.key = '#{key}'
|
25
|
+
SQL
|
26
|
+
|
27
|
+
## todo/fix: also assert for rows == 1 AND NOT MULTIPLE records - why? why not?
|
28
|
+
if rows.empty?
|
29
|
+
raise ArgumentError, "country record with key #{key} not found"
|
30
|
+
else
|
31
|
+
_build_country( rows[0] )
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def self._build_country( row )
|
38
|
+
## note: cache structs by key (do NOT rebuild duplicates; reuse)
|
39
|
+
cache[ row[0] ] ||= begin
|
40
|
+
## note: split tags & alt names (PLUS remove leading//trailing spaces)
|
41
|
+
tags = row[3].split(',').map {|tag| tag.strip }
|
42
|
+
alt_names = row[4].split('|').map {|alt_name| alt_name.strip }
|
43
|
+
country = Sports::Country.new(
|
44
|
+
key: row[0],
|
45
|
+
name: row[1],
|
46
|
+
code: row[2],
|
47
|
+
tags: tags
|
48
|
+
)
|
49
|
+
country.alt_names = alt_names
|
50
|
+
country
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
## fix/todo: add find_by (alias for find_by_name/find_by_code)
|
56
|
+
def self.find_by_code( code )
|
57
|
+
q = code.to_s.downcase ## allow symbols (and always downcase e.g. AUT to aut etc.)
|
58
|
+
|
59
|
+
## note: results in
|
60
|
+
## Côte d'Ivoire => côte d'ivoire
|
61
|
+
## quote will break sql!!!
|
62
|
+
## remove - spaces etc.
|
63
|
+
## for now remove only single quote (will break sql) - add more?
|
64
|
+
##
|
65
|
+
## or use escape_sql_string - possible??
|
66
|
+
|
67
|
+
q = q.gsub( /[']/, '' )
|
68
|
+
|
69
|
+
|
70
|
+
rows = execute( <<-SQL )
|
71
|
+
SELECT #{self.columns.join(', ')}
|
72
|
+
FROM countries
|
73
|
+
INNER JOIN country_codes ON countries.key = country_codes.key
|
74
|
+
WHERE country_codes.code = '#{q}'
|
75
|
+
SQL
|
76
|
+
|
77
|
+
if rows.empty?
|
78
|
+
nil
|
79
|
+
else
|
80
|
+
## todo/fix - raise error if more than once record found!!!
|
81
|
+
_build_country( rows[0] )
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
##
|
87
|
+
##
|
88
|
+
## note - need to escape name ?
|
89
|
+
## e.g. Côte d'Ivoire
|
90
|
+
## make sure normalize removes single quotes (')!!!
|
91
|
+
|
92
|
+
def self.find_by_name( name )
|
93
|
+
q = normalize( unaccent( name.to_s )) ## allow symbols too (e.g. use to.s first)
|
94
|
+
|
95
|
+
rows = execute( <<-SQL )
|
96
|
+
SELECT #{self.columns.join(', ')}
|
97
|
+
FROM countries
|
98
|
+
INNER JOIN country_names ON countries.key = country_names.key
|
99
|
+
WHERE country_names.name = '#{q}'
|
100
|
+
SQL
|
101
|
+
|
102
|
+
if rows.empty?
|
103
|
+
nil
|
104
|
+
else
|
105
|
+
## todo/fix - raise error if more than once record found!!!
|
106
|
+
_build_country( rows[0] )
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
def self.find_by_name_or_code( q )
|
112
|
+
name = normalize( unaccent( q.to_s )) ## allow symbols too (e.g. use to.s first)
|
113
|
+
|
114
|
+
code = q.to_s.downcase ## allow symbols (and always downcase e.g. AUT to aut etc.)
|
115
|
+
## note: results in
|
116
|
+
## Côte d'Ivoire => côte d'ivoire
|
117
|
+
## quote will break sql!!!
|
118
|
+
## remove - spaces etc.
|
119
|
+
## for now remove only single quote (will break sql) - add more?
|
120
|
+
##
|
121
|
+
## or use escape_sql_string - possible??
|
122
|
+
code = code.gsub( /[']/, '' )
|
123
|
+
|
124
|
+
|
125
|
+
rows = execute( <<-SQL )
|
126
|
+
SELECT #{self.columns.join(', ')}
|
127
|
+
FROM countries
|
128
|
+
INNER JOIN country_names ON countries.key = country_names.key
|
129
|
+
WHERE country_names.name = '#{name}'
|
130
|
+
UNION
|
131
|
+
SELECT #{self.columns.join(', ')}
|
132
|
+
FROM countries
|
133
|
+
INNER JOIN country_codes ON countries.key = country_codes.key
|
134
|
+
WHERE country_codes.code = '#{code}'
|
135
|
+
SQL
|
136
|
+
|
137
|
+
if rows.empty?
|
138
|
+
nil
|
139
|
+
else
|
140
|
+
## todo/fix - raise error if more than once record found!!!
|
141
|
+
_build_country( rows[0] )
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
end # class Country
|
146
|
+
end # module Metal
|
147
|
+
end # module CatalogDb
|