scrap_cbf_record 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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