scrap_cbf_record 0.1.0

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.
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ScrapCbfRecord
4
+ class ActiveRecord
5
+ # Class responsible for saving rankings to database
6
+ class Rankings < Base
7
+ #
8
+ # @param [rankings] hash contaning the rankings
9
+ # @return [nil]
10
+ def initialize(rankings)
11
+ raise_unless_respond_to_each(rankings, :rankings)
12
+
13
+ configurations = ScrapCbfRecord.config
14
+
15
+ super(configurations.ranking)
16
+
17
+ @rankings = rankings
18
+ end
19
+
20
+ # Creates/Updates the matches found on the instance variable rankings
21
+ # Update if ranking already exist, otherwise create it.
22
+ #
23
+ # @param [championship_hash] the championship associated with the rankings
24
+ # @raise [ActiveRecordValidationError] if fail on saving
25
+ # @return [Boolean] true if not exception is raise
26
+ def create_or_update(championship_hash)
27
+ championship, serie = find_championship_by(championship_hash)
28
+
29
+ ::ActiveRecord::Base.transaction do
30
+ @rankings.each do |hash|
31
+ ranking = find_ranking_by(hash, championship, serie)
32
+ team = Ranking.team.find_by(name: hash[:team])
33
+ next_opponent =
34
+ Ranking.next_opponent.find_by(name: hash[:next_opponent])
35
+
36
+ associations = {
37
+ championship: championship,
38
+ team: team,
39
+ next_opponent: next_opponent
40
+ }
41
+
42
+ ranking = normalize_before_save(ranking, hash, associations)
43
+
44
+ save_or_log_error(ranking)
45
+ end
46
+ end
47
+ true
48
+ end
49
+
50
+ private
51
+
52
+ def find_championship_by(championship_hash)
53
+ championship =
54
+ Ranking.championship.find_by!(year: championship_hash[:year])
55
+
56
+ serie = championship_hash[:serie]
57
+
58
+ [championship, serie]
59
+ end
60
+
61
+ def find_ranking_by(hash, championship, serie)
62
+ Ranking.find_by(
63
+ position: hash[:position],
64
+ championship: championship,
65
+ serie: serie
66
+ )
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ScrapCbfRecord
4
+ class ActiveRecord
5
+ # Class responsible for saving rounds to database
6
+ class Rounds < Base
7
+ #
8
+ # @param [rounds] hash contaning the rounds
9
+ # @return [nil]
10
+ def initialize(rounds)
11
+ raise_unless_respond_to_each(rounds, :rounds)
12
+
13
+ configurations = ScrapCbfRecord.config
14
+
15
+ super(configurations.round)
16
+
17
+ @rounds = rounds
18
+ end
19
+
20
+ # Creates the rounds found on the instance variable rounds
21
+ # Create only if doesn't exist, otherwise do nothing
22
+ #
23
+ # @param [championship_hash] the championship associated with the rounds
24
+ # @raise [ActiveRecordValidationError] if fail on saving
25
+ # @return [Boolean] true if not exception is raise
26
+ def create_unless_found(championship_hash)
27
+ championship, serie = find_championship_by(championship_hash)
28
+
29
+ ::ActiveRecord::Base.transaction do
30
+ @rounds.each do |hash|
31
+ round = Round.find_by(
32
+ number: hash[:number],
33
+ championship: championship,
34
+ serie: serie
35
+ )
36
+ next if round
37
+
38
+ hash = normalize_before_create(hash, { championship: championship })
39
+
40
+ round = Round.new(hash)
41
+
42
+ save_or_log_error(round)
43
+ end
44
+ end
45
+ true
46
+ end
47
+
48
+ private
49
+
50
+ def find_championship_by(championship_hash)
51
+ championship =
52
+ Round.championship.find_by!(year: championship_hash[:year])
53
+
54
+ serie = championship_hash[:serie]
55
+
56
+ [championship, serie]
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ScrapCbfRecord
4
+ class ActiveRecord
5
+ # Class responsible for saving teams to database
6
+ class Teams < Base
7
+ #
8
+ # @param [teams] hash contaning the teams
9
+ # @return [nil]
10
+ def initialize(teams)
11
+ raise_unless_respond_to_each(teams, :teams)
12
+
13
+ configurations = ScrapCbfRecord.config
14
+
15
+ super(configurations.team)
16
+
17
+ @teams = teams
18
+ end
19
+
20
+ # Creates the teams found on the instance variable teams
21
+ # Create only if doesn't exist, otherwise do nothing
22
+ #
23
+ # @raise [ActiveRecordValidationError] if fail on saving
24
+ # @return [Boolean] true if not exception is raise
25
+ def create_unless_found
26
+ ::ActiveRecord::Base.transaction do
27
+ @teams.each do |hash|
28
+ team = Team.find_by(name: hash[:name])
29
+ next if team
30
+
31
+ hash = normalize_before_create(hash)
32
+
33
+ team = Team.new(hash)
34
+
35
+ save_or_log_error(team)
36
+ end
37
+ end
38
+ true
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,217 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+
5
+ require_relative 'configs/base'
6
+ require_relative 'configs/championship'
7
+ require_relative 'configs/match'
8
+ require_relative 'configs/ranking'
9
+ require_relative 'configs/round'
10
+ require_relative 'configs/team'
11
+
12
+ class ScrapCbfRecord
13
+ # This class is responsible for holding the configs on how records
14
+ # must be save on database. That means:
15
+ # - which class to use to save the records
16
+ # - which record attributes to exclude on create and update
17
+ # - which record attributes to rename
18
+ # - which db associations the record has.
19
+ # Each of the configs are set for each record:
20
+ # - match record
21
+ # - ranking record
22
+ # - round record
23
+ # - team record
24
+ class Config
25
+ include Singleton
26
+
27
+ attr_reader :championship,
28
+ :match,
29
+ :ranking,
30
+ :round,
31
+ :team
32
+
33
+ def initialize
34
+ @championship = Championship.new
35
+ @match = Match.new
36
+ @ranking = Ranking.new
37
+ @round = Round.new
38
+ @team = Team.new
39
+ end
40
+
41
+ # Define the logger to be use by ActiveSupport::TaggedLogging
42
+ #
43
+ # @param [Logger] active support logger with params
44
+ # @return [::Logger]
45
+ def logger=(logger)
46
+ TagLogger.logger = ActiveSupport::TaggedLogging.new(logger)
47
+ end
48
+
49
+ # Return an array with all record classes
50
+ #
51
+ # @return [Array]
52
+ def record_classes
53
+ [
54
+ @championship.klass,
55
+ @match.klass,
56
+ @ranking.klass,
57
+ @round.klass,
58
+ @team.klass
59
+ ]
60
+ end
61
+
62
+ # Return an array with all config classes
63
+ #
64
+ # @return [Array]
65
+ def config_classes
66
+ [
67
+ @championship,
68
+ @match,
69
+ @ranking,
70
+ @round,
71
+ @team
72
+ ]
73
+ end
74
+
75
+ # Restaure configs to default
76
+ #
77
+ # @return [Boolean] true if works
78
+ def restore
79
+ @championship = Championship.new
80
+ @match = Match.new
81
+ @ranking = Ranking.new
82
+ @round = Round.new
83
+ @team = Team.new
84
+
85
+ true
86
+ end
87
+
88
+ private_class_method :new
89
+
90
+ # Validate users/default configuration
91
+ # - check if record classes were defined
92
+ # - check if attrs were defined on record classes
93
+ #
94
+ # @raise [RecordClassNotDefinedError] if record class is not defined
95
+ # @raise [RecordAssociationAttributeNotDefinedError]
96
+ # if association undefined
97
+ # @raise [RecordRenameAttributeNotDefinedError] if renamed attr undefined
98
+ # @raise [RecordAttributeNotDefinedError] if attr is undefined
99
+ # @return [Boolean] true if pass
100
+ def validate!
101
+ config_classes.each do |config_record|
102
+ validate_record_presence!(config_record)
103
+
104
+ validate_attrs_presence!(config_record)
105
+
106
+ validate_associations_presence!(config_record)
107
+ end
108
+
109
+ true
110
+ end
111
+
112
+ # Validate if Record is defined
113
+ #
114
+ # @raise [RecordClassNotDefinedError] if record is not defined
115
+ # @return [Objct] true if works
116
+ def validate_record_presence!(config_record)
117
+ config_record.klass
118
+ rescue NameError
119
+ raise RecordClassNotDefinedError, config_record
120
+ end
121
+
122
+ # Validate if association attribute exist on Record class
123
+ #
124
+ # @raise [RecordAssociationAttributeNotDefinedError] if not found
125
+ # @return [Array] returns array of associations
126
+ def validate_associations_presence!(config_record)
127
+ record_instance = config_record.klass.new
128
+
129
+ # validte foreign key associations
130
+ config_record.associations.each do |_key, assoc|
131
+ begin
132
+ record_instance.send assoc[:foreign_key] if assoc.key?(:foreign_key)
133
+ rescue NameError
134
+ raise(
135
+ RecordAssociationAttributeNotDefinedError.new(
136
+ config_record,
137
+ assoc[:foreign_key]
138
+ )
139
+ )
140
+ end
141
+ end
142
+ end
143
+
144
+ # Validate the presence on the record class
145
+ # for the attributes renamed and non-exclude.
146
+ #
147
+ # @raise [RecordRenameAttributeNotDefinedError] if renamed not found
148
+ # @raise [RecordAttributeNotDefinedError] if non-exclude not found
149
+ # @return [Boolean] returns true if valid
150
+ def validate_attrs_presence!(config_record)
151
+ record_instance = config_record.klass.new
152
+ exclude_attrs = config_record.exclude_attrs
153
+ rename_attrs = config_record.rename_attrs
154
+
155
+ config_record.record_attrs.each do |attribute|
156
+ # if is a renamed attr, its original form is exclude
157
+ # so we only have to check it here
158
+ if rename_attrs.key?(attribute)
159
+ validate_attr_presence_from_rename!(
160
+ record_instance,
161
+ attribute,
162
+ rename_attrs
163
+ )
164
+ else
165
+ # non-exclude are the ones: not renamed, associated or excluded
166
+ validate_attr_presence_from_exclude!(
167
+ record_instance,
168
+ attribute,
169
+ exclude_attrs
170
+ )
171
+ end
172
+ end
173
+
174
+ true
175
+ end
176
+
177
+ # Validate if renamed attribute exist on Record class
178
+ #
179
+ # @raise [RecordRenameAttributeNotDefinedError] if not found
180
+ # @return [Object] returns the attribute found
181
+ def validate_attr_presence_from_rename!(
182
+ record_instance,
183
+ attribute,
184
+ rename_attrs
185
+ )
186
+ if rename_attrs.key?(attribute)
187
+ record_instance.send rename_attrs[attribute]
188
+ end
189
+ rescue NameError
190
+ raise(
191
+ ScrapCbfRecord::RecordRenameAttributeNotDefinedError.new(
192
+ record_instance,
193
+ rename_attrs[attribute]
194
+ )
195
+ )
196
+ end
197
+
198
+ # Validate if non-exclude attribute exist on Record class
199
+ #
200
+ # @raise [RecordAttributeNotDefinedError] if not found
201
+ # @return [Object] returns the attribute found
202
+ def validate_attr_presence_from_exclude!(
203
+ record_instance,
204
+ attribute,
205
+ exclude_attrs
206
+ )
207
+ record_instance.send attribute unless exclude_attrs.include?(attribute)
208
+ rescue NameError
209
+ raise(
210
+ ScrapCbfRecord::RecordAttributeNotDefinedError.new(
211
+ record_instance,
212
+ attribute
213
+ )
214
+ )
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,258 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ScrapCbfRecord
4
+ class Config
5
+ # Superclass for the classes lib/config/<record>.rb
6
+ class Base
7
+ class << self
8
+ # Users may configure these settings
9
+ #
10
+ # @return [Hash] holding users or default settings
11
+ def default
12
+ raise NotImplementedError, 'default method must be implemented'
13
+ end
14
+
15
+ # These settings are not configurable by user
16
+ #
17
+ # @return [Hash] holding system settings
18
+ def required
19
+ raise NotImplementedError, 'required method must be implemented'
20
+ end
21
+
22
+ # Returns the key :class_name from the method default
23
+ # Class_name is used to find a specific record class
24
+ # The user may set a custom name for the specific record class
25
+ #
26
+ # @return [String, Symbol] holding class name
27
+ def default_class_name
28
+ default[:class_name]
29
+ end
30
+
31
+ # Returns the key :rename_attrs from the method default
32
+ #
33
+ # @return [Hash] with key been attrs name and value the renamed
34
+ def default_rename_attrs
35
+ default[:rename_attrs]
36
+ end
37
+
38
+ # Returns the key :exclude_attrs_on_create from the method default
39
+ #
40
+ # @return [Array] holding attrs to be exclude on create
41
+ def default_exclude_attrs_on_create
42
+ default[:exclude_attrs_on_create]
43
+ end
44
+
45
+ # Returns the key :exclude_attrs_on_update from the method default
46
+ # Rename_attrs is a array of strings contaning the attrs to be renamed
47
+ #
48
+ # @return [Array] holding attrs to be exclude on update
49
+ def default_exclude_attrs_on_update
50
+ default[:exclude_attrs_on_update]
51
+ end
52
+
53
+ # Returns the key :associations from the method default
54
+ # The associations are name of associations that a record has
55
+ #
56
+ # @return [Array] holding existent associations for the record
57
+ def default_associations
58
+ default[:associations]
59
+ end
60
+
61
+ # Returns the key :must_exclude_attrs from the method required
62
+ #
63
+ # @return [Array] holding attrs that must be excluded
64
+ def must_exclude_attrs
65
+ required[:must_exclude_attrs]
66
+ end
67
+ end
68
+
69
+ # @param [class_name] default[:class_name]
70
+ # @param [rename_attrs] default[:rename_attrs]
71
+ # @param [exclude_attrs_on_create] default[:exclude_attrs_on_create]
72
+ # @param [exclude_attrs_on_update] default[:exclude_attrs_on_update]
73
+ # @param [associations] default[:associations]
74
+ # @return [nil]
75
+ def initialize(
76
+ class_name,
77
+ rename_attrs,
78
+ exclude_attrs_on_create,
79
+ exclude_attrs_on_update,
80
+ associations
81
+ )
82
+ @class_name = class_name
83
+ @rename_attrs = rename_attrs
84
+ @exclude_attrs_on_create = exclude_attrs_on_create
85
+ @exclude_attrs_on_update = exclude_attrs_on_update
86
+ @associations = associations
87
+ end
88
+
89
+ # These method receives the users settings
90
+ #
91
+ # @param [class_name]
92
+ # @param [rename_attrs]
93
+ # @param [exclude_attrs_on_create]
94
+ # @param [exclude_attrs_on_update]
95
+ # @param [associations]
96
+ # @return [nil]
97
+ def config=(
98
+ class_name,
99
+ rename_attrs,
100
+ exclude_attrs_on_create,
101
+ exclude_attrs_on_update,
102
+ associations
103
+ )
104
+ @class_name = class_name
105
+ @rename_attrs = rename_attrs
106
+ @exclude_attrs_on_create = exclude_attrs_on_create
107
+ @exclude_attrs_on_update = exclude_attrs_on_update
108
+ @associations = associations
109
+ end
110
+
111
+ # Returns the class defined on class_name instance variable
112
+ # Class_name is a string or symbol and is set by user or default
113
+ #
114
+ # @raise [NameError] with class is not found
115
+ # @return [Object] class set by user
116
+ def klass
117
+ Object.const_get(@class_name)
118
+ end
119
+
120
+ # Check if argument matches with the subclasses name.
121
+ # This helps check if config correspond to the right active record config
122
+ #
123
+ # @param [record_type] symbol/string to be match with self subclasse name
124
+ # @return [Boolean]
125
+ def record_is_a?(record_type)
126
+ config_name = self.class.name.split('::').last
127
+
128
+ config_name.downcase == record_type.to_s
129
+ end
130
+
131
+ # Check if attribute was renamed.
132
+ # It will search on attrs and associations for the calling class.
133
+ # If not found returns the attribute.
134
+ #
135
+ # @param [attribute] the attribute to be searched on the calling class
136
+ # @return [Symbol]
137
+ def searchable_attr(attribute)
138
+ attribute = attribute.to_sym
139
+
140
+ foreign_key = fetch_association_foreign_key(attribute)
141
+
142
+ return foreign_key if foreign_key
143
+
144
+ return @rename_attrs[attribute] if @rename_attrs.key?(attribute)
145
+
146
+ attribute
147
+ end
148
+
149
+ # Returns the foreign key for the given association name.
150
+ # @note the association must be passed in the form <association-name>_id
151
+ # to distinguish from the record hash attrs using the association name
152
+ #
153
+ # @param association [String, Symbol] <association-name>_id
154
+ # @return [Symbol, nil] Symbol if exist
155
+ def fetch_association_foreign_key(association_fk)
156
+ # associations are in the for of association-name_id
157
+ attr_name, attr_id = association_fk.to_s.split('_')
158
+
159
+ return unless attr_name && attr_id
160
+
161
+ association_name = attr_name.to_sym
162
+ return unless @associations.key?(association_name)
163
+
164
+ @associations[association_name][:foreign_key]
165
+ end
166
+
167
+ # Check if config has specific setting association.
168
+ #
169
+ # @return [Boolean]
170
+ def championship_associate?
171
+ return false unless associations?
172
+
173
+ @championship_associate ||= @associations.key?(:championship)
174
+ end
175
+
176
+ # Check if current config has specific setting association.
177
+ #
178
+ # @return [Boolean]
179
+ def round_associate?
180
+ return false unless associations?
181
+
182
+ @round_associate ||= @associations.key?(:round)
183
+ end
184
+
185
+ # Check if current config has specific setting association.
186
+ #
187
+ # @return [Boolean]
188
+ def team_associate?
189
+ return false unless associations?
190
+
191
+ @team_associate ||= @associations.key?(:team)
192
+ end
193
+
194
+ # Check if current config has any association.
195
+ #
196
+ # @return [Boolean]
197
+ def associations?
198
+ @associations && !@associations.empty?
199
+ end
200
+
201
+ # Return the excludes attributes by user and lib.
202
+ # @note: non-exclude are the ones: not renamed, associated or excluded.
203
+ # @note: Lib excludes must_exclude_attrs
204
+ # @note: Lib excludes renamed attrs
205
+ # @note: Lib excludes associations simple form
206
+ # e.g championship is simple form from championship_id
207
+ #
208
+ # @return [Array]
209
+ def exclude_attrs
210
+ # This handle especial case: round and team doesn't have update action
211
+ user_exclusion = if record_is_a?(:round) || record_is_a?(:team)
212
+ @exclude_attrs_on_create
213
+ else
214
+ (@exclude_attrs_on_create & @exclude_attrs_on_update)
215
+ end
216
+
217
+ user_exclusion +
218
+ must_exclude_attrs +
219
+ @rename_attrs.keys +
220
+ @associations.keys
221
+ end
222
+
223
+ # Returns required must_exclude_attrs
224
+ #
225
+ # @return [Array]
226
+ def must_exclude_attrs
227
+ self.class.must_exclude_attrs
228
+ end
229
+
230
+ # Returns record_attrs
231
+ #
232
+ # @return [Array]
233
+ def record_attrs
234
+ self.class.record_attrs
235
+ end
236
+
237
+ def default_class_name
238
+ self.class.default_class_name
239
+ end
240
+
241
+ def default_rename_attrs
242
+ self.class.default_rename_attrs
243
+ end
244
+
245
+ def default_exclude_attrs_on_create
246
+ self.class.default_exclude_attrs_on_create
247
+ end
248
+
249
+ def default_exclude_attrs_on_update
250
+ self.class.default_exclude_attrs_on_update
251
+ end
252
+
253
+ def default_associations
254
+ self.class.default_associations
255
+ end
256
+ end
257
+ end
258
+ end