sportdb-models 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +7 -0
  2. data/.gemtest +0 -0
  3. data/HISTORY.md +21 -0
  4. data/Manifest.txt +142 -0
  5. data/README.md +28 -0
  6. data/Rakefile +59 -0
  7. data/config/fixtures/de.yml +46 -0
  8. data/config/fixtures/en.yml +54 -0
  9. data/config/fixtures/es.yml +48 -0
  10. data/config/fixtures/fr.yml +53 -0
  11. data/config/fixtures/it.yml +55 -0
  12. data/config/fixtures/pt.yml +46 -0
  13. data/config/fixtures/ro.yml +55 -0
  14. data/data/seasons.txt +74 -0
  15. data/data/setups/all.txt +5 -0
  16. data/lib/sportdb/calc.rb +279 -0
  17. data/lib/sportdb/deleter.rb +52 -0
  18. data/lib/sportdb/finders/date.rb +374 -0
  19. data/lib/sportdb/finders/goals.rb +260 -0
  20. data/lib/sportdb/finders/scores.rb +122 -0
  21. data/lib/sportdb/lang.rb +216 -0
  22. data/lib/sportdb/matcher.rb +31 -0
  23. data/lib/sportdb/models.rb +259 -0
  24. data/lib/sportdb/models/assoc.rb +106 -0
  25. data/lib/sportdb/models/assoc_assoc.rb +15 -0
  26. data/lib/sportdb/models/badge.rb +14 -0
  27. data/lib/sportdb/models/event.rb +65 -0
  28. data/lib/sportdb/models/event_ground.rb +15 -0
  29. data/lib/sportdb/models/event_team.rb +16 -0
  30. data/lib/sportdb/models/forward.rb +55 -0
  31. data/lib/sportdb/models/game.rb +244 -0
  32. data/lib/sportdb/models/goal.rb +15 -0
  33. data/lib/sportdb/models/ground.rb +100 -0
  34. data/lib/sportdb/models/group.rb +23 -0
  35. data/lib/sportdb/models/group_team.rb +14 -0
  36. data/lib/sportdb/models/league.rb +83 -0
  37. data/lib/sportdb/models/person.rb +21 -0
  38. data/lib/sportdb/models/roster.rb +18 -0
  39. data/lib/sportdb/models/round.rb +22 -0
  40. data/lib/sportdb/models/season.rb +14 -0
  41. data/lib/sportdb/models/stats/alltime_standing.rb +44 -0
  42. data/lib/sportdb/models/stats/alltime_standing_entry.rb +23 -0
  43. data/lib/sportdb/models/stats/event_standing.rb +55 -0
  44. data/lib/sportdb/models/stats/event_standing_entry.rb +21 -0
  45. data/lib/sportdb/models/stats/group_standing.rb +50 -0
  46. data/lib/sportdb/models/stats/group_standing_entry.rb +22 -0
  47. data/lib/sportdb/models/team.rb +119 -0
  48. data/lib/sportdb/models/team_comp.rb +64 -0
  49. data/lib/sportdb/models/utils.rb +78 -0
  50. data/lib/sportdb/models/world/city.rb +21 -0
  51. data/lib/sportdb/models/world/continent.rb +20 -0
  52. data/lib/sportdb/models/world/country.rb +19 -0
  53. data/lib/sportdb/models/world/region.rb +19 -0
  54. data/lib/sportdb/patterns.rb +38 -0
  55. data/lib/sportdb/reader.rb +130 -0
  56. data/lib/sportdb/reader_file.rb +123 -0
  57. data/lib/sportdb/reader_zip.rb +165 -0
  58. data/lib/sportdb/readers/assoc.rb +54 -0
  59. data/lib/sportdb/readers/event.rb +200 -0
  60. data/lib/sportdb/readers/game.rb +877 -0
  61. data/lib/sportdb/readers/ground.rb +53 -0
  62. data/lib/sportdb/readers/league.rb +54 -0
  63. data/lib/sportdb/readers/season.rb +83 -0
  64. data/lib/sportdb/readers/squad_club.rb +201 -0
  65. data/lib/sportdb/readers/squad_national_team.rb +173 -0
  66. data/lib/sportdb/readers/team.rb +53 -0
  67. data/lib/sportdb/schema.rb +373 -0
  68. data/lib/sportdb/standings.rb +178 -0
  69. data/lib/sportdb/stats.rb +27 -0
  70. data/lib/sportdb/utils.rb +89 -0
  71. data/lib/sportdb/utils_date.rb +26 -0
  72. data/lib/sportdb/utils_goals.rb +20 -0
  73. data/lib/sportdb/utils_group.rb +63 -0
  74. data/lib/sportdb/utils_map.rb +44 -0
  75. data/lib/sportdb/utils_round.rb +165 -0
  76. data/lib/sportdb/utils_scores.rb +17 -0
  77. data/lib/sportdb/utils_teams.rb +43 -0
  78. data/lib/sportdb/version.rb +23 -0
  79. data/test/data/at-austria/2013_14/bl.txt +227 -0
  80. data/test/data/at-austria/2013_14/bl.yml +30 -0
  81. data/test/data/at-austria/2013_14/bl_ii.txt +154 -0
  82. data/test/data/at-austria/2013_14/el.txt +4 -0
  83. data/test/data/at-austria/2013_14/el.yml +25 -0
  84. data/test/data/at-austria/2013_14/squads/austria.txt +40 -0
  85. data/test/data/at-austria/2013_14/squads/salzburg.txt +35 -0
  86. data/test/data/at-austria/leagues.txt +11 -0
  87. data/test/data/at-austria/teams.txt +75 -0
  88. data/test/data/at-austria/teams_2.txt +34 -0
  89. data/test/data/national-teams/assocs.txt +231 -0
  90. data/test/data/national-teams/europe/assocs.txt +13 -0
  91. data/test/data/national-teams/europe/teams.txt +13 -0
  92. data/test/data/national-teams/north-america/assocs.txt +10 -0
  93. data/test/data/national-teams/north-america/teams.txt +7 -0
  94. data/test/data/national-teams/teams.txt +19 -0
  95. data/test/data/players/europe/at-austria/players.txt +45 -0
  96. data/test/data/players/europe/de-deutschland/players.txt +41 -0
  97. data/test/data/players/south-america/br-brazil/players.txt +51 -0
  98. data/test/data/world-cup/1930/cup.txt +71 -0
  99. data/test/data/world-cup/1930/cup.yml +23 -0
  100. data/test/data/world-cup/1930/cup_goals.txt +47 -0
  101. data/test/data/world-cup/1930/cup_goals.yml +23 -0
  102. data/test/data/world-cup/1954/cup.txt +90 -0
  103. data/test/data/world-cup/1954/cup.yml +30 -0
  104. data/test/data/world-cup/1962/cup.txt +86 -0
  105. data/test/data/world-cup/1962/cup.yml +32 -0
  106. data/test/data/world-cup/1974/cup.yml +35 -0
  107. data/test/data/world-cup/1974/cup_finals.txt +14 -0
  108. data/test/data/world-cup/1974/cup_i.txt +55 -0
  109. data/test/data/world-cup/1974/cup_ii.txt +34 -0
  110. data/test/data/world-cup/2014/cup.txt +5 -0
  111. data/test/data/world-cup/2014/cup.yml +54 -0
  112. data/test/data/world-cup/2014/squads/br-brazil.txt +46 -0
  113. data/test/data/world-cup/2014/squads/de-deutschland.txt +8 -0
  114. data/test/data/world-cup/2014/squads/jp-japan.txt +30 -0
  115. data/test/data/world-cup/2014/squads/uy-uruguay.txt +32 -0
  116. data/test/data/world-cup/leagues.txt +5 -0
  117. data/test/data/world-cup/seasons_1930.txt +4 -0
  118. data/test/data/world-cup/seasons_1954.txt +4 -0
  119. data/test/data/world-cup/seasons_1962.txt +4 -0
  120. data/test/data/world-cup/seasons_1974.txt +5 -0
  121. data/test/data/world-cup/teams_1930.txt +26 -0
  122. data/test/data/world-cup/teams_1954.txt +30 -0
  123. data/test/data/world-cup/teams_1962.txt +29 -0
  124. data/test/data/world-cup/teams_1974.txt +29 -0
  125. data/test/helper.rb +120 -0
  126. data/test/test_assoc_reader.rb +201 -0
  127. data/test/test_changes.rb +74 -0
  128. data/test/test_cursor.rb +50 -0
  129. data/test/test_date.rb +100 -0
  130. data/test/test_goals.rb +109 -0
  131. data/test/test_lang.rb +130 -0
  132. data/test/test_load.rb +61 -0
  133. data/test/test_reader.rb +88 -0
  134. data/test/test_reader_from_string.rb +65 -0
  135. data/test/test_round_auto.rb +370 -0
  136. data/test/test_round_def.rb +109 -0
  137. data/test/test_round_header.rb +183 -0
  138. data/test/test_scores.rb +70 -0
  139. data/test/test_squad_club_reader.rb +76 -0
  140. data/test/test_squad_national_team_reader.rb +116 -0
  141. data/test/test_standings.rb +279 -0
  142. data/test/test_standings_ii.rb +46 -0
  143. data/test/test_utils.rb +124 -0
  144. data/test/test_winner.rb +95 -0
  145. metadata +378 -0
@@ -0,0 +1,54 @@
1
+ # encoding: UTF-8
2
+
3
+ module SportDb
4
+
5
+
6
+ class AssocReader
7
+
8
+ include LogUtils::Logging
9
+
10
+ ## make models available by default with namespace
11
+ # e.g. lets you use Usage instead of Model::Usage
12
+ include Models
13
+
14
+
15
+ def self.from_zip( zip_file, entry_path, more_attribs={} )
16
+ ## get text content from zip
17
+ entry = zip_file.find_entry( entry_path )
18
+
19
+ text = entry.get_input_stream().read()
20
+ text = text.force_encoding( Encoding::UTF_8 )
21
+
22
+ self.from_string( text, more_attribs )
23
+ end
24
+
25
+ def self.from_file( path, more_attribs={} )
26
+ ## note: assume/enfore utf-8 encoding (with or without BOM - byte order mark)
27
+ ## - see textutils/utils.rb
28
+ text = File.read_utf8( path )
29
+ self.from_string( text, more_attribs )
30
+ end
31
+
32
+ def self.from_string( text, more_attribs={} )
33
+ AssocReader.new( text, more_attribs )
34
+ end
35
+
36
+
37
+ def initialize( text, more_attribs={} )
38
+ ## todo/fix: how to add opts={} ???
39
+ @text = text
40
+ @more_attribs = more_attribs
41
+ end
42
+
43
+
44
+ def read()
45
+ reader = ValuesReader.from_string( @text, @more_attribs )
46
+
47
+ reader.each_line do |new_attributes, values|
48
+ Assoc.create_or_update_from_values( new_attributes, values )
49
+ end # each lines
50
+ end
51
+
52
+
53
+ end # class AssocReader
54
+ end # module SportDb
@@ -0,0 +1,200 @@
1
+ # encoding: UTF-8
2
+
3
+ module SportDb
4
+
5
+
6
+ class EventReader
7
+
8
+ include LogUtils::Logging
9
+
10
+ ## make models available by default with namespace
11
+ # e.g. lets you use Usage instead of Model::Usage
12
+ include Models
13
+
14
+ attr_reader :event # returns event record; call read first
15
+ attr_reader :fixtures # fixtures/sources entry from event config
16
+
17
+ def self.from_zip( zip_file, entry_path, more_attribs={} )
18
+ ## get text content from zip
19
+ entry = zip_file.find_entry( entry_path )
20
+
21
+ text = entry.get_input_stream().read()
22
+ text = text.force_encoding( Encoding::UTF_8 )
23
+
24
+ config = File.basename( entry_path ) # name a of .yml file
25
+
26
+ self.from_string( text, config, more_attribs )
27
+ end
28
+
29
+ def self.from_file( path, more_attribs={} )
30
+ ## note: assume/enfore utf-8 encoding (with or without BOM - byte order mark)
31
+ ## - see textutils/utils.rb
32
+ text = File.read_utf8( path )
33
+
34
+ config = File.basename( name ) # name a of .yml file
35
+
36
+ self.from_string( text, config, more_attribs )
37
+ end
38
+
39
+ def self.from_string( text, config, more_attribs={} )
40
+ EventReader.new( text, config, more_attribs )
41
+ end
42
+
43
+ def initialize( text, config, more_attribs={} )
44
+ ## todo/fix: how to add opts={} ???
45
+ @text = text
46
+ @more_attribs = more_attribs
47
+
48
+ @config = config # name of event configuration (relative basename w/o path or string)
49
+ @sources_default = config # note: use same a config for now
50
+
51
+ @event = nil
52
+ @fixtures = []
53
+ end
54
+
55
+
56
+ def read()
57
+ @fixtures = [] # reset cached fixtures
58
+ @event = nil # reset cached event rec
59
+
60
+ ####
61
+ ## fix!!!!!
62
+ ## use Event.create_or_update_from_hash or similar
63
+ ## use Event.create_or_update_from_hash_reader?? or similar
64
+ # move parsing code to model
65
+
66
+ reader = HashReader.from_string( @text )
67
+
68
+ event_attribs = {}
69
+
70
+ ## set default sources to basename by convention
71
+ # e.g 2013_14/bl => bl
72
+ # etc.
73
+ # use fixtures/sources: to override default
74
+
75
+ event_attribs[ 'sources' ] = @sources_default
76
+ event_attribs[ 'config' ] = @config # name a of .yml file
77
+
78
+ reader.each_typed do |key, value|
79
+
80
+ ## puts "processing event attrib >>#{key}<< >>#{value}<<..."
81
+
82
+ if key == 'league'
83
+ league = League.find_by_key( value.to_s.strip )
84
+
85
+ ## check if it exists
86
+ if league.present?
87
+ event_attribs['league_id'] = league.id
88
+ else
89
+ logger.error "league with key >>#{value.to_s.strip}<< missing"
90
+ exit 1
91
+ end
92
+
93
+ elsif key == 'season'
94
+ season = Season.find_by_key( value.to_s.strip )
95
+
96
+ ## check if it exists
97
+ if season.present?
98
+ event_attribs['season_id'] = season.id
99
+ else
100
+ logger.error "season with key >>#{value.to_s.strip}<< missing"
101
+ exit 1
102
+ end
103
+
104
+ elsif key == 'start_at' || key == 'begin_at'
105
+
106
+ if value.is_a?(DateTime) || value.is_a?(Date)
107
+ start_at = value
108
+ else # assume it's a string
109
+ start_at = DateTime.strptime( value.to_s.strip, '%Y-%m-%d' )
110
+ end
111
+
112
+ event_attribs['start_at'] = start_at
113
+
114
+ elsif key == 'end_at' || key == 'stop_at'
115
+
116
+ if value.is_a?(DateTime) || value.is_a?(Date)
117
+ end_at = value
118
+ else # assume it's a string
119
+ end_at = DateTime.strptime( value.to_s.strip, '%Y-%m-%d' )
120
+ end
121
+
122
+ event_attribs['end_at'] = end_at
123
+
124
+ elsif key == 'grounds' || key == 'stadiums' || key == 'venues'
125
+ ## assume grounds value is an array
126
+
127
+ ##
128
+ ## note: for now we allow invalid ground keys
129
+ ## will skip keys not found
130
+
131
+ ground_ids = []
132
+ value.each do |item|
133
+ ground_key = item.to_s.strip
134
+ ground = Ground.find_by_key( ground_key )
135
+ if ground.nil?
136
+ puts "[warn] ground/stadium w/ key >#{ground_key}< not found; skipping ground"
137
+ else
138
+ ground_ids << ground.id
139
+ end
140
+ end
141
+
142
+ event_attribs['ground_ids'] = ground_ids
143
+ elsif key == 'teams'
144
+ ## assume teams value is an array
145
+
146
+ team_ids = []
147
+ value.each do |item|
148
+ team_key = item.to_s.strip
149
+ team = Team.find_by_key!( team_key )
150
+ team_ids << team.id
151
+ end
152
+
153
+ event_attribs['team_ids'] = team_ids
154
+
155
+ elsif key == 'team3'
156
+ ## for now always assume false # todo: fix - use value and convert to boolean if not boolean
157
+ event_attribs['team3'] = false
158
+
159
+ elsif key == 'fixtures' || key == 'sources'
160
+ ### todo: check for mulitiple fixtures/sources ?? allow disallow?? why? why not?
161
+ if value.kind_of?(Array)
162
+ event_attribs['sources'] = value.join(',')
163
+ @fixtures += value
164
+ else # assume plain (single fixture) string
165
+ event_attribs['sources'] = value.to_s
166
+ @fixtures << value.to_s
167
+ end
168
+ else
169
+ ## todo: add a source location struct to_s or similar (file, line, col)
170
+ logger.error "unknown event attrib #{key}; skipping attrib"
171
+ end
172
+
173
+ end # each key,value
174
+
175
+ league_id = event_attribs['league_id']
176
+ season_id = event_attribs['season_id']
177
+
178
+ logger.debug "find event - league_id: #{league_id}, season_id: #{season_id}"
179
+
180
+ event = Event.find_by_league_id_and_season_id( league_id, season_id )
181
+
182
+ ## check if it exists
183
+ if event.present?
184
+ logger.debug "*** update event #{event.id}-#{event.key}:"
185
+ else
186
+ logger.debug "*** create event:"
187
+ event = Event.new
188
+ end
189
+
190
+ logger.debug event_attribs.to_json
191
+
192
+ event.update_attributes!( event_attribs )
193
+
194
+ # keep a cached reference for later use
195
+ @event = event
196
+ end # method read
197
+
198
+
199
+ end # class EventReader
200
+ end # module SportDb
@@ -0,0 +1,877 @@
1
+ # encoding: UTF-8
2
+
3
+ module SportDb
4
+
5
+
6
+ class GameReader
7
+
8
+ include LogUtils::Logging
9
+
10
+ ## make models available by default with namespace
11
+ # e.g. lets you use Usage instead of Model::Usage
12
+ include Models
13
+
14
+ ## value helpers e.g. is_year?, is_taglist? etc.
15
+ include TextUtils::ValueHelper
16
+
17
+ include FixtureHelpers
18
+
19
+ def self.from_zip( zip_file, entry_path, more_attribs={} )
20
+
21
+ logger = LogKernel::Logger.root
22
+
23
+ reader = EventReader.from_zip( zip_file, entry_path )
24
+ reader.read()
25
+
26
+ event = reader.event ## was fetch_event( name )
27
+ fixtures = reader.fixtures ## was fetch_event_fixtures( name )
28
+
29
+ if fixtures.empty?
30
+ ## logger.warn "no fixtures found for event - >#{name}<; assume fixture name is the same as event"
31
+ ## change extension from .yml to .txt
32
+ fixtures_with_path = [ entry_path.sub('.yml','.txt') ]
33
+ else
34
+ ## add path to fixtures (use path from event e.g)
35
+ # - bl + at-austria!/2012_13/bl -> at-austria!/2012_13/bl
36
+ # - bl_ii + at-austria!/2012_13/bl -> at-austria!/2012_13/bl_ii
37
+
38
+ dir = File.dirname( entry_path ) # use dir for fixtures
39
+
40
+ fixtures_with_path = fixtures.map do |fx|
41
+ fx_new = "#{dir}/#{fx}.txt" # add path upfront
42
+ logger.debug "fx: #{fx_new} | >#{fx}< + >#{dir}<"
43
+ fx_new
44
+ end
45
+ end
46
+
47
+ ## fix-fix-fix: change file extension to ??
48
+ text_ary = []
49
+ fixtures_with_path.each do |fixture_path|
50
+ entry = zip_file.find_entry( fixture_path )
51
+
52
+ text = entry.get_input_stream().read()
53
+ text = text.force_encoding( Encoding::UTF_8 )
54
+
55
+ text_ary << text
56
+ end
57
+
58
+ self.from_string( event, text_ary, more_attribs )
59
+ end
60
+
61
+ def self.from_file( path, more_attribs={} )
62
+
63
+ logger = LogKernel::Logger.root
64
+
65
+ ### NOTE: fix-fix-fix - pass in event path!!!!!!! (not fixture path!!!!)
66
+
67
+ ## - ## note: assume/enfore utf-8 encoding (with or without BOM - byte order mark)
68
+ ## - ## - see textutils/utils.rb
69
+ ## - text = File.read_utf8( path )
70
+
71
+ reader = EventReader.from_file( path )
72
+ reader.read()
73
+
74
+ event = reader.event ## was fetch_event( name )
75
+ fixtures = reader.fixtures ## was fetch_event_fixtures( name )
76
+
77
+
78
+ if fixtures.empty?
79
+ ## logger.warn "no fixtures found for event - >#{name}<; assume fixture name is the same as event"
80
+ ## change extension from .yml to .txt
81
+ fixtures_with_path = [ path.sub('.yml','.txt') ]
82
+ else
83
+ ## add path to fixtures (use path from event e.g)
84
+ # - bl + at-austria!/2012_13/bl -> at-austria!/2012_13/bl
85
+ # - bl_ii + at-austria!/2012_13/bl -> at-austria!/2012_13/bl_ii
86
+
87
+ dir = File.dirname( path ) # use dir for fixtures
88
+
89
+ fixtures_with_path = fixtures.map do |fx|
90
+ fx_new = "#{dir}/#{fx}.txt" # add path upfront
91
+ logger.debug "fx: #{fx_new} | >#{fx}< + >#{dir}<"
92
+ fx_new
93
+ end
94
+ end
95
+
96
+ ## fix-fix-fix: change file extension to ??
97
+ text_ary = []
98
+ fixtures_with_path.each do |fixture_path|
99
+ text_ary << File.read_utf8( fixture_path )
100
+ end
101
+
102
+ self.from_string( event, text_ary, more_attribs )
103
+ end
104
+
105
+
106
+ def self.from_string( event, text_or_text_ary, more_attribs={} )
107
+ ### fix - fix -fix:
108
+ ## change event to event_or_event_key !!!!! - allow event_key as string passed in
109
+ GameReader.new( event, text_or_text_ary, more_attribs )
110
+ end
111
+
112
+
113
+ def initialize( event, text_or_text_ary, more_attribs={} )
114
+ ### fix - fix -fix:
115
+ ## change event to event_or_event_key !!!!! - allow event_key as string passed in
116
+
117
+ ## todo/fix: how to add opts={} ???
118
+ @event = event
119
+ @text_or_text_ary = text_or_text_ary
120
+ @more_attribs = more_attribs
121
+ end
122
+
123
+
124
+ def read()
125
+ if @text_or_text_ary.is_a?( String )
126
+ text_ary = [@text_or_text_ary]
127
+ else
128
+ text_ary = @text_or_text_ary
129
+ end
130
+
131
+ ## reset cached values
132
+ ## for auto-number rounds etc.
133
+ @last_round_pos = nil
134
+
135
+ text_ary.each do |text|
136
+ ## assume en for now? why? why not?
137
+ ## fix (cache) store lang in event table (e.g. auto-add and auto-update)!!!
138
+ SportDb.lang.lang = SportDb.lang.classify( text )
139
+
140
+ reader = LineReader.from_string( text )
141
+
142
+ read_fixtures_worker( @event.key, reader )
143
+ end
144
+
145
+ ## fix add prop ??
146
+ ### Prop.create!( key: "db.#{fixture_name_to_prop_key(name)}.version", value: "file.txt.#{File.mtime(path).strftime('%Y.%m.%d')}" )
147
+ end
148
+
149
+
150
+ def read_fixtures_worker( event_key, reader )
151
+ ## NB: assume active activerecord connection
152
+
153
+ ## reset cached values
154
+ @patch_round_ids_dates = []
155
+ @patch_round_ids_pos = []
156
+
157
+ @round = nil ## fix: change/rename to @last_round !!!
158
+ @group = nil ## fix: change/rename to @last_group !!!
159
+ @last_date = nil
160
+
161
+ @last_team1 = nil # used for goals (to match players via squads)
162
+ @last_team2 = nil
163
+ @last_game = nil
164
+
165
+
166
+ #####
167
+ # fix: move to read and share event/known_teams
168
+ # for all 1-n fixture files (no need to configure every time!!)
169
+
170
+ @event = Event.find_by_key!( event_key )
171
+
172
+ logger.debug "Event #{@event.key} >#{@event.title}<"
173
+
174
+ ### fix: use build_title_table_for ??? why? why not??
175
+ @known_teams = @event.known_teams_table
176
+
177
+ @known_grounds = TextUtils.build_title_table_for( @event.grounds )
178
+
179
+
180
+ parse_fixtures( reader )
181
+
182
+ end # method load_fixtures
183
+
184
+
185
+
186
+ def parse_group_header( line )
187
+ logger.debug "parsing group header line: >#{line}<"
188
+
189
+ # note: group header resets (last) round (allows, for example):
190
+ # e.g.
191
+ # Group Playoffs/Replays -- round header
192
+ # team1 team2 -- match
193
+ # Group B: -- group header
194
+ # team1 team2 - match (will get new auto-matchday! not last round)
195
+ @round = nil ## fix: change/rename to @last_round !!!
196
+
197
+ title, pos = find_group_title_and_pos!( line )
198
+
199
+ logger.debug " title: >#{title}<"
200
+ logger.debug " pos: >#{pos}<"
201
+ logger.debug " line: >#{line}<"
202
+
203
+ # set group for games
204
+ @group = Group.find_by_event_id_and_pos!( @event.id, pos )
205
+ end
206
+
207
+
208
+ def parse_group_def( line )
209
+ logger.debug "parsing group def line: >#{line}<"
210
+
211
+ match_teams!( line )
212
+ team_keys = find_teams!( line )
213
+
214
+ title, pos = find_group_title_and_pos!( line )
215
+
216
+ logger.debug " line: >#{line}<"
217
+
218
+ group_attribs = {
219
+ title: title
220
+ }
221
+
222
+ group = Group.find_by_event_id_and_pos( @event.id, pos )
223
+ if group.present?
224
+ logger.debug "update group #{group.id}:"
225
+ else
226
+ logger.debug "create group:"
227
+ group = Group.new
228
+ group_attribs = group_attribs.merge( {
229
+ event_id: @event.id,
230
+ pos: pos
231
+ })
232
+ end
233
+
234
+ logger.debug group_attribs.to_json
235
+
236
+ group.update_attributes!( group_attribs )
237
+
238
+ group.teams.clear # remove old teams
239
+ ## add new teams
240
+ team_keys.each do |team_key|
241
+ team = Team.find_by_key!( team_key )
242
+ logger.debug " adding team #{team.title} (#{team.code})"
243
+ group.teams << team
244
+ end
245
+ end
246
+
247
+
248
+ def parse_round_def( line )
249
+ logger.debug "parsing round def line: >#{line}<"
250
+
251
+ ### todo/fix/check: move cut off optional comment in reader for all lines? why? why not?
252
+ cut_off_end_of_line_comment!( line ) # cut off optional comment starting w/ #
253
+
254
+ start_at = find_date!( line, start_at: @event.start_at )
255
+ end_at = find_date!( line, start_at: @event.start_at )
256
+
257
+ # note: if end_at missing -- assume start_at is (==) end_at
258
+ end_at = start_at if end_at.nil?
259
+
260
+ # note: - NOT needed; start_at and end_at are saved as date only (NOT datetime)
261
+ # set hours,minutes,secs to beginning and end of day (do NOT use default 12.00)
262
+ # e.g. use 00.00 and 23.59
263
+ # start_at = start_at.beginning_of_day
264
+ # end_at = end_at.end_of_day
265
+
266
+ # note: make sure start_at/end_at is date only (e.g. use start_at.to_date)
267
+ # sqlite3 saves datetime in date field as datetime, for example (will break date compares later!)
268
+ start_at = start_at.to_date
269
+ end_at = end_at.to_date
270
+
271
+
272
+ pos = find_round_pos!( line )
273
+ title = find_round_def_title!( line )
274
+ # NB: use extracted round title for knockout check
275
+ knockout_flag = is_knockout_round?( title )
276
+
277
+
278
+ logger.debug " start_at: #{start_at}"
279
+ logger.debug " end_at: #{end_at}"
280
+ logger.debug " pos: #{pos}"
281
+ logger.debug " title: >#{title}<"
282
+ logger.debug " knockout_flag: #{knockout_flag}"
283
+
284
+ logger.debug " line: >#{line}<"
285
+
286
+ #######################################
287
+ # fix: add auto flag is false !!!!
288
+
289
+ round_attribs = {
290
+ title: title,
291
+ knockout: knockout_flag,
292
+ start_at: start_at,
293
+ end_at: end_at
294
+ }
295
+
296
+ round = Round.find_by_event_id_and_pos( @event.id, pos )
297
+ if round.present?
298
+ logger.debug "update round #{round.id}:"
299
+ else
300
+ logger.debug "create round:"
301
+ round = Round.new
302
+
303
+ round_attribs = round_attribs.merge( {
304
+ event_id: @event.id,
305
+ pos: pos
306
+ })
307
+ end
308
+
309
+ logger.debug round_attribs.to_json
310
+
311
+ round.update_attributes!( round_attribs )
312
+ end
313
+
314
+
315
+ def parse_round_header( line )
316
+ logger.debug "parsing round header line: >#{line}<"
317
+
318
+ ### todo/fix/check: move cut off optional comment in reader for all lines? why? why not?
319
+ cut_off_end_of_line_comment!( line ) # cut off optional comment starting w/ #
320
+
321
+ # NB: cut off optional title2 starting w/ // first
322
+ title2 = find_round_header_title2!( line )
323
+
324
+ # todo/fix: check if it is possible title2 w/ group?
325
+ # add an example here
326
+ group_title, group_pos = find_group_title_and_pos!( line )
327
+
328
+ ## todo/check/fix:
329
+ # make sure Round of 16 will not return pos 16 -- how? possible?
330
+ # add unit test too to verify
331
+ pos = find_round_pos!( line )
332
+
333
+ ## check if pos available; if not auto-number/calculate
334
+ if pos.nil?
335
+ if @patch_round_ids_pos.empty?
336
+ pos = (@last_round_pos||0)+1
337
+ logger.debug( " no round pos found; auto-number round - use (#{pos})" )
338
+ else
339
+ # note: if any rounds w/o pos already seen (add for auto-numbering at the end)
340
+ # will get auto-numbered sorted by start_at date
341
+ pos = 999001+@patch_round_ids_pos.length # e.g. 999<count> - 999001,999002,etc.
342
+ logger.debug( " no round pos found; auto-number round w/ patch (backtrack) at the end" )
343
+ end
344
+ end
345
+
346
+ # store pos for auto-number next round if missing
347
+ # - note: only if greater/bigger than last; use max
348
+ # - note: last_round_pos might be nil - thus set to 0
349
+ if pos > 999000
350
+ # note: do NOT update last_round_pos for to-be-patched rounds
351
+ else
352
+ @last_round_pos = [pos,@last_round_pos||0].max
353
+ end
354
+
355
+
356
+ title = find_round_header_title!( line )
357
+
358
+ ## NB: use extracted round title for knockout check
359
+ knockout_flag = is_knockout_round?( title )
360
+
361
+
362
+ if group_pos.present?
363
+ @group = Group.find_by_event_id_and_pos!( @event.id, group_pos )
364
+ else
365
+ @group = nil # reset group to no group
366
+ end
367
+
368
+ logger.debug " line: >#{line}<"
369
+
370
+ ## NB: dummy/placeholder start_at, end_at date
371
+ ## replace/patch after adding all games for round
372
+
373
+ round_attribs = {
374
+ title: title,
375
+ title2: title2,
376
+ knockout: knockout_flag
377
+ }
378
+
379
+ if pos > 999000
380
+ # no pos (e.g. will get autonumbered later) - try match by title for now
381
+ # e.g. lets us use title 'Group Replays', for example, multiple times
382
+ @round = Round.find_by_event_id_and_title( @event.id, title )
383
+ else
384
+ @round = Round.find_by_event_id_and_pos( @event.id, pos )
385
+ end
386
+
387
+ if @round.present?
388
+ logger.debug "update round #{@round.id}:"
389
+ else
390
+ logger.debug "create round:"
391
+ @round = Round.new
392
+
393
+ round_attribs = round_attribs.merge( {
394
+ event_id: @event.id,
395
+ pos: pos,
396
+ start_at: Date.parse('1911-11-11'),
397
+ end_at: Date.parse('1911-11-11')
398
+ })
399
+ end
400
+
401
+ logger.debug round_attribs.to_json
402
+
403
+ @round.update_attributes!( round_attribs )
404
+
405
+ @patch_round_ids_pos << @round.id if pos > 999000
406
+ ### store list of round ids for patching start_at/end_at at the end
407
+ @patch_round_ids_dates << @round.id # todo/fix/check: check if round has definition (do NOT patch if definition (not auto-added) present)
408
+ end
409
+
410
+
411
+ def try_parse_game( line )
412
+ # note: clone line; for possible test do NOT modify in place for now
413
+ # note: returns true if parsed, false if no match
414
+ parse_game( line.dup )
415
+ end
416
+
417
+ def parse_game( line )
418
+ logger.debug "parsing game (fixture) line: >#{line}<"
419
+
420
+ match_teams!( line )
421
+ team1_key = find_team1!( line )
422
+ team2_key = find_team2!( line )
423
+
424
+ ## note: if we do NOT find two teams; return false - no match found
425
+ if team1_key.nil? || team2_key.nil?
426
+ logger.debug " no game match (two teams required) found for line: >#{line}<"
427
+ return false
428
+ end
429
+
430
+ pos = find_game_pos!( line )
431
+
432
+ if is_postponed?( line )
433
+ postponed = true
434
+ date_v2 = find_date!( line, start_at: @event.start_at )
435
+ date = find_date!( line, start_at: @event.start_at )
436
+ else
437
+ postponed = false
438
+ date_v2 = nil
439
+ date = find_date!( line, start_at: @event.start_at )
440
+ end
441
+
442
+ ###
443
+ # check if date found?
444
+ # NB: ruby falsey is nil & false only (not 0 or empty array etc.)
445
+ if date
446
+ ### check: use date_v2 if present? why? why not?
447
+ @last_date = date # keep a reference for later use
448
+ else
449
+ date = @last_date # no date found; (re)use last seen date
450
+ end
451
+
452
+
453
+ scores = find_scores!( line )
454
+
455
+
456
+ ####
457
+ # note:
458
+ # only map ground if we got any grounds (setup/configured in event)
459
+
460
+ if @event.grounds.count > 0
461
+
462
+ ## todo/check: use @known_grounds for check?? why? why not??
463
+ ## use in @known_grounds = TextUtils.build_title_table_for( @event.grounds )
464
+
465
+ ##
466
+ # fix: mark mapped title w/ type (ground-) or such!! - too avoid fallthrough match
467
+ # e.g. three teams match - but only two get mapped, third team gets match for ground
468
+ # e.g Somalia v Djibouti @ Djibouti
469
+ map_ground!( line )
470
+ ground_key = find_ground!( line )
471
+ ground = ground_key.nil? ? nil : Ground.find_by_key!( ground_key )
472
+ else
473
+ # no grounds configured; always nil
474
+ ground = nil
475
+ end
476
+
477
+ logger.debug " line: >#{line}<"
478
+
479
+
480
+ ### todo: cache team lookups in hash?
481
+
482
+ team1 = Team.find_by_key!( team1_key )
483
+ team2 = Team.find_by_key!( team2_key )
484
+
485
+ @last_team1 = team1 # store for later use for goals etc.
486
+ @last_team2 = team2
487
+
488
+
489
+ if @round.nil?
490
+ ## no round header found; calculate round from date
491
+
492
+ ###
493
+ ## todo/fix: add some unit tests for round look up
494
+ # fix: use date_v2 if present!! (old/original date; otherwise use date)
495
+
496
+ #
497
+ # fix: check - what to do with hours e.g. start_at use 00:00 and for end_at use 23.59 ??
498
+ # -- for now - remove hours (e.g. use end_of_day and beginnig_of_day)
499
+
500
+ ##
501
+ # note: start_at and end_at are dates ONLY (note datetime)
502
+ # - do NOT pass in hours etc. in query
503
+ # again use --> date.end_of_day, date.beginning_of_day
504
+ # new: not working: date.to_date, date.to_date
505
+ # will not find round if start_at same as date !! (in theory hours do not matter)
506
+
507
+ ###
508
+ # hack:
509
+ # special case for sqlite3 (date compare not working reliable; use casts)
510
+ # fix: move to adapter_name to activerecord_utils as sqlite? or similar?
511
+
512
+ if ActiveRecord::Base.connection.adapter_name.downcase.starts_with?( 'sqlite' )
513
+ logger.debug( " [sqlite] using sqlite-specific query for date compare for rounds finder" )
514
+ round = Round.where( 'event_id = ? AND ( julianday(start_at) <= julianday(?)'+
515
+ 'AND julianday(end_at) >= julianday(?))',
516
+ @event.id, date.to_date, date.to_date).first
517
+ else # all other dbs (postgresql, mysql, etc.)
518
+ round = Round.where( 'event_id = ? AND (start_at <= ? AND end_at >= ?)',
519
+ @event.id, date.to_date, date.to_date).first
520
+ end
521
+
522
+ pp round
523
+ if round.nil?
524
+ logger.warn( " !!!! no round match found for date #{date}" )
525
+ pp Round.all
526
+
527
+ ###################################
528
+ # -- try auto-adding matchday
529
+ round = Round.new
530
+
531
+ round_attribs = {
532
+ event_id: @event.id,
533
+ title: "Matchday #{date.to_date}",
534
+ pos: 999001+@patch_round_ids_pos.length, # e.g. 999<count> - 999001,999002,etc.
535
+ start_at: date.to_date,
536
+ end_at: date.to_date
537
+ }
538
+
539
+ logger.info( " auto-add round >Matchday #{date.to_date}<" )
540
+ logger.debug round_attribs.to_json
541
+
542
+ round.update_attributes!( round_attribs )
543
+
544
+ @patch_round_ids_pos << round.id # todo/check - add just id or "full" record as now - why? why not?
545
+ end
546
+
547
+ # store pos for auto-number next round if missing
548
+ # - note: only if greater/bigger than last; use max
549
+ # - note: last_round_pos might be nil - thus set to 0
550
+ if round.pos > 999000
551
+ # note: do NOT update last_round_pos for to-be-patched rounds
552
+ else
553
+ @last_round_pos = [round.pos,@last_round_pos||0].max
554
+ end
555
+
556
+ ## note: will crash (round.pos) if round is nil
557
+ logger.debug( " using round #{round.pos} >#{round.title}< start_at: #{round.start_at}, end_at: #{round.end_at}" )
558
+ else
559
+ ## use round from last round header
560
+ round = @round
561
+ end
562
+
563
+
564
+ ### check if games exists
565
+ ## with this teams in this round if yes only update
566
+ game = Game.find_by_round_id_and_team1_id_and_team2_id(
567
+ round.id, team1.id, team2.id
568
+ )
569
+
570
+ game_attribs = {
571
+ score1: scores[0],
572
+ score2: scores[1],
573
+ score1et: scores[2],
574
+ score2et: scores[3],
575
+ score1p: scores[4],
576
+ score2p: scores[5],
577
+ play_at: date,
578
+ play_at_v2: date_v2,
579
+ postponed: postponed,
580
+ knockout: round.knockout, ## note: for now always use knockout flag from round - why? why not??
581
+ ground_id: ground.present? ? ground.id : nil,
582
+ group_id: @group.present? ? @group.id : nil
583
+ }
584
+
585
+ game_attribs[ :pos ] = pos if pos.present?
586
+
587
+ ####
588
+ # note: only update if any changes (or create if new record)
589
+ if game.present? &&
590
+ game.check_for_changes( game_attribs ) == false
591
+ logger.debug " skip update game #{game.id}; no changes found"
592
+ else
593
+ if game.present?
594
+ logger.debug "update game #{game.id}:"
595
+ else
596
+ logger.debug "create game:"
597
+ game = Game.new
598
+
599
+ more_game_attribs = {
600
+ round_id: round.id,
601
+ team1_id: team1.id,
602
+ team2_id: team2.id
603
+ }
604
+
605
+ ## NB: use round.games.count for pos
606
+ ## lets us add games out of order if later needed
607
+ more_game_attribs[ :pos ] = round.games.count+1 if pos.nil?
608
+
609
+ game_attribs = game_attribs.merge( more_game_attribs )
610
+ end
611
+
612
+ logger.debug game_attribs.to_json
613
+ game.update_attributes!( game_attribs )
614
+ end
615
+
616
+ @last_game = game # store for later reference (e.g. used for goals etc.)
617
+
618
+ return true # game match found
619
+ end # method parse_game
620
+
621
+
622
+ def try_parse_date_header( line )
623
+ # note: clone line; for possible test do NOT modify in place for now
624
+ # note: returns true if parsed, false if no match
625
+ parse_date_header( line.dup )
626
+ end
627
+
628
+ def parse_date_header( line )
629
+ # note: returns true if parsed, false if no match
630
+
631
+ # line with NO teams plus include date e.g.
632
+ # [Fri Jun/17] or
633
+ # Jun/17 or
634
+ # Jun/17: etc.
635
+
636
+
637
+ match_teams!( line )
638
+ team1_key = find_team1!( line )
639
+ team2_key = find_team2!( line )
640
+
641
+ date = find_date!( line, start_at: @event.start_at )
642
+
643
+ if date && team1_key.nil? && team2_key.nil?
644
+ logger.debug( "date header line found: >#{line}<")
645
+ logger.debug( " date: #{date}")
646
+
647
+ @last_date = date # keep a reference for later use
648
+ return true
649
+ else
650
+ return false
651
+ end
652
+ end
653
+
654
+
655
+ def parse_goals( line )
656
+ logger.debug "parsing goals (fixture) line: >#{line}<"
657
+
658
+ goals = GoalsFinder.new.find!( line )
659
+
660
+ ## check if squads/rosters present for player mappings
661
+ #
662
+ squad1_count = Roster.where( event_id: @event.id, team_id: @last_team1 ).count
663
+ if squad1_count > 0
664
+ squad1 = Roster.where( event_id: @event.id, team_id: @last_team1 )
665
+ else
666
+ squad1 = []
667
+ end
668
+
669
+ squad2_count = Roster.where( event_id: @event.id, team_id: @last_team2 ).count
670
+ if squad2_count > 0
671
+ squad2 = Roster.where( event_id: @event.id, team_id: @last_team2 )
672
+ else
673
+ squad2 = []
674
+ end
675
+
676
+ #####
677
+ # todo/fix: try lookup by squads first!!!
678
+ # issue warning if player not included in squad!!
679
+
680
+ ##########
681
+ # try mapping player names to player
682
+
683
+ ## note: first delete all goals for match (and recreate new ones
684
+ # no need to figure out update/merge strategy)
685
+ @last_game.goals.delete_all
686
+
687
+
688
+ goals.each do |goal|
689
+ player_name = goal.name
690
+
691
+ player = Person.where( name: player_name ).first
692
+ if player
693
+ logger.info " player match (name eq) - using player key #{player.key}"
694
+ else
695
+ # try like match (player name might only include part of name e.g. Messi)
696
+ # try three variants
697
+ # try %Messi
698
+ # try Messi%
699
+ # try %Messi% -- check if there's an easier way w/ "one" where clause?
700
+ player = Person.where( 'name LIKE ? OR name LIKE ? OR name LIKE ?',
701
+ "%#{player_name}",
702
+ "#{player_name}%",
703
+ "%#{player_name}%"
704
+ ).first
705
+
706
+ if player
707
+ logger.info " player match (name like) - using player key #{player.key}"
708
+ else
709
+ # try synonyms
710
+ player = Person.where( 'synonyms LIKE ? OR synonyms LIKE ? OR synonyms LIKE ?',
711
+ "%#{player_name}",
712
+ "#{player_name}%",
713
+ "%#{player_name}%"
714
+ ).first
715
+ if player
716
+ logger.info " player match (synonyms like) - using player key #{player.key}"
717
+ else
718
+ # auto-create player (player not found)
719
+ logger.info " player NOT found >#{player_name}< - auto-create"
720
+
721
+ ## fix: add auto flag (for auto-created persons/players)
722
+ ## fix: move title_to_key logic to person model etc.
723
+ player_key = TextUtils.title_to_key( player_name )
724
+ player_attribs = {
725
+ key: player_key,
726
+ title: player_name
727
+ }
728
+ logger.info " using attribs: #{player_attribs.inspect}"
729
+
730
+ player = Person.create!( player_attribs )
731
+ end
732
+ end
733
+ end
734
+
735
+ goal_attribs = {
736
+ game_id: @last_game.id,
737
+ team_id: goal.team == 1 ? @last_team1.id : @last_team2.id,
738
+ person_id: player.id,
739
+ minute: goal.minute,
740
+ offset: goal.offset,
741
+ penalty: goal.penalty,
742
+ owngoal: goal.owngoal,
743
+ score1: goal.score1,
744
+ score2: goal.score2
745
+ }
746
+
747
+ logger.info " adding goal using attribs: #{goal_attribs.inspect}"
748
+ Goal.create!( goal_attribs )
749
+ end # each goals
750
+
751
+ end # method parse_goals
752
+
753
+
754
+ =begin
755
+ ###### add to person and use!!!
756
+ def self.create_or_update_from_values( values, more_attribs={} )
757
+ ## key & title required
758
+
759
+ attribs, more_values = find_key_n_title( values )
760
+ attribs = attribs.merge( more_attribs )
761
+
762
+ ## check for optional values
763
+ Person.create_or_update_from_attribs( attribs, more_values )
764
+ end
765
+ =end
766
+
767
+
768
+ def parse_fixtures( reader )
769
+
770
+ reader.each_line do |line|
771
+
772
+ if is_goals?( line )
773
+ parse_goals( line )
774
+ elsif is_round_def?( line )
775
+ ## todo/fix: add round definition (w begin n end date)
776
+ ## todo: do not patch rounds with definition (already assume begin/end date is good)
777
+ ## -- how to deal with matches that get rescheduled/postponed?
778
+ parse_round_def( line )
779
+ elsif is_round?( line )
780
+ parse_round_header( line )
781
+ elsif is_group_def?( line ) ## NB: group goes after round (round may contain group marker too)
782
+ ### todo: add pipe (|) marker (required)
783
+ parse_group_def( line )
784
+ elsif is_group?( line )
785
+ ## -- lets you set group e.g. Group A etc.
786
+ parse_group_header( line )
787
+ elsif try_parse_game( line )
788
+ # do nothing here
789
+ elsif try_parse_date_header( line )
790
+ # do nothing here
791
+ else
792
+ logger.info "skipping line (no match found): >#{line}<"
793
+ end
794
+ end # lines.each
795
+
796
+ ###########################
797
+ # backtrack and patch round pos and round dates (start_at/end_at)
798
+ # note: patch dates must go first! (otherwise sort_by_date will not work for round pos)
799
+
800
+ unless @patch_round_ids_dates.empty?
801
+ ###
802
+ # fix: do NOT patch if auto flag is set to false !!!
803
+ # e.g. rounds got added w/ round def (not w/ round header)
804
+
805
+ # note: use uniq - to allow multiple round headers (possible?)
806
+
807
+ Round.find( @patch_round_ids_dates.uniq ).each do |r|
808
+ logger.debug "patch round start_at/end_at date for #{r.title}:"
809
+
810
+ ## note:
811
+ ## will add "scope" pos first e.g
812
+ #
813
+ ## SELECT "games".* FROM "games" WHERE "games"."round_id" = ?
814
+ # ORDER BY pos, play_at asc [["round_id", 7]]
815
+ # thus will NOT order by play_at but by pos first!!!
816
+ # =>
817
+ # need to unscope pos!!! or use unordered_games - games_by_play_at_date etc.??
818
+ # thus use reorder()!!! - not just order('play_at asc')
819
+
820
+ games = r.games.reorder( 'play_at asc' ).all
821
+
822
+ ## skip rounds w/ no games
823
+
824
+ ## todo/check/fix: what's the best way for checking assoc w/ 0 recs?
825
+ next if games.size == 0
826
+
827
+ # note: make sure start_at/end_at is date only (e.g. use play_at.to_date)
828
+ # sqlite3 saves datetime in date field as datetime, for example (will break date compares later!)
829
+
830
+ round_attribs = {
831
+ start_at: games[0].play_at.to_date, # use games.first ?
832
+ end_at: games[-1].play_at.to_date # use games.last ? why? why not?
833
+ }
834
+
835
+ logger.debug round_attribs.to_json
836
+ r.update_attributes!( round_attribs )
837
+ end
838
+ end
839
+
840
+ unless @patch_round_ids_pos.empty?
841
+
842
+ # step 0: check for offset (last_round_pos)
843
+ if @last_round_pos
844
+ offset = @last_round_pos
845
+ logger.info " +++ patch round pos - use offset; start w/ #{offset}"
846
+ else
847
+ offset = 0
848
+ logger.debug " patch round pos - no offset; start w/ 0"
849
+ end
850
+
851
+ # step 1: sort by date
852
+ # step 2: update pos
853
+ # note: use uniq - to allow multiple round headers (possible?)
854
+ Round.order( 'start_at asc').find( @patch_round_ids_pos.uniq ).each_with_index do |r,idx|
855
+ # note: starts counting w/ zero(0)
856
+ logger.debug "[#{idx+1}] patch round pos >#{offset+idx+1}< for #{r.title}:"
857
+ round_attribs = {
858
+ pos: offset+idx+1
859
+ }
860
+
861
+ # update title if Matchday XXXX e.g. use Matchday 1 etc.
862
+ if r.title.starts_with?('Matchday')
863
+ round_attribs[:title] = "Matchday #{offset+idx+1}"
864
+ end
865
+
866
+ logger.debug round_attribs.to_json
867
+ r.update_attributes!( round_attribs )
868
+
869
+ # update last_round_pos offset too
870
+ @last_round_pos = [offset+idx+1,@last_round_pos||0].max
871
+ end
872
+ end
873
+
874
+ end # method parse_fixtures
875
+
876
+ end # class GameReader
877
+ end # module SportDb