football_api 0.0.4

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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +31 -0
  9. data/Rakefile +2 -0
  10. data/football_api-0.0.1.gem +0 -0
  11. data/football_api-0.0.2.gem +0 -0
  12. data/football_api-0.0.3.gem +0 -0
  13. data/football_api.gemspec +36 -0
  14. data/lib/football_api/base_request.rb +75 -0
  15. data/lib/football_api/card.rb +19 -0
  16. data/lib/football_api/comment.rb +13 -0
  17. data/lib/football_api/commentary.rb +79 -0
  18. data/lib/football_api/competition.rb +54 -0
  19. data/lib/football_api/errors.rb +13 -0
  20. data/lib/football_api/event.rb +19 -0
  21. data/lib/football_api/fixture.rb +44 -0
  22. data/lib/football_api/goal.rb +17 -0
  23. data/lib/football_api/match.rb +59 -0
  24. data/lib/football_api/match_bench.rb +27 -0
  25. data/lib/football_api/match_info.rb +14 -0
  26. data/lib/football_api/match_stats.rb +29 -0
  27. data/lib/football_api/match_substitutions.rb +27 -0
  28. data/lib/football_api/match_summary.rb +39 -0
  29. data/lib/football_api/match_team.rb +22 -0
  30. data/lib/football_api/mixins/requestable.rb +19 -0
  31. data/lib/football_api/mixins/symbolizer.rb +59 -0
  32. data/lib/football_api/player.rb +12 -0
  33. data/lib/football_api/standing.rb +104 -0
  34. data/lib/football_api/version.rb +3 -0
  35. data/lib/football_api.rb +32 -0
  36. data/spec/commentary_spec.rb +112 -0
  37. data/spec/competition_spec.rb +27 -0
  38. data/spec/fixture_spec.rb +90 -0
  39. data/spec/helpers/json_helper.rb +41 -0
  40. data/spec/json/commentaries.json +1967 -0
  41. data/spec/json/competitions.json +23 -0
  42. data/spec/json/fixtures.json +4559 -0
  43. data/spec/json/matches.json +17 -0
  44. data/spec/json/standings.json +719 -0
  45. data/spec/match_spec.rb +25 -0
  46. data/spec/spec_helper.rb +115 -0
  47. data/spec/standing_spec.rb +37 -0
  48. metadata +346 -0
@@ -0,0 +1,39 @@
1
+ module FootballApi
2
+ class MatchSummary
3
+
4
+ attr_accessor :id, :local_team_goals, :local_team_yellowcards, :local_team_redcards,
5
+ :visitor_team_goals, :visitor_team_yellowcards, :visitor_team_redcards
6
+
7
+ # For the teams goals, the api returns an empty array
8
+ # when there are no goals.
9
+ # Goals is an hash if there are goals to present.
10
+ def initialize(hash = {})
11
+ @id = hash[:id]
12
+
13
+ @local_team_goals = parse_team_goals(hash[:localteam])
14
+ @local_team_yellowcards = parse_cards(hash[:localteam][:yellowcards], :yellow)
15
+ @local_team_redcards = parse_cards(hash[:localteam][:redcards], :red)
16
+
17
+ @visitor_team_goals = parse_team_goals(hash[:visitorteam])
18
+ @visitor_team_yellowcards = parse_cards(hash[:visitorteam][:yellowcards], :yellow)
19
+ @visitor_team_redcards = parse_cards(hash[:visitorteam][:redcards], :red)
20
+ end
21
+
22
+ def parse_team_goals(hash = {})
23
+ return [] if !hash[:goals] || hash[:goals].is_a?(Array) || !hash[:goals][:player]
24
+
25
+ Array(hash[:goals][:player]).map do |player|
26
+ FootballApi::Goal.new(player.merge(score: player.to_s.to_i))
27
+ end
28
+ end
29
+
30
+ # If the cards struct has no members, its type is an array, so we must do
31
+ # the type validation before sending it to new.
32
+ # Like with the goals, the only way to iterpolate through all the records
33
+ # is with the hash key.
34
+ def parse_cards(hash, type)
35
+ return [] if !hash.is_a?(Hash) || !hash[:player]
36
+ Array(hash[:player]).map { |card| FootballApi::Card.new(card.merge(type: type)) }
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,22 @@
1
+ module FootballApi
2
+ class MatchTeam
3
+
4
+ attr_accessor :id, :players
5
+
6
+ def initialize(hash = {}, key)
7
+ @id = hash[:comm_match_id]
8
+ @players = parse_players(hash, key)
9
+ end
10
+
11
+ def parse_players(hash = {}, key)
12
+ players = []
13
+ Array(hash[:comm_match_teams][key][:player]).each do |player|
14
+ players << FootballApi::Player.new(player)
15
+ end
16
+ Array(hash[:comm_match_subs][key][:player]).each do |player|
17
+ players << FootballApi::Player.new(player)
18
+ end
19
+ players
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ module FootballApi
2
+ module Requestable
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ attr_accessor :action, :json_id, :params_method
9
+
10
+ ##
11
+ # Options can be any of the class attributes
12
+ def api_options(options = {})
13
+ self.action = options.delete(:action).to_s if options[:action]
14
+ self.json_id = options.delete(:json_key) if options[:json_key]
15
+ self.params_method = options.delete(:action_params)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,59 @@
1
+ module FootballApi
2
+ module Symbolizer
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ HASH_OR_ARRAY_KEYS = %i(player substitution comment)
8
+
9
+ module ClassMethods
10
+ # Custom deep symbolize of an hash
11
+ # So we can override the mess of some footbal-api arrays
12
+ def custom_deep_symbolic_hash(hash)
13
+ {}.tap do |h|
14
+ Hash(hash).each do |key, value|
15
+ key = symbolyze_key(key)
16
+ h[key] = map_value(key, value)
17
+ end
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ # Convert key to symbol
24
+ def symbolyze_key(key)
25
+ key.to_sym rescue key
26
+ end
27
+
28
+ # Recursive way of checking all hash
29
+ def map_value(key, value)
30
+ case value
31
+ when Array
32
+ value.map { |v| map_value(nil, v) }
33
+ when Hash
34
+ return custom_deep_symbolic_hash(value) unless is_a_custom_key?(key)
35
+ map_value(nil, array_from_mess(value))
36
+ else
37
+ value
38
+ end
39
+ end
40
+
41
+ def is_a_custom_key?(key)
42
+ HASH_OR_ARRAY_KEYS.include?(key)
43
+ end
44
+
45
+ # Since the custom keys come as an hash like: => { '1': { ... }, '2': { ... } }
46
+ # OR!
47
+ # A simple object from the root
48
+ # We need to unify the return value and return a simple array of objects
49
+ # Ugly? yup! as f*ck!
50
+ def array_from_mess(hash)
51
+ return key_contains_object?(hash.keys.first) ? [hash] : hash.values
52
+ end
53
+
54
+ def key_contains_object?(key)
55
+ (key.to_s =~ /[0-9]/) == nil
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,12 @@
1
+ module FootballApi
2
+ class Player
3
+ attr_accessor :number, :name, :pos, :id
4
+
5
+ def initialize(hash = {})
6
+ @number = hash[:number]
7
+ @name = hash[:name]
8
+ @pos = hash[:pos]
9
+ @id = hash[:id]
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,104 @@
1
+ module FootballApi
2
+ class Standing < FootballApi::BaseRequest
3
+ include FootballApi::Requestable
4
+
5
+ api_options action: :standings, action_params: :standing_params, json_key: :teams
6
+
7
+ class << self
8
+ attr_accessor :competition_id
9
+
10
+ def all_from_competition(competition)
11
+ self.competition_id = competition.is_a?(Competition) ? competition.id : competition
12
+
13
+ Array(response).map{ |standing| new(standing) }
14
+ end
15
+
16
+ def standing_params
17
+ { comp_id: self.competition_id }
18
+ end
19
+ end
20
+
21
+ # Response sample :
22
+ # [
23
+ # {
24
+ # "stand_id"=>"12049092",
25
+ # "stand_competition_id"=>"1204",
26
+ # "stand_season"=>"2014/2015",
27
+ # "stand_round"=>"22",
28
+ # "stand_stage_id"=>"12041081",
29
+ # "stand_group"=>"",
30
+ # "stand_country"=>"England",
31
+ # "stand_team_id"=>"9092",
32
+ # "stand_team_name"=>"Chelsea",
33
+ # "stand_status"=>"same",
34
+ # "stand_recent_form"=>"WWLDW",
35
+ # "stand_position"=>"1",
36
+ # "stand_overall_gp"=>"22",
37
+ # "stand_overall_w"=>"16",
38
+ # "stand_overall_d"=>"4",
39
+ # "stand_overall_l"=>"2",
40
+ # "stand_overall_gs"=>"51",
41
+ # "stand_overall_ga"=>"19",
42
+ # "stand_home_gp"=>"10",
43
+ # "stand_home_w"=>"10",
44
+ # "stand_home_d"=>"0",
45
+ # "stand_home_l"=>"0",
46
+ # "stand_home_gs"=>"24",
47
+ # "stand_home_ga"=>"3",
48
+ # "stand_away_gp"=>"12",
49
+ # "stand_away_w"=>"10",
50
+ # "stand_away_d"=>"0",
51
+ # "stand_away_l"=>"0",
52
+ # "stand_away_gs"=>"24",
53
+ # "stand_away_ga"=>"3",
54
+ # "stand_gd"=>"32",
55
+ # "stand_points"=>"52",
56
+ # "stand_desc"=>"Promotion - Champions League (Group Stage)"
57
+ # }, ...
58
+ # ]
59
+
60
+ attr_accessor :stand_id, :stand_competition_id, :stand_season, :stand_round, :stand_stage_id,
61
+ :stand_group, :stand_country, :stand_team_id, :stand_team_name, :stand_status,
62
+ :stand_recent_form, :stand_position, :stand_overall_gp, :stand_overall_w,
63
+ :stand_overall_d, :stand_overall_l, :stand_overall_gs, :stand_overall_ga,
64
+ :stand_home_gp, :stand_home_w, :stand_home_d, :stand_home_l, :stand_home_gs,
65
+ :stand_home_ga, :stand_away_gp, :stand_away_w, :stand_away_d, :stand_away_l,
66
+ :stand_away_gs, :stand_away_ga, :stand_gd, :stand_points, :stand_desc
67
+
68
+ def initialize(hash = {})
69
+ @stand_id = hash[:stand_id]
70
+ @stand_competition_id = hash[:stand_competition_id]
71
+ @stand_season = hash[:stand_season]
72
+ @stand_round = hash[:stand_round]
73
+ @stand_stage_id = hash[:stand_stage_id]
74
+ @stand_group = hash[:stand_group]
75
+ @stand_country = hash[:stand_country]
76
+ @stand_team_id = hash[:stand_team_id]
77
+ @stand_team_name = hash[:stand_team_name]
78
+ @stand_status = hash[:stand_status]
79
+ @stand_recent_form = hash[:stand_recent_form]
80
+ @stand_position = hash[:stand_position]
81
+ @stand_overall_gp = hash[:stand_overall_gp]
82
+ @stand_overall_w = hash[:stand_overall_w]
83
+ @stand_overall_d = hash[:stand_overall_d]
84
+ @stand_overall_l = hash[:stand_overall_l]
85
+ @stand_overall_gs = hash[:stand_overall_gs]
86
+ @stand_overall_ga = hash[:stand_overall_ga]
87
+ @stand_home_gp = hash[:stand_home_gp]
88
+ @stand_home_w = hash[:stand_home_w]
89
+ @stand_home_d = hash[:stand_home_d]
90
+ @stand_home_l = hash[:stand_home_l]
91
+ @stand_home_gs = hash[:stand_home_gs]
92
+ @stand_home_ga = hash[:stand_home_ga]
93
+ @stand_away_gp = hash[:stand_away_gp]
94
+ @stand_away_w = hash[:stand_away_w]
95
+ @stand_away_d = hash[:stand_away_d]
96
+ @stand_away_l = hash[:stand_away_l]
97
+ @stand_away_gs = hash[:stand_away_gs]
98
+ @stand_away_ga = hash[:stand_away_ga]
99
+ @stand_gd = hash[:stand_gd]
100
+ @stand_points = hash[:stand_points]
101
+ @stand_desc = hash[:stand_desc]
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,3 @@
1
+ module FootballApi
2
+ VERSION = "0.0.4"
3
+ end
@@ -0,0 +1,32 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext/object'
3
+ require 'active_support/core_ext/hash'
4
+ require 'active_support/core_ext/numeric'
5
+ require 'active_support/core_ext/string'
6
+ require 'active_support/core_ext/time'
7
+ require 'active_support/time_with_zone'
8
+ require 'httparty'
9
+ require 'football_api/mixins/requestable'
10
+ require 'football_api/mixins/symbolizer'
11
+ require 'football_api/base_request'
12
+ require 'football_api/card'
13
+ require 'football_api/comment'
14
+ require 'football_api/commentary'
15
+ require 'football_api/competition'
16
+ require 'football_api/errors'
17
+ require 'football_api/event'
18
+ require 'football_api/fixture'
19
+ require 'football_api/goal'
20
+ require 'football_api/match'
21
+ require 'football_api/match_info'
22
+ require 'football_api/match_stats'
23
+ require 'football_api/match_summary'
24
+ require 'football_api/match_team'
25
+ require 'football_api/player'
26
+ require 'football_api/standing'
27
+ require 'football_api/match_bench'
28
+ require 'football_api/match_substitutions'
29
+ require 'football_api/version'
30
+
31
+ module FootballApi
32
+ end
@@ -0,0 +1,112 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe FootballApi::Commentary do
4
+ let(:match_id) { 1788007 }
5
+ let(:static_id) { 1755579 }
6
+ let(:default) { JSONHelper::Commentaries.get(:default) }
7
+ let(:uri) {
8
+ "#{@base_url}/api/?APIKey=#{@api_key}&Action=commentaries&match_id=#{match_id}"
9
+ }
10
+
11
+ before(:each) do
12
+ stub_request(:get, uri)
13
+ .with(headers: @headers)
14
+ .to_return(status: 200, body: default.to_json)
15
+ end
16
+
17
+ let(:request_response) { FootballApi::Commentary.all_from_match(match_id) }
18
+
19
+ describe '.all_from_match' do
20
+ it 'sets match id' do
21
+ # doing a :static_id validation, this value must be set before request
22
+ # we can't request a match that we dont already have some information
23
+ # like :match_id and :match_static_id
24
+
25
+ expect(request_response).not_to be_nil
26
+ expect(request_response.static_id.to_i).to eq(static_id)
27
+ end
28
+
29
+ it 'sets all match summary cards' do
30
+ summary = request_response.match_summary
31
+
32
+ expect(summary).not_to be_nil
33
+ expect(summary.local_team_yellowcards.size).to eq(2)
34
+ expect(summary.local_team_redcards.size).to eq(0)
35
+ expect(summary.visitor_team_yellowcards.size).to eq(4)
36
+ expect(summary.visitor_team_redcards.size).to eq(0)
37
+ end
38
+
39
+ it 'sets all match summary goals' do
40
+ summary = request_response.match_summary
41
+
42
+ expect(summary).not_to be_nil
43
+ expect(summary.local_team_goals.size).to eq(1)
44
+ expect(summary.visitor_team_goals.size).to eq(2)
45
+ end
46
+
47
+ it 'sets local team and players' do
48
+ local_team = request_response.local_match_team
49
+
50
+ expect(local_team).not_to be_nil
51
+ expect(local_team.players.size).to eq(18)
52
+ expect(local_team.id).to eq(request_response.match_id)
53
+ end
54
+
55
+ it 'sets visitor team and players' do
56
+ visitor_team = request_response.visitor_match_team
57
+
58
+ expect(visitor_team).not_to be_nil
59
+ expect(visitor_team.players.size).to eq(18)
60
+ expect(visitor_team.id).to eq(request_response.match_id)
61
+ end
62
+
63
+ it 'sets match bench' do
64
+ match_bench = request_response.match_bench
65
+
66
+ expect(match_bench).not_to be_nil
67
+ expect(match_bench.match_id).to eq(request_response.match_id)
68
+ end
69
+
70
+ it 'sets local team bench' do
71
+ match_bench = request_response.match_bench
72
+
73
+ expect(match_bench.local_team).not_to be_nil
74
+ expect(match_bench.local_team.players.size).to eq(7)
75
+ end
76
+
77
+ it 'sets visitor team bench' do
78
+ match_bench = request_response.match_bench
79
+
80
+ expect(match_bench.visitor_team).not_to be_nil
81
+ expect(match_bench.visitor_team.players.size).to eq(7)
82
+ end
83
+
84
+ it 'sets match substitutions' do
85
+ match_substitutions = request_response.match_substitutions
86
+
87
+ expect(match_substitutions).not_to be_nil
88
+ expect(match_substitutions.match_id).to eq(request_response.match_substitutions.match_id)
89
+ end
90
+
91
+ it 'sets local team substitutions' do
92
+ match_substitutions = request_response.match_substitutions
93
+
94
+ expect(match_substitutions.local_team).not_to be_nil
95
+ expect(match_substitutions.local_team.size).to be(3)
96
+ end
97
+
98
+ it 'sets visitor team substitutions' do
99
+ match_substitutions = request_response.match_substitutions
100
+
101
+ expect(match_substitutions.visitor_team).not_to be_nil
102
+ expect(match_substitutions.visitor_team.size).to be(3)
103
+ end
104
+
105
+ it 'sets commentaries' do
106
+ commentaries = request_response.commentaries
107
+
108
+ expect(commentaries).not_to be_nil
109
+ expect(commentaries.size).to eq(111)
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe FootballApi::Competition do
4
+ let(:response) { JSONHelper::Competitions.get(:all) }
5
+ let(:uri) { "#{@base_url}/api/?APIKey=#{@api_key}&Action=competitions" }
6
+
7
+ before(:each) do
8
+ stub_request(:get, uri)
9
+ .with(headers: @headers)
10
+ .to_return(status: 200, body: response.to_json)
11
+ end
12
+ let(:request_response) { FootballApi::Competition.all }
13
+
14
+ describe '.all' do
15
+ it 'not_fails' do
16
+ expect(request_response).not_to be_nil
17
+ expect(request_response.size).to eq(1)
18
+ end
19
+
20
+ it 'has_competition' do
21
+ competition = request_response.first
22
+
23
+ expect(competition).not_to be_nil
24
+ expect(competition.id).not_to be_nil
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe FootballApi::Fixture do
4
+ let(:competition_id) { 1204 }
5
+
6
+ describe '.where from_to_date ' do
7
+ let(:response) { JSONHelper::Fixtures.get(:from_to_date) }
8
+
9
+ let(:from_date) { '01.01.2015' }
10
+ let(:to_date) { '01.02.2015' }
11
+ let(:from_to_parameters) {
12
+ { :comp_id => competition_id, :from_date => from_date, :to_date => to_date }
13
+ }
14
+ let(:uri) {
15
+ "#{@base_url}/api/?APIKey=#{@api_key}&Action=fixtures"\
16
+ "&comp_id=#{competition_id}&from_date=#{from_date}&to_date=#{to_date}"
17
+ }
18
+
19
+ before(:each) do
20
+ stub_request(:get, uri)
21
+ .with(headers: @headers)
22
+ .to_return(status: 200, body: response.to_json)
23
+ end
24
+
25
+ let(:request_response) { FootballApi::Fixture.where(from_to_parameters) }
26
+
27
+ it 'not_fails' do
28
+ expect(request_response).not_to be_nil
29
+ expect(request_response.size).to eq(40)
30
+ end
31
+
32
+ it 'has_first_match' do
33
+ fixture = request_response.first
34
+ expect(fixture.match).not_to be_nil
35
+ expect(fixture.match.match_comp_id.to_i).to eq(competition_id)
36
+ expect(fixture.match_events.size).to eq(2)
37
+ expect(fixture.match_timer).to be_blank
38
+ end
39
+
40
+ it 'has_last_match' do
41
+ fixture = request_response.last
42
+ expect(fixture.match).not_to be_nil
43
+ expect(fixture.match.match_comp_id.to_i).to eq(competition_id)
44
+ expect(fixture.match_events.size).to eq(4)
45
+ expect(fixture.match_timer).to be_blank
46
+ end
47
+ end
48
+
49
+ describe '.where match_date ' do
50
+ let(:response) { JSONHelper::Fixtures.get(:match_date) }
51
+
52
+ let(:match_date) { '01.01.2015' }
53
+ let(:date_parameters) {
54
+ { :comp_id => competition_id, :match_date => match_date }
55
+ }
56
+ let(:uri) {
57
+ "#{@base_url}/api/?APIKey=#{@api_key}&Action=fixtures"\
58
+ "&comp_id=#{competition_id}&match_date=#{match_date}"
59
+ }
60
+
61
+ before(:each) do
62
+ stub_request(:get, uri)
63
+ .with(headers: @headers)
64
+ .to_return(status: 200, body: response.to_json)
65
+ end
66
+
67
+ let(:request_response) { FootballApi::Fixture.where(date_parameters) }
68
+
69
+ it 'not_fails' do
70
+ expect(request_response).not_to be_nil
71
+ expect(request_response.size).to eq(10)
72
+ end
73
+
74
+ it 'has_first_match' do
75
+ fixture = request_response.first
76
+ expect(fixture.match).not_to be_nil
77
+ expect(fixture.match.match_comp_id.to_i).to eq(competition_id)
78
+ expect(fixture.match_events.size).to eq(2)
79
+ expect(fixture.match_timer).to be_blank
80
+ end
81
+
82
+ it 'has_last_match' do
83
+ fixture = request_response.last
84
+ expect(fixture.match).not_to be_nil
85
+ expect(fixture.match.match_comp_id.to_i).to eq(competition_id)
86
+ expect(fixture.match_events.size).to eq(12)
87
+ expect(fixture.match_timer).to be_blank
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,41 @@
1
+ # Automatically loads json files with an associated class for loading fixture
2
+ module JSONHelper
3
+ class << self
4
+ # Uhuhuh! What a cache!
5
+ def cache
6
+ @cache ||= {}
7
+ end
8
+
9
+ def cache_or_load(path)
10
+ cache[path] ||= begin
11
+ file = File.read(path)
12
+ JSON.parse(file).symbolize_keys
13
+ end
14
+ end
15
+
16
+ def load_file(name)
17
+ path = File.expand_path("../../json/#{name}", __FILE__)
18
+ cache_or_load(path)
19
+ end
20
+ end
21
+
22
+ # TODO: Better way to deal with this.
23
+ # Declaring a class for each one was too much.
24
+ # With this, it works, but is too meta, I think.
25
+ class Generic
26
+ def self.get(name)
27
+ new.get(name)
28
+ end
29
+
30
+ def get(name)
31
+ file_name = "#{File.basename(self.class.name.underscore)}.json"
32
+ fixtures = ::JSONHelper.load_file(file_name)
33
+ fixtures[name.to_sym].clone
34
+ end
35
+ end
36
+
37
+ Dir[File.expand_path('../../json/*.json', __FILE__)].each do |file_name|
38
+ klass_name = "#{File.basename(file_name, '.json').camelize}"
39
+ JSONHelper.const_set(klass_name, Class.new(Generic))
40
+ end
41
+ end