basketball 0.0.9 → 0.0.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +31 -21
- data/basketball.gemspec +14 -8
- data/exe/basketball +91 -0
- data/lib/basketball/app/coordinator_cli.rb +56 -72
- data/lib/basketball/app/coordinator_repository.rb +12 -88
- data/lib/basketball/app/document_repository.rb +67 -0
- data/lib/basketball/app/file_store.rb +16 -0
- data/lib/basketball/app/in_memory_store.rb +42 -0
- data/lib/basketball/app/league_repository.rb +20 -0
- data/lib/basketball/app/league_serializable.rb +99 -0
- data/lib/basketball/app/room_cli.rb +30 -26
- data/lib/basketball/app/room_repository.rb +1 -41
- data/lib/basketball/app.rb +10 -2
- data/lib/basketball/draft/pick.rb +0 -7
- data/lib/basketball/draft/room.rb +11 -13
- data/lib/basketball/entity.rb +9 -6
- data/lib/basketball/org/conference.rb +47 -0
- data/lib/basketball/org/division.rb +43 -0
- data/lib/basketball/org/has_divisions.rb +25 -0
- data/lib/basketball/org/has_players.rb +20 -0
- data/lib/basketball/org/has_teams.rb +24 -0
- data/lib/basketball/org/league.rb +59 -32
- data/lib/basketball/org.rb +12 -1
- data/lib/basketball/season/arena.rb +26 -25
- data/lib/basketball/season/calendar.rb +52 -22
- data/lib/basketball/season/coordinator.rb +25 -18
- data/lib/basketball/season/detail.rb +47 -0
- data/lib/basketball/season/exhibition.rb +1 -1
- data/lib/basketball/season/opponent.rb +6 -0
- data/lib/basketball/season/record.rb +92 -0
- data/lib/basketball/season/scheduler.rb +223 -0
- data/lib/basketball/season/standings.rb +56 -0
- data/lib/basketball/season.rb +6 -0
- data/lib/basketball/value_object.rb +6 -28
- data/lib/basketball/value_object_dsl.rb +30 -0
- data/lib/basketball/version.rb +1 -1
- metadata +22 -6
- /data/exe/{basketball-room → basketball-draft-room} +0 -0
- /data/exe/{basketball-coordinator → basketball-season-coordinator} +0 -0
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Basketball
|
4
|
+
module App
|
5
|
+
# Base class for all repositories which are based on a flat document.
|
6
|
+
# At the very minimum sub-classes should implement #to_h(object) and #from_h(hash).
|
7
|
+
class DocumentRepository
|
8
|
+
attr_reader :store
|
9
|
+
|
10
|
+
def initialize(store = InMemoryStore.new)
|
11
|
+
super()
|
12
|
+
|
13
|
+
@store = store
|
14
|
+
end
|
15
|
+
|
16
|
+
def load(id)
|
17
|
+
contents = store.read(id)
|
18
|
+
|
19
|
+
deserialize(contents).tap do |object|
|
20
|
+
object.send('id=', id)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def save(id, object)
|
25
|
+
object.send('id=', id)
|
26
|
+
|
27
|
+
contents = serialize(object)
|
28
|
+
|
29
|
+
store.write(id, contents)
|
30
|
+
|
31
|
+
object
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete(object)
|
35
|
+
return false unless object.id
|
36
|
+
|
37
|
+
store.delete(object.id)
|
38
|
+
|
39
|
+
object.send('id=', nil)
|
40
|
+
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
def from_h(hash)
|
47
|
+
Entity.new(hash[:id])
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_h(entity)
|
51
|
+
{ id: entity.id }
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def deserialize(string)
|
57
|
+
hash = JSON.parse(string, symbolize_names: true)
|
58
|
+
|
59
|
+
from_h(hash)
|
60
|
+
end
|
61
|
+
|
62
|
+
def serialize(object)
|
63
|
+
to_h(object).to_json
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -4,7 +4,15 @@ module Basketball
|
|
4
4
|
module App
|
5
5
|
# Knows how to read and write documents to disk.
|
6
6
|
class FileStore
|
7
|
+
class PathNotFoundError < StandardError; end
|
8
|
+
|
9
|
+
def exist?(path)
|
10
|
+
File.exist?(path)
|
11
|
+
end
|
12
|
+
|
7
13
|
def read(path)
|
14
|
+
raise PathNotFoundError, "'#{path}' not found" unless exist?(path)
|
15
|
+
|
8
16
|
File.read(path)
|
9
17
|
end
|
10
18
|
|
@@ -17,6 +25,14 @@ module Basketball
|
|
17
25
|
|
18
26
|
nil
|
19
27
|
end
|
28
|
+
|
29
|
+
def delete(path)
|
30
|
+
raise PathNotFoundError, "'#{path}' not found" unless exist?(path)
|
31
|
+
|
32
|
+
File.delete(path)
|
33
|
+
|
34
|
+
nil
|
35
|
+
end
|
20
36
|
end
|
21
37
|
end
|
22
38
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Basketball
|
4
|
+
module App
|
5
|
+
# Knows how to read and write documents to a Ruby Hash.
|
6
|
+
class InMemoryStore
|
7
|
+
class KeyNotFoundError < StandardError; end
|
8
|
+
|
9
|
+
attr_reader :data
|
10
|
+
|
11
|
+
def initialize(data = {})
|
12
|
+
@data = data
|
13
|
+
|
14
|
+
freeze
|
15
|
+
end
|
16
|
+
|
17
|
+
def exist?(key)
|
18
|
+
data.key?(key.to_s)
|
19
|
+
end
|
20
|
+
|
21
|
+
def read(key)
|
22
|
+
raise KeyNotFoundError, "'#{key}' not found" unless exist?(key)
|
23
|
+
|
24
|
+
data[key.to_s]
|
25
|
+
end
|
26
|
+
|
27
|
+
def write(key, contents)
|
28
|
+
data[key.to_s] = contents
|
29
|
+
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def delete(key)
|
34
|
+
raise KeyNotFoundError, "'#{key}' not found" unless exist?(key)
|
35
|
+
|
36
|
+
data.delete(key)
|
37
|
+
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Basketball
|
4
|
+
module App
|
5
|
+
# Knows how to flatten a League instance and rehydrate one from JSON and/or a Ruby hash.
|
6
|
+
class LeagueRepository < DocumentRepository
|
7
|
+
include LeagueSerializable
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def from_h(hash)
|
12
|
+
deserialize_league(hash)
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_h(league)
|
16
|
+
serialize_league(league)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Basketball
|
4
|
+
module App
|
5
|
+
# Provides methods to serialize/deserialize a League object
|
6
|
+
module LeagueSerializable
|
7
|
+
# Serialization
|
8
|
+
|
9
|
+
def serialize_league(league)
|
10
|
+
{
|
11
|
+
id: league.id,
|
12
|
+
conferences: serialize_conferences(league.conferences)
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
def serialize_conferences(conferences)
|
17
|
+
conferences.map do |conference|
|
18
|
+
{
|
19
|
+
id: conference.id,
|
20
|
+
divisions: serialize_divisions(conference.divisions)
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def serialize_divisions(divisions)
|
26
|
+
divisions.map do |division|
|
27
|
+
{
|
28
|
+
id: division.id,
|
29
|
+
teams: serialize_teams(division.teams)
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def serialize_teams(teams)
|
35
|
+
teams.map do |team|
|
36
|
+
{
|
37
|
+
id: team.id,
|
38
|
+
players: serialize_players(team.players)
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def serialize_players(players)
|
44
|
+
players.map do |player|
|
45
|
+
{
|
46
|
+
id: player.id,
|
47
|
+
overall: player.overall,
|
48
|
+
position: player.position&.code
|
49
|
+
}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Deserialization
|
54
|
+
|
55
|
+
def deserialize_league(league_hash)
|
56
|
+
Org::League.new(
|
57
|
+
conferences: deserialize_conferences(league_hash[:conferences])
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
def deserialize_conferences(conference_hashes)
|
62
|
+
(conference_hashes || []).map do |conference_hash|
|
63
|
+
Org::Conference.new(
|
64
|
+
id: conference_hash[:id],
|
65
|
+
divisions: deserialize_divisions(conference_hash[:divisions])
|
66
|
+
)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def deserialize_divisions(division_hashes)
|
71
|
+
(division_hashes || []).map do |division_hash|
|
72
|
+
Org::Division.new(
|
73
|
+
id: division_hash[:id],
|
74
|
+
teams: deserialize_teams(division_hash[:teams])
|
75
|
+
)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def deserialize_teams(team_hashes)
|
80
|
+
(team_hashes || []).map do |team_hash|
|
81
|
+
Org::Team.new(
|
82
|
+
id: team_hash[:id],
|
83
|
+
players: deserialize_players(team_hash[:players])
|
84
|
+
)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def deserialize_players(player_hashes)
|
89
|
+
(player_hashes || []).map do |player_hash|
|
90
|
+
Org::Player.new(
|
91
|
+
id: player_hash[:id],
|
92
|
+
overall: player_hash[:overall],
|
93
|
+
position: Org::Position.new(player_hash[:position])
|
94
|
+
)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -3,23 +3,27 @@
|
|
3
3
|
module Basketball
|
4
4
|
module App
|
5
5
|
# Examples:
|
6
|
-
# exe/basketball-room -o tmp/draft.json
|
7
|
-
# exe/basketball-room -i tmp/draft.json -o tmp/draft-wip.json -s 26 -p P-5,P-10 -
|
8
|
-
# exe/basketball-room -i tmp/draft-wip.json -x 2
|
9
|
-
# exe/basketball-room -i tmp/draft-wip.json -g -
|
10
|
-
# exe/basketball-room -i tmp/draft-wip.json -s 30 -
|
11
|
-
# exe/basketball-room -i tmp/draft-wip.json -
|
12
|
-
#
|
13
|
-
# exe/basketball-room -o tmp/draft-wip.json -ale
|
6
|
+
# exe/basketball-draft-room -o tmp/draft.json
|
7
|
+
# exe/basketball-draft-room -i tmp/draft.json -o tmp/draft-wip.json -s 26 -p P-5,P-10 -l 10
|
8
|
+
# exe/basketball-draft-room -i tmp/draft-wip.json -x 2
|
9
|
+
# exe/basketball-draft-room -i tmp/draft-wip.json -g -l 10
|
10
|
+
# exe/basketball-draft-room -i tmp/draft-wip.json -s 30 -l 10
|
11
|
+
# exe/basketball-draft-room -i tmp/draft-wip.json -ate
|
14
12
|
class RoomCLI
|
15
13
|
class PlayerNotFound < StandardError; end
|
16
14
|
|
17
|
-
attr_reader :opts,
|
15
|
+
attr_reader :opts,
|
16
|
+
:io,
|
17
|
+
:room_repository
|
18
18
|
|
19
|
-
def initialize(
|
20
|
-
|
21
|
-
|
22
|
-
|
19
|
+
def initialize(
|
20
|
+
args:,
|
21
|
+
io: $stdout,
|
22
|
+
room_repository: RoomRepository.new(FileStore.new)
|
23
|
+
)
|
24
|
+
@io = io
|
25
|
+
@opts = slop_parse(args)
|
26
|
+
@room_repository = room_repository
|
23
27
|
|
24
28
|
if opts[:input].to_s.empty? && opts[:output].to_s.empty?
|
25
29
|
io.puts('Input and/or output paths are required.')
|
@@ -37,7 +41,7 @@ module Basketball
|
|
37
41
|
status(room)
|
38
42
|
write(room)
|
39
43
|
events(room)
|
40
|
-
|
44
|
+
teams(room)
|
41
45
|
query(room)
|
42
46
|
|
43
47
|
self
|
@@ -63,15 +67,15 @@ module Basketball
|
|
63
67
|
|
64
68
|
def slop_parse(args)
|
65
69
|
Slop.parse(args) do |o|
|
66
|
-
o.banner = 'Usage: basketball-room [options] ...'
|
70
|
+
o.banner = 'Usage: basketball-draft-room [options] ...'
|
67
71
|
|
68
72
|
o.string '-i', '--input', 'Path to load the Room from. If omitted then a new draft will be generated.'
|
69
73
|
o.string '-o', '--output', 'Path to write the room to (if omitted then input path will be used)'
|
70
74
|
o.integer '-s', '--simulate', 'Number of picks to simulate (default is 0).', default: 0
|
71
75
|
o.bool '-a', '--simulate-all', 'Simulate the rest of the draft', default: false
|
72
76
|
o.array '-p', '--picks', 'Comma-separated list of ordered player IDs to pick.', delimiter: ','
|
73
|
-
o.integer '-
|
74
|
-
o.bool '-
|
77
|
+
o.integer '-l', '--list', 'List the top rated available players (default is 0).', default: 0
|
78
|
+
o.bool '-t', '--teams', 'Output all teams and their picks', default: false
|
75
79
|
o.integer '-x', '--skip', 'Number of picks to skip (default is 0).', default: 0
|
76
80
|
o.bool '-e', '--events', 'Output event log.', default: false
|
77
81
|
|
@@ -112,11 +116,11 @@ module Basketball
|
|
112
116
|
Draft::Room.new(rounds: 12, players:, front_offices:)
|
113
117
|
end
|
114
118
|
|
115
|
-
def
|
116
|
-
return unless opts[:
|
119
|
+
def teams(room)
|
120
|
+
return unless opts[:teams]
|
117
121
|
|
118
122
|
io.puts
|
119
|
-
io.puts(room.
|
123
|
+
io.puts(room.teams)
|
120
124
|
end
|
121
125
|
|
122
126
|
def events(room)
|
@@ -129,19 +133,19 @@ module Basketball
|
|
129
133
|
end
|
130
134
|
|
131
135
|
def query(room)
|
132
|
-
|
136
|
+
list = opts[:list]
|
133
137
|
|
134
|
-
return if
|
138
|
+
return if list <= 0
|
135
139
|
|
136
|
-
players = room.undrafted_players.sort_by(&:overall).reverse.take(opts[:
|
140
|
+
players = room.undrafted_players.sort_by(&:overall).reverse.take(opts[:list])
|
137
141
|
|
138
142
|
io.puts
|
139
|
-
io.puts("Top #{
|
143
|
+
io.puts("Top #{list} available players")
|
140
144
|
io.puts(players)
|
141
145
|
end
|
142
146
|
|
143
147
|
def read
|
144
|
-
|
148
|
+
room_repository.load(opts[:input])
|
145
149
|
end
|
146
150
|
|
147
151
|
# rubocop:disable Metrics/AbcSize
|
@@ -200,7 +204,7 @@ module Basketball
|
|
200
204
|
def write(room)
|
201
205
|
output = output_default_to_input
|
202
206
|
|
203
|
-
|
207
|
+
room_repository.save(output, room)
|
204
208
|
|
205
209
|
io.puts
|
206
210
|
io.puts("Draft written to: #{output}")
|
@@ -3,54 +3,14 @@
|
|
3
3
|
module Basketball
|
4
4
|
module App
|
5
5
|
# Can load and save Room objects to JSON files.
|
6
|
-
class RoomRepository
|
6
|
+
class RoomRepository < DocumentRepository
|
7
7
|
PICK_EVENT = 'Pick'
|
8
8
|
SKIP_EVENT = 'Skip'
|
9
9
|
|
10
10
|
private_constant :PICK_EVENT, :SKIP_EVENT
|
11
11
|
|
12
|
-
attr_reader :store
|
13
|
-
|
14
|
-
def initialize(store: FileStore.new)
|
15
|
-
super()
|
16
|
-
|
17
|
-
@store = store
|
18
|
-
|
19
|
-
freeze
|
20
|
-
end
|
21
|
-
|
22
|
-
def load(path)
|
23
|
-
contents = store.read(path)
|
24
|
-
|
25
|
-
room = deserialize(contents)
|
26
|
-
|
27
|
-
room.send('id=', path)
|
28
|
-
|
29
|
-
room
|
30
|
-
end
|
31
|
-
|
32
|
-
def save(path, room)
|
33
|
-
contents = serialize(room)
|
34
|
-
|
35
|
-
store.write(path, contents)
|
36
|
-
|
37
|
-
room.send('id=', path)
|
38
|
-
|
39
|
-
room
|
40
|
-
end
|
41
|
-
|
42
12
|
private
|
43
13
|
|
44
|
-
def deserialize(string)
|
45
|
-
hash = JSON.parse(string, symbolize_names: true)
|
46
|
-
|
47
|
-
from_h(hash)
|
48
|
-
end
|
49
|
-
|
50
|
-
def serialize(object)
|
51
|
-
to_h(object).to_json
|
52
|
-
end
|
53
|
-
|
54
14
|
def from_h(hash)
|
55
15
|
front_offices = deserialize_front_offices(hash[:front_offices])
|
56
16
|
players = deserialize_players(hash[:players])
|
data/lib/basketball/app.rb
CHANGED
@@ -1,10 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
#
|
3
|
+
# Stores
|
4
4
|
require_relative 'app/file_store'
|
5
|
+
require_relative 'app/in_memory_store'
|
5
6
|
|
6
|
-
#
|
7
|
+
# Serialization
|
8
|
+
require_relative 'app/league_serializable'
|
9
|
+
|
10
|
+
# Repositories / Common
|
11
|
+
require_relative 'app/document_repository'
|
12
|
+
|
13
|
+
# Repositories / Implementations
|
7
14
|
require_relative 'app/coordinator_repository'
|
15
|
+
require_relative 'app/league_repository'
|
8
16
|
require_relative 'app/room_repository'
|
9
17
|
|
10
18
|
# Controllers
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module Basketball
|
4
4
|
module Draft
|
5
5
|
# Main pick-by-pick iterator object which will round-robin rotate team selections.
|
6
|
-
class Room
|
6
|
+
class Room < Entity
|
7
7
|
class AlreadyPickedError < StandardError; end
|
8
8
|
class EndOfDraftError < StandardError; end
|
9
9
|
class EventOutOfOrderError < StandardError; end
|
@@ -12,9 +12,11 @@ module Basketball
|
|
12
12
|
class UnknownFrontOfficeError < StandardError; end
|
13
13
|
class UnknownPlayerError < StandardError; end
|
14
14
|
|
15
|
-
attr_reader :rounds, :players, :front_offices, :events
|
15
|
+
attr_reader :rounds, :players, :front_offices, :events
|
16
16
|
|
17
17
|
def initialize(front_offices:, rounds:, players: [], events: [])
|
18
|
+
super()
|
19
|
+
|
18
20
|
raise InvalidRoundsError, "#{rounds} should be a positive number" unless rounds.positive?
|
19
21
|
|
20
22
|
@rounds = rounds
|
@@ -28,17 +30,15 @@ module Basketball
|
|
28
30
|
end
|
29
31
|
|
30
32
|
# This method will return a materialized list of teams and their selections.
|
31
|
-
def
|
32
|
-
|
33
|
-
|
34
|
-
team = Org::Team.new(id: front_office.id)
|
35
|
-
|
36
|
-
league.register!(team)
|
33
|
+
def teams
|
34
|
+
front_offices.each_with_object([]) do |front_office, memo|
|
35
|
+
team = Org::Team.new(id: front_office.id)
|
37
36
|
|
38
|
-
|
39
|
-
|
40
|
-
end
|
37
|
+
drafted_players(front_office).each do |player|
|
38
|
+
team.sign!(player)
|
41
39
|
end
|
40
|
+
|
41
|
+
memo << team
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
@@ -157,8 +157,6 @@ module Basketball
|
|
157
157
|
|
158
158
|
private
|
159
159
|
|
160
|
-
attr_writer :id
|
161
|
-
|
162
160
|
def player_events
|
163
161
|
events.select { |e| e.respond_to?(:player) }
|
164
162
|
end
|
data/lib/basketball/entity.rb
CHANGED
@@ -4,16 +4,11 @@ module Basketball
|
|
4
4
|
# Base class for uniquely identifiable classes. Subclasses are simply based on a string-based ID
|
5
5
|
# and comparison/sorting/equality will be done in a case-insensitive manner.
|
6
6
|
class Entity
|
7
|
-
extend Forwardable
|
8
7
|
include Comparable
|
9
8
|
|
10
9
|
attr_reader :id
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
def initialize(id)
|
15
|
-
raise ArgumentError, 'id is required' if id.to_s.empty?
|
16
|
-
|
11
|
+
def initialize(id = nil)
|
17
12
|
@id = id
|
18
13
|
end
|
19
14
|
|
@@ -30,8 +25,16 @@ module Basketball
|
|
30
25
|
comparable_id.hash
|
31
26
|
end
|
32
27
|
|
28
|
+
def to_s
|
29
|
+
id.to_s
|
30
|
+
end
|
31
|
+
|
33
32
|
def comparable_id
|
34
33
|
id.to_s.upcase
|
35
34
|
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_writer :id
|
36
39
|
end
|
37
40
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Basketball
|
4
|
+
module Org
|
5
|
+
# A collection of divisions, teams, and players.
|
6
|
+
class Conference < Entity
|
7
|
+
include HasDivisions
|
8
|
+
|
9
|
+
attr_reader :divisions
|
10
|
+
|
11
|
+
def initialize(id:, divisions: [])
|
12
|
+
super(id)
|
13
|
+
|
14
|
+
@divisions = []
|
15
|
+
|
16
|
+
divisions.each { |d| register_division!(d) }
|
17
|
+
|
18
|
+
freeze
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
([super] + divisions.map(&:to_s)).join("\n")
|
23
|
+
end
|
24
|
+
|
25
|
+
def teams
|
26
|
+
divisions.flat_map(&:teams)
|
27
|
+
end
|
28
|
+
|
29
|
+
def players
|
30
|
+
divisions.flat_map(&:players)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def register_division!(division)
|
36
|
+
raise ArgumentError, 'division is required' unless division
|
37
|
+
raise DivisionAlreadyRegisteredError, "#{division} already registered" if division?(division)
|
38
|
+
|
39
|
+
assert_teams_are_not_already_registered(division.teams)
|
40
|
+
|
41
|
+
divisions << division
|
42
|
+
|
43
|
+
self
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Basketball
|
4
|
+
module Org
|
5
|
+
# A collection of teams and players.
|
6
|
+
class Division < Entity
|
7
|
+
include HasTeams
|
8
|
+
|
9
|
+
attr_reader :teams
|
10
|
+
|
11
|
+
def initialize(id:, teams: [])
|
12
|
+
super(id)
|
13
|
+
|
14
|
+
@teams = []
|
15
|
+
|
16
|
+
teams.each { |t| register_team!(t) }
|
17
|
+
|
18
|
+
freeze
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
([super] + teams.map(&:to_s)).join("\n")
|
23
|
+
end
|
24
|
+
|
25
|
+
def players
|
26
|
+
teams.flat_map(&:players)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def register_team!(team)
|
32
|
+
raise ArgumentError, 'team is required' unless team
|
33
|
+
raise TeamAlreadyRegisteredError, "#{team} already registered" if team?(team)
|
34
|
+
|
35
|
+
assert_players_are_not_already_signed(team.players)
|
36
|
+
|
37
|
+
teams << team
|
38
|
+
|
39
|
+
self
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Basketball
|
4
|
+
module Org
|
5
|
+
# Helper methods for objects that can be composed of divisions which are also composed of teams
|
6
|
+
# and players.
|
7
|
+
module HasDivisions
|
8
|
+
include HasTeams
|
9
|
+
|
10
|
+
def division?(division)
|
11
|
+
divisions.include?(division)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def assert_divisions_are_not_already_registered(divisions)
|
17
|
+
divisions.each do |division|
|
18
|
+
raise DivisionAlreadyRegisteredError, "#{division} already registered" if division?(division)
|
19
|
+
|
20
|
+
assert_teams_are_not_already_registered(division.teams)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Basketball
|
4
|
+
module Org
|
5
|
+
# Helper methods for objects that can be composed of players.
|
6
|
+
module HasPlayers
|
7
|
+
def player?(player)
|
8
|
+
players.include?(player)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def assert_players_are_not_already_signed(players)
|
14
|
+
players.each do |player|
|
15
|
+
raise PlayerAlreadySignedError, "#{player} already registered" if player?(player)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|