scrap_cbf_record 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/scrap_cbf_record.rb +72 -0
- data/lib/scrap_cbf_record/active_record.rb +105 -0
- data/lib/scrap_cbf_record/active_records/base.rb +196 -0
- data/lib/scrap_cbf_record/active_records/matches.rb +80 -0
- data/lib/scrap_cbf_record/active_records/rankings.rb +70 -0
- data/lib/scrap_cbf_record/active_records/rounds.rb +60 -0
- data/lib/scrap_cbf_record/active_records/teams.rb +42 -0
- data/lib/scrap_cbf_record/config.rb +217 -0
- data/lib/scrap_cbf_record/configs/base.rb +258 -0
- data/lib/scrap_cbf_record/configs/championship.rb +97 -0
- data/lib/scrap_cbf_record/configs/match.rb +164 -0
- data/lib/scrap_cbf_record/configs/ranking.rb +152 -0
- data/lib/scrap_cbf_record/configs/round.rb +113 -0
- data/lib/scrap_cbf_record/configs/team.rb +99 -0
- data/lib/scrap_cbf_record/errors.rb +83 -0
- data/lib/scrap_cbf_record/logger.rb +20 -0
- data/lib/scrap_cbf_record/models/base.rb +16 -0
- data/lib/scrap_cbf_record/models/championship.rb +28 -0
- data/lib/scrap_cbf_record/models/concerns/active_record_relation.rb +37 -0
- data/lib/scrap_cbf_record/models/match.rb +71 -0
- data/lib/scrap_cbf_record/models/ranking.rb +58 -0
- data/lib/scrap_cbf_record/models/round.rb +54 -0
- data/lib/scrap_cbf_record/models/team.rb +22 -0
- data/lib/scrap_cbf_record/version.rb +5 -0
- metadata +170 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1689f847640b2f8bcfd5e9362b785d4b703bddd402097082f0c8f6bace631afe
|
4
|
+
data.tar.gz: 0fca0b02a69c838721a5f60fc1228e059bb35a2dd95f383b4d5cec94ca13b0d3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4600056001b3c311ee5a9ea08ec1a3e35b6a1aa3502002afa8fc54c693c00bbf7094126f8a835418ddbb6164ab4b50bcf82680f255d8adf850da84976006766d
|
7
|
+
data.tar.gz: 2857154aa9b4acff0221fa0ad6447d3d77eb685bbf61584ae795f15c85b55b1a24f5b0c677d49408163e928ae11bf8af8c3017eeae33ba41a1649c627cb21e20
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'scrap_cbf_record/version'
|
4
|
+
require_relative 'scrap_cbf_record/logger'
|
5
|
+
require_relative 'scrap_cbf_record/errors'
|
6
|
+
require_relative 'scrap_cbf_record/models/base'
|
7
|
+
require_relative 'scrap_cbf_record/models/championship'
|
8
|
+
require_relative 'scrap_cbf_record/models/match'
|
9
|
+
require_relative 'scrap_cbf_record/models/ranking'
|
10
|
+
require_relative 'scrap_cbf_record/models/round'
|
11
|
+
require_relative 'scrap_cbf_record/models/team'
|
12
|
+
require_relative 'scrap_cbf_record/models/concerns/active_record_relation'
|
13
|
+
require_relative 'scrap_cbf_record/config'
|
14
|
+
require_relative 'scrap_cbf_record/active_record'
|
15
|
+
|
16
|
+
# This module saves on database the output from the gem ScrapCbf
|
17
|
+
#
|
18
|
+
# It has two modules to accomplish that:
|
19
|
+
# - configs: holds the settings for how to save the data
|
20
|
+
# - records: responsible for saving the data on database.
|
21
|
+
#
|
22
|
+
# There configs are:
|
23
|
+
# - championship
|
24
|
+
# - match
|
25
|
+
# - ranking
|
26
|
+
# - round
|
27
|
+
# - team
|
28
|
+
#
|
29
|
+
# The records are:
|
30
|
+
# - matches: saves the matches for a specific championship
|
31
|
+
# - rankings: saves the rankings for a specific championship
|
32
|
+
# - rounds: saves the rounds for a specific championship
|
33
|
+
# - teams: saves the teams that participated on the specific champ.
|
34
|
+
class ScrapCbfRecord
|
35
|
+
class << self
|
36
|
+
# Returns the global configurations for these module
|
37
|
+
#
|
38
|
+
# @return [ScrapCbfRecord::Config]
|
39
|
+
def config
|
40
|
+
@config ||= settings
|
41
|
+
end
|
42
|
+
|
43
|
+
# Sets the global configurations
|
44
|
+
# We can set the configurations in the following way:
|
45
|
+
#
|
46
|
+
# ScrapCbfRecord.settings do |config|
|
47
|
+
# config.championship = {
|
48
|
+
# class_name: 'Championship'
|
49
|
+
# rename_attrs: {},
|
50
|
+
# exclude_attrs_on_create: %i[],
|
51
|
+
# exclude_attrs_on_update: %i[],
|
52
|
+
# associations: %i[]
|
53
|
+
# }
|
54
|
+
#
|
55
|
+
# config.match = { ... }
|
56
|
+
# config.ranking = { ... }
|
57
|
+
# config.round = { ... }
|
58
|
+
# config.team = { ... }
|
59
|
+
# end
|
60
|
+
# If a config or a config's attribute was not set,
|
61
|
+
# default setting will be used
|
62
|
+
#
|
63
|
+
# @return [ScrapCbfRecord::Config]
|
64
|
+
def settings
|
65
|
+
configuration = Config.instance
|
66
|
+
|
67
|
+
yield configuration if block_given?
|
68
|
+
|
69
|
+
@config = configuration
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
5
|
+
require 'active_support/core_ext/hash/except'
|
6
|
+
|
7
|
+
require_relative 'active_records/base'
|
8
|
+
require_relative 'active_records/matches'
|
9
|
+
require_relative 'active_records/rankings'
|
10
|
+
require_relative 'active_records/rounds'
|
11
|
+
require_relative 'active_records/teams'
|
12
|
+
|
13
|
+
class ScrapCbfRecord
|
14
|
+
# This module uses Active Record module to save data on database.
|
15
|
+
class ActiveRecord
|
16
|
+
class << self
|
17
|
+
def save(records)
|
18
|
+
new(records).save
|
19
|
+
|
20
|
+
true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# The argument records is a hash (json or not) with the following look :
|
25
|
+
# - hash[:championship] the championship for a specific year and divison
|
26
|
+
# - hash[:matches] the matches for the specific championship
|
27
|
+
# - hash[:rankings] the rankings for the specific championship
|
28
|
+
# - hash[:rounds] the rounds for the specific championship
|
29
|
+
# - hash[:teams] the teams that participated on the specific championship
|
30
|
+
#
|
31
|
+
# @param [records] hash or json returned from ScrapCbf gem
|
32
|
+
# @return [nil]
|
33
|
+
def initialize(records)
|
34
|
+
records = parse_json!(records) if records.is_a?(String)
|
35
|
+
|
36
|
+
raise ::ArgumentError, invalid_type_message unless records.is_a?(Hash)
|
37
|
+
|
38
|
+
@records = records.with_indifferent_access
|
39
|
+
|
40
|
+
validate_record_key_presence!(@records)
|
41
|
+
|
42
|
+
@championship = @records[:championship]
|
43
|
+
@matches = @records[:matches]
|
44
|
+
@rankings = @records[:rankings]
|
45
|
+
@rounds = @records[:rounds]
|
46
|
+
@teams = @records[:teams]
|
47
|
+
end
|
48
|
+
|
49
|
+
# Save records to the database.
|
50
|
+
# Note: Because of database relationships and dependencies between records
|
51
|
+
# there maybe exist a saving order.
|
52
|
+
# - Teams must be save before Rankings and Match.
|
53
|
+
# - Rounds must be save before Matches
|
54
|
+
#
|
55
|
+
# @raise [ActiveRecordValidationError] in case of failing while saving
|
56
|
+
#
|
57
|
+
# @return [Boolean] true in case of success
|
58
|
+
def save
|
59
|
+
save_teams(@teams)
|
60
|
+
save_rankings(@rankings, @championship)
|
61
|
+
save_rounds(@rounds, @championship)
|
62
|
+
save_matches(@matches, @championship)
|
63
|
+
|
64
|
+
true
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def save_teams(teams)
|
70
|
+
Teams.new(teams).create_unless_found
|
71
|
+
end
|
72
|
+
|
73
|
+
def save_rankings(rankings, championship)
|
74
|
+
Rankings.new(rankings).create_or_update(championship)
|
75
|
+
end
|
76
|
+
|
77
|
+
def save_rounds(rounds, championship)
|
78
|
+
Rounds.new(rounds).create_unless_found(championship)
|
79
|
+
end
|
80
|
+
|
81
|
+
def save_matches(matches, championship)
|
82
|
+
Matches.new(matches).create_or_update(championship)
|
83
|
+
end
|
84
|
+
|
85
|
+
def invalid_type_message
|
86
|
+
'must be a Hash or Json of a Hash'
|
87
|
+
end
|
88
|
+
|
89
|
+
def parse_json!(records)
|
90
|
+
JSON.parse(records)
|
91
|
+
rescue JSON::ParserError => e
|
92
|
+
raise JsonDecodeError, e
|
93
|
+
end
|
94
|
+
|
95
|
+
def validate_record_key_presence!(records)
|
96
|
+
raise MissingKeyError, 'championship' unless records.key?(:championship)
|
97
|
+
raise MissingKeyError, 'matches' unless records.key?(:matches)
|
98
|
+
raise MissingKeyError, 'rankings' unless records.key?(:rankings)
|
99
|
+
raise MissingKeyError, 'rounds' unless records.key?(:rounds)
|
100
|
+
raise MissingKeyError, 'teams' unless records.key?(:teams)
|
101
|
+
|
102
|
+
true
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ScrapCbfRecord
|
4
|
+
class ActiveRecord
|
5
|
+
# Superclass for the classes lib/active_records/<record>.rb
|
6
|
+
class Base
|
7
|
+
def initialize(config)
|
8
|
+
# config is associated with the current record class
|
9
|
+
#
|
10
|
+
@config = config
|
11
|
+
|
12
|
+
# This reference the user class used to save on database
|
13
|
+
#
|
14
|
+
@model = config.klass
|
15
|
+
|
16
|
+
# current configs set by users
|
17
|
+
#
|
18
|
+
@associations = @config.associations
|
19
|
+
@exclude_attrs_on_create = @config.exclude_attrs_on_create
|
20
|
+
@exclude_attrs_on_update = @config.exclude_attrs_on_update
|
21
|
+
@rename_attrs = @config.rename_attrs
|
22
|
+
|
23
|
+
# current configs required by the system
|
24
|
+
#
|
25
|
+
@must_exclude_attrs = @config.must_exclude_attrs
|
26
|
+
end
|
27
|
+
|
28
|
+
# Normalize, for create and update, the new record with:
|
29
|
+
# Setting Associations
|
30
|
+
# Rename attributes
|
31
|
+
# Exclusion of attributes
|
32
|
+
#
|
33
|
+
# @param [Object, Nil] the record if exist
|
34
|
+
# @param [Hash] contaning the new record
|
35
|
+
# @param [Hash] contaning the existent record's associations
|
36
|
+
# @return [Object] normalized
|
37
|
+
def normalize_before_save(record, hash, associations = {})
|
38
|
+
if record
|
39
|
+
hash = normalize_before_update(hash, associations)
|
40
|
+
|
41
|
+
record.assign_attributes(hash)
|
42
|
+
|
43
|
+
record
|
44
|
+
else
|
45
|
+
hash = normalize_before_create(hash, associations)
|
46
|
+
|
47
|
+
@model.new(hash)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Normalize, on create, the new record with:
|
52
|
+
# Setting Associations
|
53
|
+
# Rename attributes
|
54
|
+
# Exclusion of attributes
|
55
|
+
#
|
56
|
+
# @param [Hash] contaning the new record
|
57
|
+
# @param [Hash] contaning the existent record's associations
|
58
|
+
# @return [Hash] normalized
|
59
|
+
def normalize_before_create(hash, assocs = {})
|
60
|
+
hash = include_associations(
|
61
|
+
hash,
|
62
|
+
@associations,
|
63
|
+
assocs
|
64
|
+
)
|
65
|
+
|
66
|
+
hash = exclude_attrs(
|
67
|
+
hash,
|
68
|
+
@exclude_attrs_on_create,
|
69
|
+
@must_exclude_attrs,
|
70
|
+
@associations.keys
|
71
|
+
)
|
72
|
+
|
73
|
+
hash = rename_attrs(hash, @rename_attrs)
|
74
|
+
|
75
|
+
hash
|
76
|
+
end
|
77
|
+
|
78
|
+
# Normalize, on update, the new record with:
|
79
|
+
# Setting Associations
|
80
|
+
# Rename attributes
|
81
|
+
# Exclusion of attributes
|
82
|
+
#
|
83
|
+
# @param [Hash] contaning the new record
|
84
|
+
# @param [Hash] contaning the existent record's associations
|
85
|
+
# @return [Hash] normalized
|
86
|
+
def normalize_before_update(hash, assocs = {})
|
87
|
+
hash = include_associations(
|
88
|
+
hash,
|
89
|
+
@associations,
|
90
|
+
assocs
|
91
|
+
)
|
92
|
+
|
93
|
+
hash = exclude_attrs(
|
94
|
+
hash,
|
95
|
+
@exclude_attrs_on_update,
|
96
|
+
@must_exclude_attrs,
|
97
|
+
@associations.keys
|
98
|
+
)
|
99
|
+
|
100
|
+
hash = rename_attrs(hash, @rename_attrs)
|
101
|
+
|
102
|
+
hash
|
103
|
+
end
|
104
|
+
|
105
|
+
# Save record instance or log the errors found
|
106
|
+
#
|
107
|
+
# @raise [ActiveRecordValidationError]
|
108
|
+
# @param record [ActiveRecord] instance to be saved
|
109
|
+
# @return [ActiveRecord] the instance saved
|
110
|
+
def save_or_log_error(record)
|
111
|
+
if record.valid?
|
112
|
+
record.save
|
113
|
+
else
|
114
|
+
log_record_errors(record)
|
115
|
+
# It raises custom error to display message warning about the logs.
|
116
|
+
raise ActiveRecordValidationError
|
117
|
+
end
|
118
|
+
record
|
119
|
+
end
|
120
|
+
|
121
|
+
protected
|
122
|
+
|
123
|
+
# Includes associations <attribute_id> and its value to the record hash
|
124
|
+
# only include if the associations was set by the user.
|
125
|
+
#
|
126
|
+
# @param hash [Hash] record hash to be modified
|
127
|
+
# @param associations [Hash] hash contaning associations details
|
128
|
+
# @param assocs [Hash] hash contaning the associations
|
129
|
+
# values returned by find_by
|
130
|
+
# @return [Hash] record hash with associations included
|
131
|
+
def include_associations(hash, associations, assocs)
|
132
|
+
associations.each do |name, attrs|
|
133
|
+
instance = assocs[name.to_sym]
|
134
|
+
|
135
|
+
foreign_key = attrs[:foreign_key]
|
136
|
+
|
137
|
+
# for cases where instance is:
|
138
|
+
# - association is empty (nil)
|
139
|
+
# - association is present
|
140
|
+
#
|
141
|
+
# update hash with the foreign_key
|
142
|
+
hash[foreign_key.to_sym] = (instance.id if instance.present?)
|
143
|
+
end
|
144
|
+
|
145
|
+
hash
|
146
|
+
end
|
147
|
+
|
148
|
+
# Exclude some keys from the record hash
|
149
|
+
#
|
150
|
+
# @param hash [Hash] record hash to be modified
|
151
|
+
# @param attrs [Array] has the keys to be exclude.
|
152
|
+
# These attrs are the union of exclude_attrs_on_create/update.
|
153
|
+
# @param must_exclude [Array] has the keys to be excluded.
|
154
|
+
# The keys are set by the lib, and is used to remove unwanted attrs.
|
155
|
+
# @param associations [Array] has the names of the associations.
|
156
|
+
# include_associations method does't remove the associations added.
|
157
|
+
# e.g if championship association exist, then:
|
158
|
+
# add championship_id to hash.
|
159
|
+
# But, it doesn't remove the championship key from the hash.
|
160
|
+
# It's remove here.
|
161
|
+
# @return [Hash] record hash modified
|
162
|
+
def exclude_attrs(hash, attrs, must_exclude, associations)
|
163
|
+
exclude = attrs + associations + must_exclude
|
164
|
+
hash.except(*exclude)
|
165
|
+
end
|
166
|
+
|
167
|
+
# Rename keys from the record hash
|
168
|
+
#
|
169
|
+
# @param hash [Hash] record hash to be modified
|
170
|
+
# @param renames [Hash] has the keys to be renamed by the key's value.
|
171
|
+
# @return [Hash] record hash modified
|
172
|
+
def rename_attrs(hash, renames)
|
173
|
+
# rename attrs
|
174
|
+
renames.each do |key, val|
|
175
|
+
hash[val.to_sym] = hash.delete(key) if hash.key?(key)
|
176
|
+
end
|
177
|
+
|
178
|
+
hash
|
179
|
+
end
|
180
|
+
|
181
|
+
def raise_unless_respond_to_each(records, records_type)
|
182
|
+
return if records.respond_to?(:each)
|
183
|
+
|
184
|
+
raise ::ArgumentError, "#{records_type} must respond to method :each"
|
185
|
+
end
|
186
|
+
|
187
|
+
def log_record_errors(record)
|
188
|
+
TagLogger.with_context([Time.current], 'Errors found while saving')
|
189
|
+
record.errors.each do |attribute, message|
|
190
|
+
TagLogger.with_context(:info, record.inspect.to_s)
|
191
|
+
TagLogger.with_context(:error, "#{attribute}: #{message}")
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ScrapCbfRecord
|
4
|
+
class ActiveRecord
|
5
|
+
# Class responsible for saving matches to database
|
6
|
+
class Matches < Base
|
7
|
+
#
|
8
|
+
# @param [matches] hash contaning the matches
|
9
|
+
# @return [nil]
|
10
|
+
def initialize(matches)
|
11
|
+
raise_unless_respond_to_each(matches, :matches)
|
12
|
+
|
13
|
+
configurations = ScrapCbfRecord.config
|
14
|
+
|
15
|
+
super(configurations.match)
|
16
|
+
|
17
|
+
@matches = matches
|
18
|
+
end
|
19
|
+
|
20
|
+
# Creates/Updates the matches found on the instance variable matches
|
21
|
+
# Update if match already exist, otherwise create it
|
22
|
+
#
|
23
|
+
# @param [championship_hash] the championship associated with the matches
|
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
|
+
@matches.each do |hash|
|
31
|
+
attrs = [hash, championship, serie]
|
32
|
+
match = find_match_by(*attrs)
|
33
|
+
round = find_round_by(*attrs)
|
34
|
+
team = Match.team.find_by(name: hash[:team])
|
35
|
+
opponent = Match.opponent.find_by(name: hash[:opponent])
|
36
|
+
|
37
|
+
associations = {
|
38
|
+
championship: championship,
|
39
|
+
round: round,
|
40
|
+
team: team,
|
41
|
+
opponent: opponent
|
42
|
+
}
|
43
|
+
|
44
|
+
match = normalize_before_save(match, hash, associations)
|
45
|
+
|
46
|
+
save_or_log_error(match)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
true
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def find_championship_by(championship_hash)
|
55
|
+
championship =
|
56
|
+
Match.championship.find_by!(year: championship_hash[:year])
|
57
|
+
|
58
|
+
serie = championship_hash[:serie]
|
59
|
+
|
60
|
+
[championship, serie]
|
61
|
+
end
|
62
|
+
|
63
|
+
def find_match_by(hash, championship, serie)
|
64
|
+
Match.find_by(
|
65
|
+
id_match: hash[:id_match],
|
66
|
+
championship: championship,
|
67
|
+
serie: serie
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
def find_round_by(hash, championship, serie)
|
72
|
+
Match.round.find_by(
|
73
|
+
number: hash[:round],
|
74
|
+
championship: championship,
|
75
|
+
serie: serie
|
76
|
+
)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|