sportdb-models_v2 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ebc3350f86107513b943494184c6974a0398b43935f76be2fc4577bb3fc89eaa
4
+ data.tar.gz: 429f8bbca4edf4a5ca2c1dda64a6c0a9ac418344bca8da3bd2edcc5fc4531272
5
+ SHA512:
6
+ metadata.gz: 54b1eda6275a80eba81062324756e028ce9d99c8e56164fd1591ec3095c7ea6e7fd61dc63799c0d8d0b5a3b32d42875739923f5a50f623de80804539f34c08bb
7
+ data.tar.gz: 287499f53e769175bb5a77da7b1b4a311161391d4252b04b78a5563fe6097e25a02d0e01032cb77a147a2fc15b603ba19a2a942a863ccb6d2fa6f461f6676532
data/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+
2
+ ### 0.0.1 / 2025-04-07
3
+
4
+ * Everything is new. First release
data/Manifest.txt ADDED
@@ -0,0 +1,18 @@
1
+ CHANGELOG.md
2
+ Manifest.txt
3
+ README.md
4
+ Rakefile
5
+ lib/sportdb/models_v2.rb
6
+ lib/sportdb/models_v2/deleter.rb
7
+ lib/sportdb/models_v2/match_reader.rb
8
+ lib/sportdb/models_v2/models/event.rb
9
+ lib/sportdb/models_v2/models/forward.rb
10
+ lib/sportdb/models_v2/models/league.rb
11
+ lib/sportdb/models_v2/models/match.rb
12
+ lib/sportdb/models_v2/models/team.rb
13
+ lib/sportdb/models_v2/schema.rb
14
+ lib/sportdb/models_v2/stats.rb
15
+ lib/sportdb/models_v2/sync/event.rb
16
+ lib/sportdb/models_v2/sync/league.rb
17
+ lib/sportdb/models_v2/sync/match.rb
18
+ lib/sportdb/models_v2/version.rb
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # sportdb-models gem - sport.db schema 'n' models for easy (re)use
2
+
3
+
4
+
5
+ * home :: [github.com/sportdb/sport.db](https://github.com/sportdb/sport.db)
6
+ * bugs :: [github.com/sportdb/sport.db/issues](https://github.com/sportdb/sport.db/issues)
7
+ * gem :: [rubygems.org/gems/sportdb-models](https://rubygems.org/gems/sportdb-models)
8
+ * rdoc :: [rubydoc.info/gems/sportdb-models](http://rubydoc.info/gems/sportdb-models)
9
+
10
+
11
+
12
+ ## Usage Models
13
+
14
+ ![](sportdb-models.png)
15
+
16
+
17
+ ## License
18
+
19
+ The `sportdb-models` scripts are dedicated to the public domain.
20
+ Use it as you please with no restrictions whatsoever.
21
+
22
+
23
+ ## Questions? Comments?
24
+
25
+ Yes, you can. More than welcome.
26
+ See [Help & Support »](https://github.com/openfootball/help)
27
+
28
+
data/Rakefile ADDED
@@ -0,0 +1,35 @@
1
+ require 'hoe'
2
+ require './lib/sportdb/models_v2/version.rb'
3
+
4
+
5
+ Hoe.spec 'sportdb-models_v2' do
6
+
7
+ self.version = SportDbV2::Module::Models::VERSION
8
+
9
+ self.summary = "sportdb-models_v2 - sport.db schema 'n' models for easy (re)use"
10
+ self.description = summary
11
+
12
+ self.urls = { home: 'https://github.com/sportdb/sport.db' }
13
+
14
+ self.author = 'Gerald Bauer'
15
+ self.email = 'gerald.bauer@gmail.com'
16
+
17
+ # switch extension to .markdown for gihub formatting
18
+ # -- NB: auto-changed when included in manifest
19
+ self.readme_file = 'README.md'
20
+ self.history_file = 'CHANGELOG.md'
21
+
22
+ self.extra_deps = [
23
+ ['cocos'],
24
+ ## add props & logutils (or props/logutils-activerecord good enough?) - why? why not?
25
+ ['props-activerecord'], ## pulls in activerecord
26
+ ['logutils-activerecord'],
27
+ ['sportdb-quick'],
28
+ ]
29
+
30
+ self.licenses = ['Public Domain']
31
+
32
+ self.spec_extras = {
33
+ required_ruby_version: '>= 3.1.0'
34
+ }
35
+ end
@@ -0,0 +1,51 @@
1
+
2
+ module SportDbV2
3
+
4
+ class Deleter
5
+ ######
6
+ # note: make models available in sportdb module by default with namespace
7
+ # e.g. lets you use Team instead of Model::Team
8
+ ## include Models
9
+
10
+ def run
11
+ puts "todo - add delete all tables here"
12
+ =begin
13
+ # for now delete all tables
14
+
15
+ ## stats
16
+ AlltimeStandingEntry.delete_all
17
+ AlltimeStanding.delete_all
18
+ GroupStandingEntry.delete_all
19
+ GroupStanding.delete_all
20
+
21
+
22
+ Goal.delete_all
23
+
24
+ Match.delete_all
25
+ Event.delete_all
26
+ EventTeam.delete_all
27
+ EventGround.delete_all
28
+ Group.delete_all
29
+ GroupTeam.delete_all
30
+ Stage.delete_all
31
+ StageTeam.delete_all
32
+ Round.delete_all
33
+ Badge.delete_all
34
+
35
+ Lineup.delete_all
36
+
37
+ Team.delete_all
38
+
39
+ League.delete_all
40
+ Season.delete_all
41
+
42
+ Ground.delete_all # stadiums
43
+
44
+ Assoc.delete_all # associations / organizations
45
+ AssocAssoc.delete_all # associations / organizations
46
+ =end
47
+ end
48
+
49
+ end # class Deleter
50
+
51
+ end # module SportDbV2
@@ -0,0 +1,108 @@
1
+
2
+ module SportDbV2
3
+ class MatchReader
4
+
5
+
6
+ def self.read( path ) ## use - rename to read_file or from_file etc. - why? why not?
7
+ txt = File.open( path, 'r:utf-8' ) {|f| f.read }
8
+ parse( txt )
9
+ end
10
+
11
+ def self.parse( txt )
12
+ new( txt ).parse
13
+ end
14
+
15
+
16
+
17
+ include Logging
18
+
19
+
20
+ def initialize( txt )
21
+ @errors = []
22
+ @outline = QuickLeagueOutline.parse( txt )
23
+ end
24
+
25
+ attr_reader :errors
26
+ def errors?() @errors.size > 0; end
27
+
28
+
29
+ def parse( season: nil )
30
+ ## note: every (new) read call - resets errors list to empty
31
+ @errors = []
32
+
33
+ ### todo/fix - add back season filter - maybe upstream to outline - why? why not?
34
+ ########
35
+ # step 1 - prepare secs
36
+ # -- filter seasons if filter present
37
+ #
38
+ # secs = filter_secs( sec, season: season ) if season
39
+
40
+
41
+ @outline.each_sec do |sec| ## sec(tion)s
42
+ ### move season parse into outline upstream - why? why not?
43
+ season = Season.parse( sec.season ) ## convert (str) to season obj!!!
44
+ lines = sec.lines
45
+
46
+ ## quick hack - assume "Regular" or "Regular Season"
47
+ ## as default stage (thus, no stage)
48
+ stage = sec.stage ## check if returns nil or empty string?
49
+
50
+ stage = nil if stage && ['regular',
51
+ 'regular season',
52
+ 'regular stage',
53
+ ].include?( stage.downcase )
54
+
55
+
56
+ ### todo/fix - remove "legacy/old" requirement for start date!!!!
57
+ start = if season.year?
58
+ Date.new( season.start_year, 1, 1 )
59
+ else
60
+ Date.new( season.start_year, 7, 1 )
61
+ end
62
+
63
+
64
+ parser = MatchParser.new( lines,
65
+ start ) ## note: keep season start_at date for now (no need for more specific stage date need for now)
66
+
67
+ auto_conf_teams, matches, rounds, groups = parser.parse
68
+
69
+ ## auto-add "upstream" errors from parser
70
+ @errors += parser.errors if parser.errors?
71
+
72
+ puts ">>> #{auto_conf_teams.size} teams:"
73
+ pp auto_conf_teams
74
+ puts ">>> #{matches.size} matches:"
75
+ ## pp matches
76
+ puts ">>> #{rounds.size} rounds:"
77
+ pp rounds
78
+ puts ">>> #{groups.size} groups:"
79
+ pp groups
80
+
81
+
82
+ puts "league:"
83
+ league_rec = Sync::League.find_or_create( name: sec.league )
84
+ pp league_rec
85
+
86
+ event_rec = Sync::Event.find_or_create( league_rec: league_rec,
87
+ season: season )
88
+ pp event_rec
89
+
90
+
91
+
92
+ matches.each do |match|
93
+ ## note: pass along stage (if present): stage - optional from heading!!!!
94
+ ### use | instead of comma (,) for stage separator in (full) round - why? why not?
95
+ match = match.update( stage: stage ) if stage
96
+
97
+ match_rec = Sync::Match.create!( match, event_rec: event_rec )
98
+ pp match
99
+ pp match_rec
100
+ end
101
+ end
102
+
103
+ true ## success/ok
104
+ end # method parse
105
+
106
+
107
+ end # class MatchReader
108
+ end # module SportDbV2
@@ -0,0 +1,44 @@
1
+
2
+ module SportDbV2
3
+ module Model
4
+
5
+
6
+ class Event < ActiveRecord::Base
7
+
8
+ belongs_to :league
9
+
10
+ ## has_many :matches, -> { order('pos') }, class_name: 'Match'
11
+ def matches
12
+ Match.where( league_id: league_id, season: season )
13
+ end
14
+
15
+
16
+ ## use event_rounds - why? why not?
17
+ ## round ALWAYS specific to event
18
+ has_many :rounds, class_name: 'EventRound'
19
+
20
+ ## note - pass teams through to get shared team record
21
+ ## for "local" event name use event_teams - why? why not?
22
+ has_many :event_teams, class_name: 'EventTeam'
23
+ has_many :teams, :through => :event_teams
24
+ end # class Event
25
+
26
+
27
+ class EventTeam < ActiveRecord::Base
28
+ self.table_name = 'event_teams'
29
+
30
+ belongs_to :event
31
+ belongs_to :team
32
+ end # class EventTeam
33
+
34
+
35
+ class EventRound < ActiveRecord::Base
36
+ self.table_name = 'event_rounds'
37
+
38
+ belongs_to :event
39
+ has_many :matches, class_name: 'Match', foreign_key: 'event_round_id'
40
+ end # class EventRound
41
+
42
+
43
+ end # module Model
44
+ end # module SportDb
@@ -0,0 +1,22 @@
1
+
2
+ ### forward references
3
+ ## require first to resolve circular references
4
+
5
+ module SportDbV2
6
+ module Model
7
+
8
+ Prop = ConfDb::Model::Prop
9
+
10
+ ## note: for now only team and league use worlddb tables
11
+ # e.g. with belongs_to assoc (country,region)
12
+
13
+ class Team < ActiveRecord::Base ; end
14
+ class League < ActiveRecord::Base ; end
15
+
16
+ end
17
+
18
+ ## add backwards compatible n convenience namespace
19
+ Models = Model
20
+ end # module SportDb
21
+
22
+
@@ -0,0 +1,20 @@
1
+ module SportDbV2
2
+ module Model
3
+
4
+
5
+ class League < ActiveRecord::Base
6
+
7
+ ## leagues also used for conferences, world series, cups, etc.
8
+ #
9
+ ## league (or cup/conference/series/etc.) + season (or year) = event
10
+
11
+ has_many :events
12
+ has_many :seasons, :through => :events
13
+
14
+ belongs_to :country, :class_name => 'WorldDb::Model::Country', :foreign_key => 'country_id'
15
+
16
+ end # class League
17
+
18
+
19
+ end # module Model
20
+ end # module SportDbV2
@@ -0,0 +1,31 @@
1
+
2
+ module SportDbV2
3
+ module Model
4
+
5
+
6
+ class Match < ActiveRecord::Base
7
+
8
+ self.table_name = 'matches'
9
+
10
+ belongs_to :team1, class_name: 'Team', foreign_key: 'team1_id'
11
+ belongs_to :team2, class_name: 'Team', foreign_key: 'team2_id'
12
+
13
+ def team1_name() team1.name; end
14
+ def team2_name() team2.name; end
15
+
16
+
17
+ belongs_to :league
18
+
19
+ ## todo - add via
20
+ ## belongs_to :event
21
+ def event
22
+ ## check if where always returns array or single record too??
23
+ Event.where( league_id: league_id, season: season )
24
+ end
25
+ belongs_to :event_round # round is optional
26
+
27
+
28
+ end # class Match
29
+
30
+ end # module Model
31
+ end # module SportDbV2
@@ -0,0 +1,40 @@
1
+
2
+ module SportDbV2
3
+ module Model
4
+
5
+
6
+ class Team < ActiveRecord::Base
7
+
8
+ has_many :home_matches, class_name: 'Match', foreign_key: 'team1_id'
9
+ has_many :away_matches, class_name: 'Match', foreign_key: 'team2_id'
10
+
11
+ ## note: for now allow any key and code
12
+ ## validates :key, format: { with: TEAM_KEY_RE, message: TEAM_KEY_MESSAGE }
13
+ ## validates :code, format: { with: TEAM_CODE_RE, message: TEAM_CODE_MESSAGE }, allow_nil: true
14
+
15
+ has_many :event_teams, class_name: 'EventTeam' # join table (events+teams)
16
+ has_many :events, :through => :event_teams
17
+
18
+ ### fix!!! - how to do it with has_many macro? use finder_sql?
19
+ ## finder_sql is depreciated in Rails 4!!!
20
+ # use -> { where() } etc. -- try it if it works
21
+ ## keep as is! best solution ??
22
+ ## a discussion here -> https://github.com/rails/rails/issues/9726
23
+ ## a discussion here (not really helpful) -> http://stackoverflow.com/questions/2125440/activerecord-has-many-where-two-columns-in-table-a-are-primary-keys-in-table-b
24
+
25
+ def matches
26
+ Match.where( 'team1_id = ? or team2_id = ?', id, id ).order( 'date' )
27
+ end
28
+
29
+ def upcoming_matches
30
+ Match.where( 'team1_id = ? or team2_id = ?', id, id ).where( 'date > ?', Date.today ).order( 'date' )
31
+ end
32
+
33
+ def past_matches
34
+ Match.where( 'team1_id = ? or team2_id = ?', id, id ).where( 'date < ?', Date.today ).order( 'date desc' )
35
+ end
36
+ end # class Team
37
+
38
+
39
+ end # module Model
40
+ end # module SportDbV2
@@ -0,0 +1,260 @@
1
+
2
+ ## check/todos:
3
+ # for start/end_date
4
+ # use first/last_date for auto calculation - why? why not?
5
+ #
6
+
7
+
8
+ module SportDbV2
9
+
10
+ class CreateDb
11
+
12
+ def up
13
+ ActiveRecord::Schema.define do
14
+
15
+
16
+ create_table :leagues do |t| ## also for cups/conferences/tournaments/world series/etc.
17
+ # t.string :key, null: false
18
+ t.string :name, null: false # e.g. Premier League, Deutsche Bundesliga, World Cup, Champions League, etc.
19
+ ## t.string :alt_names # comma separated list of alt names / synonyms
20
+
21
+ ## t.references :country, index: false ## optional for now ### todo: create "virtual" country for international leagues e.g. use int? or world (ww?)/europe (eu)/etc. similar? already taken??
22
+
23
+ ## fix: rename to :clubs from :club - why? why not?
24
+ ## fix: rename to :intl from :international - why? why not? shorter? better?
25
+ ## todo/check: flip clup to league flag? why? why not?
26
+ ## t.boolean :clubs, null: false, default: false # club teams or national teams?
27
+ ## t.boolean :intl, null: false, default: false # national league or international?
28
+ ## t.boolean :cup, null: false, default: false ## or regular season league?? use a tournament type field with enums - why? why not?
29
+
30
+ ## t.integer :level ## use tier? e.g. level 1, level 2, etc.
31
+
32
+ ## t.integer :start_year
33
+ ## t.integer :end_year
34
+
35
+ ## todo: add t.boolean :national flag? for national teams?
36
+ t.timestamps
37
+ end
38
+ add_index :leagues, :name, unique: true
39
+
40
+
41
+
42
+ create_table :teams do |t|
43
+ ## for now only name required
44
+
45
+
46
+ ## t.string :key, null: false # import/export key
47
+ t.string :name, null: false # "canonical" unique name
48
+
49
+ ## t.string :code # three letter code (short name)
50
+ ## t.string :alt_names # comma separated list of alt names / synonyms
51
+
52
+ ## t.references :country, index: false
53
+ ## t.references :city, index: false # note: city is optional (should be required for clubs e.g. non-national teams)
54
+ ## t.references :district, index: false # note: optional district (in city)
55
+
56
+ ## todo/check/fix: use team type or such e.g. club/national/etc. - why? why not?
57
+ ### or remove and add virtual attribute in model instead - why? why not?
58
+ ## t.boolean :club, null: false, default: false # is it a club (not a national team)?
59
+ ## t.boolean :national, null: false, default: false # is it a national selection team (not a club)?
60
+
61
+ ## todo/fix: add team reference for a and b team!!!!
62
+
63
+
64
+ ## t.integer :start_year # founding year -fix change to start_year / founded - why? why not?
65
+ ## t.integer :end_year
66
+ ## add more? - start_year2, end_year2 - why? why not?
67
+ # e.g. founded = 1946, 2013 (refounded)
68
+ # dissolved = 1997
69
+
70
+ ## t.string :address
71
+ ## t.string :web
72
+
73
+ ## t.string :comments
74
+
75
+ t.timestamps
76
+ end
77
+ add_index :teams, :name, unique: true
78
+
79
+
80
+
81
+
82
+ create_table :events do |t|
83
+ ## t.string :key, null: false # import/export key
84
+ t.references :league, null: false, index: false
85
+ t.string :season, null: false ## e.g. 2024/25 or 2024
86
+
87
+ t.string :name ## , null: false # league name for this season
88
+
89
+ t.date :start_date # note: only use date (w/o time)
90
+ t.date :end_date # note: only use date (w/o time)
91
+
92
+ ## t.integer :num ## optional series counter e.g. World Cup No. 2, Bundesliga No. 43 etc. etc.
93
+
94
+ ## t.boolean :team3, null: false, default: true ## e.g. Champions League has no 3rd place (only 1st and 2nd/final)
95
+ ## todo: add league/cup flag/flags or to league itself?
96
+ ## or add add a tournament type field - why? why not?
97
+
98
+ ## auto-added flag (e.g. start_at n end_at dates got calculated)
99
+ ## if auto-added flag is false - do NOT auto-update start_at, end_at etc.
100
+ ## t.boolean :auto, null: false, default: true
101
+
102
+
103
+ #### track 1-n sources (from repos) - # todo move to its own table later
104
+ ## NB: relative to event.yml - use mapper to "resolve" to full path w/ repo; use league+season keys
105
+ # t.string :sources # e.g. cup or bl,bl_ii # NB: for now store all in on string separated by comma
106
+ # t.string :config # e.g. cup or bl # e.g assumes cup.yml, bl.yml etc. for now
107
+
108
+
109
+ t.timestamps
110
+ end
111
+ add_index :events, [:league_id, :season], unique: true
112
+
113
+
114
+ ## add_index :events, :key, unique: true
115
+
116
+ # todo: remove id from join table (without extra fields)? why?? why not??
117
+ create_table :event_teams do |t|
118
+ t.references :event, null: false, index: false ## Note: do NOT auto-add index
119
+ t.references :team, null: false, index: false ## Note: do NOT auto-add index
120
+ t.string :name ##, null: false ## team name in use for this event (season)
121
+
122
+ t.timestamps
123
+ end
124
+ add_index :event_teams, [:event_id, :team_id], unique: true
125
+
126
+
127
+ create_table :event_rounds do |t|
128
+ t.references :event, null: false, index: false ## Note: do NOT auto-add index
129
+ t.string :name, null: false
130
+ ## t.integer :pos, null: false ## use only for "internal" sort order (defaults to insertion order)
131
+
132
+ ## t.integer :num ## optional match day/week number
133
+ ## t.string :key ## optional match day/week number key (as string)
134
+
135
+ ## add new table stage/stages for grouping rounds in group rounds and playoff rounds, for example???
136
+ ## # "regular" season (group) matches or post-season (playoff) knockouts (k.o's)
137
+ ## t.boolean :knockout, null: false, default: false
138
+ ## todo: add leg (e.g. leg1, leg2, etc. why? why not?)
139
+ t.date :start_date # note: only use date (w/o time) - fix: change to start_date!!!
140
+ t.date :end_date # note: only use date (w/o time) - fix: change to end_date!!!
141
+ ## t.date :start_date2 # note: only use date (w/o time) - fix: change to start_date!!!
142
+ ## t.date :end_date2 # note: only use date (w/o time) - fix: change to end_date!!!
143
+
144
+ ## add last_date/first-date(auto) - for "real" last and first dates - auto-added - why? why not?
145
+
146
+ ## auto-added flag (e.g. start_at n end_at dates got calculated)
147
+ ## if auto-added flag is false - do NOT auto-update start_at, end_at etc.
148
+ ## t.boolean :auto, null: false, default: true
149
+
150
+ t.timestamps
151
+ end
152
+ add_index :event_rounds, [:event_id, :name], unique: true
153
+
154
+
155
+
156
+
157
+ create_table :matches do |t|
158
+ ## t.string :key # import/export key
159
+ t.references :league, null: false, index: false
160
+ t.string :season, null: false
161
+
162
+
163
+ ## t.integer :pos, null: false ## note: use only for "internal" sort order (defaults to insertion order)
164
+ ## t.integer :num ## optional - "event global" match number e.g. World Cup - Match 1, Match 2, etc.
165
+
166
+
167
+ t.references :team1, null: false, index: false ## Note: do NOT auto-add index
168
+ t.references :team2, null: false, index: false ## Note: do NOT auto-add index
169
+
170
+ ## add team1_name and team2_name - for alternative name - why? why not?
171
+
172
+ ## event same as league+season - keep - why? why not?
173
+ ## todo/fix -- use composite key in event ("upstream")!!!!
174
+ ## do NOT duplicate - why? why not?
175
+ ## t.references :event, null: false, index: false
176
+ t.references :event_round, index: false ## Note: do NOT auto-add index
177
+
178
+ ## t.references :group, index: false ## Note: do NOT auto-add index -- group is optional
179
+ ## t.references :stage, index: false # optional - regular seasion / champions round etc.
180
+
181
+ ## "inline" helper keys auto-populate for easier "no-join/single-table" queries
182
+ ## t.string :team1_key
183
+ ## t.string :team2_key
184
+ ## t.string :event_key
185
+ ## t.string :round_key
186
+ ## t.integer :round_num ## e.g. 1,2,3 for match day/match week
187
+ ## t.string :group_key
188
+ ## t.string :stage_key
189
+
190
+
191
+ t.date :date # optional play date - todo/fix: split into play_date AND play_time!!!
192
+ ## note: activerecord(rails) will auto-add date (2000-01-01) to hours/time!!!
193
+ ## work around - save a string type!!!!
194
+ ## e.g. 19:00 etc.
195
+ ## t.time :time
196
+ t.string :time
197
+
198
+
199
+ ## t.boolean :postponed, null: false, default: false
200
+ ## t.date :date2 # optional old date (when postponed)
201
+ ## t.date :date3 # optional old date (when postponed twice)
202
+
203
+ t.string :status ## optional match status - note: uses UPCASE string constants for now
204
+ ## e.g. CANCELLED / ABANDONED / REPLAY / AWARDED / POSTPONED / etc.
205
+
206
+
207
+ ## t.references :ground, index: false # optional - stadium (lets you get city,region,country,etc)
208
+ ## t.references :city, index: false # optional - convenience for ground.city_id ???
209
+
210
+ ## change home to neutral - why? why not?
211
+ ## t.boolean :home, null: false, default: true # is team1 play at home or neutral (that is, at its home stadium)
212
+ ## t.boolean :knockout, null: false, default: false
213
+
214
+ t.integer :score1 ## "generic" score
215
+ t.integer :score2 ## "generic" score - undetermined - might be a.e.t or full time
216
+
217
+ ## add score1_agg ??
218
+ ## score2_agg
219
+ t.integer :score1ht # half time (45min)
220
+ t.integer :score2ht # half time
221
+ t.integer :score1ft # full time (90min)
222
+ t.integer :score2ft # full time
223
+
224
+ ## add flag for aet? (or after sudden death goal/golden goal or such?)
225
+ ## add score_note or such - why? why not?
226
+ t.integer :score1et # extratime (120min) - team 1
227
+ t.integer :score2et # extratime - team 2
228
+ t.integer :score1p # penalty - team 1 (opt)
229
+ t.integer :score2p # penalty - team 2 (opt) elfmeter (opt)
230
+
231
+ ## t.references :next_match, index: false ## Note: do NOT auto-add index -- for hinspiel bei rueckspiel in knockout match
232
+ ## t.references :prev_match, index: false ## Note: do NOT auto-add index
233
+
234
+ ## t.integer :winner # 1,2,0,nil calculate on save - "real" winner (after 90 or extra time or penalty, aggregated first+second leg?)
235
+ ## t.integer :winner90 # 1,2,0,nil calculate on save - winner after 90 mins (or regugular play time depending on sport - add alias or find a better name!)
236
+
237
+ ## t.string :comments
238
+
239
+ t.timestamps
240
+ end
241
+
242
+ =begin
243
+ add_index :matches, :key, unique: true
244
+ add_index :matches, :event_id # fk event_id index
245
+ add_index :matches, :round_id # fk round_id index
246
+ add_index :matches, :group_id # fk group_id index
247
+ add_index :matches, :next_match_id # fk next_match_id index
248
+ add_index :matches, :prev_match_id # fk next_match_id index
249
+ add_index :matches, :team1_id
250
+ add_index :matches, :team2_id
251
+ =end
252
+
253
+
254
+ end # Schema.define
255
+ end # method up
256
+
257
+
258
+ end # class CreateDb
259
+
260
+ end # module SportDbV2
@@ -0,0 +1,16 @@
1
+
2
+ module SportDbV2
3
+
4
+ class Stats
5
+ include Models
6
+
7
+ def tables
8
+ puts " #{League.count} leagues"
9
+ puts " #{Event.count} events (league+season) / #{EventRound.count} event_rounds"
10
+ puts " #{Team.count} teams"
11
+ puts " #{Match.count} matches"
12
+ end
13
+
14
+ end # class Stats
15
+
16
+ end # module SportDbV2
@@ -0,0 +1,26 @@
1
+ module SportDbV2
2
+ module Sync
3
+
4
+ class Event
5
+ def self.find_or_create( league_rec:, season: )
6
+ ## note: allow passing in of activerecord db records too - why? why not?
7
+ ## raise ArgumentError, "league struct record expected; got #{league.class.name}" unless league.is_a?( Model::League )
8
+ raise ArgumentError, "season expected; got #{season.class.name}" unless season.is_a?( Season )
9
+
10
+ rec = Model::Event.find_by( league_id: league_rec.id,
11
+ season: season.to_key )
12
+ if rec.nil?
13
+ attribs = {
14
+ league_id: league_rec.id,
15
+ season: season.to_key,
16
+ }
17
+
18
+ rec = Model::Event.create!( attribs )
19
+ end
20
+ rec
21
+ end
22
+ end # class Event
23
+
24
+ end # module Sync
25
+ end # module SportDbV2
26
+
@@ -0,0 +1,18 @@
1
+ module SportDbV2
2
+ module Sync
3
+ class League
4
+
5
+ def self.find_or_create( name: )
6
+ rec = Model::League.find_by( name: name )
7
+ if rec.nil?
8
+ attribs = { name: name }
9
+
10
+ rec = Model::League.create!( attribs )
11
+ end
12
+ rec
13
+ end
14
+
15
+ end # class League
16
+ end # module Sync
17
+ end # module SportDb
18
+
@@ -0,0 +1,108 @@
1
+ module SportDbV2
2
+ module Sync
3
+
4
+
5
+ class Team
6
+ def self.find_or_create( name: )
7
+ rec = Model::Team.find_by( name: name )
8
+ if rec.nil?
9
+ attribs = { name: name }
10
+
11
+ rec = Model::Team.create!( attribs )
12
+ end
13
+ rec
14
+ end
15
+ end # class Team
16
+
17
+ class EventRound
18
+ def self.find_or_create( name:, event_rec: )
19
+ rec = Model::EventRound.find_by( name: name, event_id: event_rec.id )
20
+ if rec.nil?
21
+
22
+ attribs = { event_id: event_rec.id,
23
+ name: name
24
+ }
25
+
26
+ ## todo/fix: check if round has (optional) start or end date and add!!!
27
+ ## attribs[ :start_date] = round.start_date.to_date
28
+ rec = Model::EventRound.create!( attribs )
29
+ end
30
+ rec
31
+ end
32
+ end # class EventRound
33
+
34
+
35
+
36
+ class Match
37
+ def self.create!( match, event_rec: )
38
+ ## note: MUST find round, thus, use bang (!)
39
+
40
+ ## todo/check: allow strings too - why? why not?
41
+
42
+
43
+ ## todo/check:
44
+ ## how to model "same" rounds in different stages
45
+ ## e.g. Belgium
46
+ ## in regular season (stage) - round 1, round 2, etc.
47
+ ## in playoff (stage) - round 1, round 2, etc.
48
+ ## reference same round or create a new one for each stage!!???
49
+ ## and lookup by name AND stage??
50
+
51
+ round_rec = if match.round || match.stage || match.group
52
+ ## note - for now use "all-in-one" super round
53
+ ### stage, round, group - is the layout
54
+ ## query for round - allow string or round rec
55
+ ## todo/fix - also allow stage.name and group.name - why? why not?
56
+ ## e.g. group_name = match.group.is_a?( String ) ? match.group : match.group.name
57
+
58
+ round_name = match.round.is_a?( String ) ? match.round : match.round.name
59
+ round_name = "#{match.stage}, #{round_name}" if match.stage
60
+ round_name = "#{round_name}, #{match.group}" if match.group
61
+
62
+ Sync::EventRound.find_or_create( name: round_name,
63
+ event_rec: event_rec )
64
+ else # note: allow matches WITHOUT rounds too (e.g. England Football League 1888 and others)
65
+ nil
66
+ end
67
+
68
+
69
+
70
+ team1_name = match.team1.is_a?( String ) ? match.team1 : match.team1.name
71
+ team2_name = match.team2.is_a?( String ) ? match.team2 : match.team2.name
72
+ team1_rec = Team.find_or_create( name: team1_name )
73
+ team2_rec = Team.find_or_create( name: team2_name )
74
+
75
+
76
+ rec = nil ## match record
77
+
78
+ attribs = { league_id: event_rec.league.id,
79
+ season: event_rec.season,
80
+
81
+ team1_id: team1_rec.id,
82
+ team2_id: team2_rec.id,
83
+
84
+ # pos: max_pos,
85
+ # num: match.num, ## note - might be nil (not nil for euro, world cup, etc.)
86
+ date: match.date, # assume iso format as string e.g. 2021-07-10 !!!
87
+ time: match.time, # assume iso format as string e.g. 21:00 !!!
88
+
89
+ score1ft: match.score1,
90
+ score2ft: match.score2,
91
+ score1ht: match.score1i,
92
+ score2ht: match.score2i,
93
+
94
+ score1et: match.score1et,
95
+ score2et: match.score2et,
96
+ score1p: match.score1p,
97
+ score2p: match.score2p,
98
+ status: match.status }
99
+
100
+ attribs[ :event_round_id ] = round_rec.id if round_rec
101
+
102
+ rec = Model::Match.create!( attribs )
103
+ rec
104
+ end
105
+ end # class Match
106
+
107
+ end # module Sync
108
+ end # module SportDbV2
@@ -0,0 +1,27 @@
1
+ module SportDbV2
2
+ module Module
3
+ module Models
4
+ MAJOR = 0
5
+ MINOR = 0
6
+ PATCH = 1
7
+ VERSION = [MAJOR,MINOR,PATCH].join('.')
8
+
9
+ def self.version
10
+ VERSION
11
+ end
12
+
13
+ def self.banner
14
+ "sportdb-models_v2/#{VERSION} on Ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE}) [#{RUBY_PLATFORM}] in (#{root})"
15
+ end
16
+
17
+ def self.root
18
+ File.expand_path( File.dirname(File.dirname(File.dirname(File.dirname(__FILE__)))) )
19
+ end
20
+ end # module Models
21
+ end # module Module
22
+
23
+ #################
24
+ ## add convenience shortcuts
25
+ VERSION = Module::Models::VERSION
26
+ end # module SportDbV2
27
+
@@ -0,0 +1,233 @@
1
+ require 'cocos'
2
+
3
+
4
+ require 'props' # see github.com/rubylibs/props
5
+ require 'logutils' # see github.com/rubylibs/logutils
6
+
7
+ require 'active_record' ## todo: add sqlite3? etc.
8
+
9
+ ## add more activerecords addons/utils
10
+ ## -- require 'tagutils'
11
+ require 'activerecord/utils' ### check what used for what??
12
+ require 'props/activerecord' # includes ConfDb (ConfDb::Model::Prop, etc.)
13
+ require 'logutils/activerecord' # includes LogDb (LogDb::Model::Log, etc.)
14
+
15
+
16
+ ### pull-in sportdb parser machinery
17
+ require 'sportdb/quick'
18
+
19
+ ######
20
+ ## add V2 references (from "V1")
21
+ module SportDbV2
22
+ Logging = SportDb::Logging
23
+ QuickLeagueOutline = SportDb::QuickLeagueOutline
24
+ MatchParser = SportDb::MatchParser
25
+ end # module SportDbV2
26
+
27
+
28
+
29
+
30
+ # our own code
31
+ require_relative 'models_v2/version' # let version always go first
32
+
33
+ =begin
34
+ require_relative 'models/formats'
35
+
36
+ require_relative 'models/models/world/city'
37
+ require_relative 'models/models/world/country'
38
+ require_relative 'models/models/world/continent'
39
+ require_relative 'models/models/world/state'
40
+
41
+ require_relative 'models/models/assoc'
42
+ require_relative 'models/models/badge'
43
+ require_relative 'models/models/goal'
44
+ require_relative 'models/models/ground'
45
+ require_relative 'models/models/group'
46
+ require_relative 'models/models/lineup'
47
+ require_relative 'models/models/person'
48
+ require_relative 'models/models/round'
49
+ require_relative 'models/models/season'
50
+ require_relative 'models/models/stage'
51
+ =end
52
+
53
+
54
+ require_relative 'models_v2/models/forward'
55
+ require_relative 'models_v2/models/event'
56
+ require_relative 'models_v2/models/league'
57
+ require_relative 'models_v2/models/match'
58
+
59
+ require_relative 'models_v2/models/team'
60
+
61
+ require_relative 'models_v2/schema' # note: requires sportdb/models (include SportDB::Models)
62
+
63
+
64
+ require_relative 'models_v2/deleter'
65
+ require_relative 'models_v2/stats'
66
+
67
+ ####
68
+ # pull in reader & friends
69
+ require_relative 'models_v2/match_reader'
70
+
71
+
72
+ require_relative 'models_v2/sync/league'
73
+ require_relative 'models_v2/sync/event'
74
+ require_relative 'models_v2/sync/match'
75
+
76
+
77
+
78
+ ####
79
+ # more db machinery
80
+
81
+ module SportDbV2
82
+
83
+ def self.create
84
+ CreateDb.new.up
85
+ ConfDb::Model::Prop.create!( key: 'db.schema.sport_v2.version', value: VERSION )
86
+ end
87
+
88
+ def self.create_all
89
+ ## build schema - convenience helper
90
+ LogDb.create
91
+ ConfDb.create
92
+ SportDbV2.create
93
+ end
94
+
95
+ def self.auto_migrate!
96
+ ### todo/fix:
97
+ ## check props table and versions!!!!!
98
+
99
+ # first time? - auto-run db migratation, that is, create db tables
100
+ unless LogDb::Model::Log.table_exists?
101
+ LogDb.create # add logs table
102
+ end
103
+
104
+ unless ConfDb::Model::Prop.table_exists?
105
+ ConfDb.create # add props table
106
+ end
107
+
108
+ unless SportDbV2::Model::League.table_exists?
109
+ SportDbV2.create
110
+ end
111
+ end # method auto_migrate!
112
+
113
+ =begin
114
+ # delete ALL records (use with care!)
115
+ def self.delete!
116
+ puts '*** deleting sport table records/data...'
117
+ Deleter.new.run
118
+ end # method delete!
119
+ =end
120
+
121
+ def self.tables
122
+ Stats.new.tables
123
+ end
124
+
125
+
126
+ ### use/change to **config - to allow "inline" conif with keywords
127
+ ## (without enclosing {}) - why? why not?
128
+ def self.connect!( config={} ) # convenience shortcut w/ automigrate
129
+ connect( config )
130
+ auto_migrate!
131
+ end
132
+
133
+ def self.connect( config={} )
134
+ if config.empty?
135
+ puts "ENV['DATBASE_URL'] - >#{ENV['DATABASE_URL']}<"
136
+
137
+ ### change default to ./sport.db ?? why? why not?
138
+ db = URI.parse( ENV['DATABASE_URL'] || 'sqlite3:///sport.db' )
139
+
140
+ config = if db.scheme == 'postgres'
141
+ { adapter: 'postgresql',
142
+ host: db.host,
143
+ port: db.port,
144
+ username: db.user,
145
+ password: db.password,
146
+ database: db.path[1..-1],
147
+ encoding: 'utf8'
148
+ }
149
+ else # assume sqlite3
150
+ { adapter: db.scheme, # sqlite3
151
+ database: db.path[1..-1] # sport.db (NB: cut off leading /, thus 1..-1)
152
+ }
153
+ end
154
+ else
155
+ ## note: for compatibility lets you also pass-in/use string keys
156
+ ## e.g. YAML.load uses/returns always string keys - always auto-convert to symbols
157
+ config = config.symbolize_keys
158
+ end
159
+
160
+
161
+ ## todo/check/fix: move jruby "hack" to attic - why? why not?
162
+ ## todo/check: use if defined?( JRUBY_VERSION ) instead ??
163
+ ## if RUBY_PLATFORM =~ /java/ && config[:adapter] == 'sqlite3'
164
+ # quick hack for JRuby sqlite3 support via jdbc
165
+ ## puts "jruby quick hack - adding jdbc libs for jruby sqlite3 database support"
166
+ ## require 'jdbc/sqlite3'
167
+ ## require 'active_record/connection_adapters/jdbc_adapter'
168
+ ## require 'active_record/connection_adapters/jdbcsqlite3_adapter'
169
+ ## end
170
+
171
+ puts "Connecting to db using settings: "
172
+ pp config
173
+ ActiveRecord::Base.establish_connection( config )
174
+ # ActiveRecord::Base.logger = Logger.new( STDOUT )
175
+
176
+ ## if sqlite3 add (use) some pragmas for speedups
177
+ if config[:adapter] == 'sqlite3' &&
178
+ config[:database] != ':memory:'
179
+ ## note: if in memory database e.g. ':memory:' no pragma needed!!
180
+ ## try to speed up sqlite
181
+ ## see http://www.sqlite.org/pragma.html
182
+ con = ActiveRecord::Base.connection
183
+ con.execute( 'PRAGMA synchronous=OFF;' )
184
+ con.execute( 'PRAGMA journal_mode=OFF;' )
185
+ con.execute( 'PRAGMA temp_store=MEMORY;' )
186
+ end
187
+ end
188
+
189
+
190
+
191
+ def self.open( path ) ## shortcut for sqlite only
192
+ config = {
193
+ adapter: 'sqlite3',
194
+ database: path # e.g. ':memory', './sport.db'
195
+ }
196
+ connect!( config )
197
+ end
198
+
199
+ def self.setup_in_memory_db
200
+ # Database Setup & Config
201
+ ## add logger as option - why? why not?
202
+ ## e.g. open_mem( logger: true ) or
203
+ ## always possible to add logger "manually" before
204
+ ## thus, no need for option/flag really ??
205
+ ## ActiveRecord::Base.logger = Logger.new( STDOUT )
206
+ ## ActiveRecord::Base.colorize_logging = false - no longer exists - check new api/config setting?
207
+
208
+ config = {
209
+ adapter: 'sqlite3',
210
+ database: ':memory:'
211
+ }
212
+ connect( config )
213
+
214
+ ## build schema
215
+ create_all
216
+ end # setup_in_memory_db (using SQLite :memory:)
217
+
218
+
219
+ ###########
220
+ # add more aliases/alt names for in memory db - why? why not?
221
+ ## rename to open_mem for canonical name - why? why not?
222
+ class << self
223
+ alias_method :open_mem, :setup_in_memory_db
224
+ alias_method :open_memory, :setup_in_memory_db
225
+ alias_method :open_in_memory, :setup_in_memory_db
226
+ end
227
+ end # module SportDb
228
+
229
+
230
+ ## say hello
231
+ puts SportDbV2::Module::Models.banner
232
+
233
+
metadata ADDED
@@ -0,0 +1,155 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sportdb-models_v2
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Gerald Bauer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-04-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: cocos
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: props-activerecord
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: logutils-activerecord
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sportdb-quick
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
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: rdoc
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '4.0'
76
+ - - "<"
77
+ - !ruby/object:Gem::Version
78
+ version: '7'
79
+ type: :development
80
+ prerelease: false
81
+ version_requirements: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '4.0'
86
+ - - "<"
87
+ - !ruby/object:Gem::Version
88
+ version: '7'
89
+ - !ruby/object:Gem::Dependency
90
+ name: hoe
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '4.2'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '4.2'
103
+ description: sportdb-models_v2 - sport.db schema 'n' models for easy (re)use
104
+ email: gerald.bauer@gmail.com
105
+ executables: []
106
+ extensions: []
107
+ extra_rdoc_files:
108
+ - CHANGELOG.md
109
+ - Manifest.txt
110
+ - README.md
111
+ files:
112
+ - CHANGELOG.md
113
+ - Manifest.txt
114
+ - README.md
115
+ - Rakefile
116
+ - lib/sportdb/models_v2.rb
117
+ - lib/sportdb/models_v2/deleter.rb
118
+ - lib/sportdb/models_v2/match_reader.rb
119
+ - lib/sportdb/models_v2/models/event.rb
120
+ - lib/sportdb/models_v2/models/forward.rb
121
+ - lib/sportdb/models_v2/models/league.rb
122
+ - lib/sportdb/models_v2/models/match.rb
123
+ - lib/sportdb/models_v2/models/team.rb
124
+ - lib/sportdb/models_v2/schema.rb
125
+ - lib/sportdb/models_v2/stats.rb
126
+ - lib/sportdb/models_v2/sync/event.rb
127
+ - lib/sportdb/models_v2/sync/league.rb
128
+ - lib/sportdb/models_v2/sync/match.rb
129
+ - lib/sportdb/models_v2/version.rb
130
+ homepage: https://github.com/sportdb/sport.db
131
+ licenses:
132
+ - Public Domain
133
+ metadata: {}
134
+ post_install_message:
135
+ rdoc_options:
136
+ - "--main"
137
+ - README.md
138
+ require_paths:
139
+ - lib
140
+ required_ruby_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: 3.1.0
145
+ required_rubygems_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ requirements: []
151
+ rubygems_version: 3.5.22
152
+ signing_key:
153
+ specification_version: 4
154
+ summary: sportdb-models_v2 - sport.db schema 'n' models for easy (re)use
155
+ test_files: []