All Files (98.46% covered at 270.76 hits/line)
73 files in total.
3110 relevant lines.
3062 lines covered and
48 lines missed
-
# Base class for this application's controllers
-
#
-
1
class ApplicationController < ActionController::API
-
1
respond_to :json
-
1
rescue_from ::ActiveRecord::RecordNotFound, with: :rescue_record_not_found
-
-
# Include JSON serialization
-
1
include ::ActionController::Serialization
-
-
# Include authentication support
-
1
include Authenticable
-
-
1
protected
-
-
# Render an HTTP error on ActiveRecord::RecordNotFound
-
1
def rescue_record_not_found
-
11
render json: { errors: 'Record not found' }, status: :not_found
-
end
-
-
end
-
-
-
# Add authentication support to a controller
-
1
module Authenticable
-
-
# Get user associated with auth token.
-
# Overrides devise implementation.
-
#
-
# * *Returns* :
-
# - User
-
#
-
1
def current_user
-
184
@current_user ||= User.find_by(auth_token: header_token)
-
end
-
-
# Check for a current user. If not, render
-
# an error.
-
# Overrides devise implementation.
-
#
-
1
def authorize_user!
-
144
if header_token.blank?
-
# Anonymous users can't request current user
-
25
render json: { errors: 'Login required' }, status: :forbidden
-
else
-
# Token may be invalid
-
119
render json: { errors: 'Not authorized' }, status: :unauthorized unless user_signed_in?
-
end
-
end
-
-
# Check for a current user.
-
# Overrides device implementation.
-
#
-
1
def user_signed_in?
-
120
current_user.present?
-
end
-
-
1
private
-
-
1
def header_token
-
284
request.headers['Authorization']
-
end
-
-
end
-
# Match loading for controllers
-
1
module MatchLoader
-
# Load match, sets, games, teams and players at once.
-
# * *Args* :
-
# - +match_id+ -> Id of Match
-
# * *Returns* : Relation
-
1
def self.eager_load_match(match_id)
-
Match
-
.includes(first_team: [:first_player,
-
:second_player],
-
second_team: [:first_player,
-
:second_player],
-
match_sets: [set_games:
-
[:player_server,
-
:team_winner]])
-
58
.find(match_id)
-
end
-
end
-
# Controller for match scoreboards
-
#
-
# * Renders a match with sets and games using V1::MatchScoreboardSerializer
-
# * Executes commands to play the match
-
#
-
1
class V1::MatchScoreboardController < ApplicationController
-
-
1
rescue_from ::Exceptions::UnknownOperation, with: :when_unknown_operation
-
1
before_action :authorize_user!, only: [:update]
-
1
before_action :set_match_eager_load, only: [:show, :update]
-
-
# Render the match score
-
# * *Params*
-
# * +:id+ - id of a Match
-
# * *Response*
-
# * Serialized Match score
-
1
def show
-
6
render json: V1::MatchScoreboardSerializer.new(@match, root: false)
-
end
-
-
# Execute an action by calling Match.play_match!
-
# * *Params*
-
# * +:action+ - action such as +:win_game+
-
# * +:version+ - match play version number
-
# * +:team+ - id of a Team to win a game
-
# * +:player+ - id of a Player to serve or win a game
-
# * *Response*
-
# * Serialized Match score
-
1
def update
-
27
request_params = match_scoreboard_params
-
27
action = request_params[:action].to_sym
-
27
version = request_params[:version]
-
27
team_id = request_params[:team]
-
27
player_id = request_params[:player] unless team_id
-
-
27
play_params = {}
-
27
play_params[:version] = version if version
-
27
play_params[:opponent] =
-
if team_id
-
4
Team.find(team_id) if team_id
-
elsif player_id
-
14
Player.find(player_id) if player_id
-
end
-
-
27
play_match {
-
27
@match.play_match!(action, play_params)
-
}
-
end
-
-
1
private
-
-
1
def set_match_eager_load
-
# @match = self.class.eager_load_match params[:id]
-
35
@match = MatchLoader::eager_load_match params[:id]
-
end
-
-
1
def when_unknown_operation(exception)
-
4
render json: { errors: exception.message }, status: :unprocessable_entity
-
end
-
-
1
def match_scoreboard_params
-
27
params.require(:match_scoreboard).permit(:action,
-
:team,
-
:player,
-
:version)
-
end
-
-
1
def play_match
-
27
begin
-
27
yield
-
# TODO: Notify clients of a change
-
20
reload_match
-
rescue ::Exceptions::UnknownOperation
-
# raise HTTP error
-
4
raise
-
rescue => e
-
# render the scoreboard with an error element
-
3
reload_match ({ other: e.message })
-
end
-
23
render json: V1::MatchScoreboardSerializer.new(@match, root: false)
-
end
-
-
1
def reload_match(errors=nil)
-
23
@match = MatchLoader::eager_load_match params[:id]
-
23
@match.errors.messages.merge! errors if errors
-
end
-
-
end
-
# Controller for matches
-
#
-
# * Renders a list of all matches
-
# * Renders one particular match
-
# * Creates a new match
-
# * Updates a match
-
# * Deletes a match
-
# * Serializes a Match using V1::MatchSerializer
-
#
-
1
class V1::MatchesController < ApplicationController
-
1
before_action :authorize_user!, only: [:create, :update, :destroy]
-
1
before_action :set_match, only: [:show, :update, :destroy]
-
-
1
@match = nil
-
# Get a list of all matches,
-
# sorted by match title
-
# * *Response*
-
# * Serialized array of matches
-
1
def index
-
2
@matches = Match.order 'lower(title)'
-
2
render json: @matches, serializer: V1::ApplicationArraySerializer
-
end
-
-
# Get a particular match
-
# * *Params*
-
# * +:id+ - id of a Match
-
# * *Response*
-
# * Match
-
1
def show
-
4
render json: @match
-
end
-
-
# Create a match
-
# * *Request*
-
# * +:title+ - match title
-
# * +:scoring+ - scoring kind
-
# * +:doubles+ - true for doubles
-
# * +:first_team_id+ - id of a Team
-
# * +:second_team_id+ - id of a Team
-
# * +:first_player_id+ - id of a Player
-
# * +:second_player_id+ - id of a Player
-
# Teams are for a doubles match. Players are for a singles match.
-
# * *Response*
-
# * serialized Match or HTTP error
-
1
def create
-
16
@match = create_match
-
16
if @match.save
-
8
render json: @match, status: :created, location: @match
-
else
-
8
render json: { errors: @match.errors }, status: :unprocessable_entity
-
end
-
end
-
-
# Update a match
-
# * *Params*
-
# * +:id+ - id of a Match
-
# * *Request*
-
# * +:title+ - change title
-
# * +:scoring+ - change scoring kind
-
# * +:doubles+ - change doubles
-
# * +:first_team_id+ - change first team
-
# * +:second_team_id+ - change second team
-
# * +:first_player_id+ - change first player
-
# * +:second_player_id+ - change second player
-
# Teams are for a doubles match. Players are for a singles match.
-
# * *Response*
-
# * serialized Match or HTTP error
-
1
def update
-
18
doubles = match_params_doubles?(@match.doubles)
-
18
update_sym = if doubles
-
11
:update_doubles_match
-
else
-
7
:update_singles_match
-
end
-
18
if send(update_sym, @match, match_params(doubles))
-
16
render json: @match, status: :ok
-
else
-
2
render json: { errors: @match.errors }, status: :unprocessable_entity
-
end
-
end
-
-
# Delete a match
-
# * *Params*
-
# * +:id+ - id of a Match
-
# * *Response*
-
# * +:no_content+ or HTTP error
-
1
def destroy
-
1
@match.destroy
-
1
head :no_content
-
end
-
-
1
private
-
-
# Opponents for a doubles match and for a singles match are different.
-
# Singles match has players. Doubles match has teams.
-
1
def match_params(doubles)
-
34
params_var = params_require
-
34
permit = [:title, :scoring, :doubles]
-
34
permit += if doubles
-
24
[:first_team_id, :second_team_id]
-
else
-
10
[:first_player_id, :second_player_id]
-
end
-
34
params_var.permit(permit)
-
end
-
-
1
def match_params_doubles?(default_value=false)
-
34
exists_doubles_param? ? doubles_param? : default_value
-
end
-
-
1
def params_require
-
94
params.require :match
-
end
-
-
1
def doubles_param?
-
26
params_require[:doubles].to_s == 'true'
-
end
-
-
1
def exists_doubles_param?
-
34
!params_require[:doubles].nil?
-
end
-
-
1
def set_match
-
26
@match = Match.find(params[:id])
-
end
-
-
1
def create_doubles_match(params_var)
-
13
Match.new params_var
-
end
-
-
1
def update_doubles_match(match, params_var)
-
11
match.update params_var
-
end
-
-
1
def create_match
-
16
doubles = match_params_doubles?
-
16
params_var = match_params(doubles)
-
16
if doubles
-
13
create_doubles_match params_var
-
else
-
3
create_singles_match params_var
-
end
-
end
-
-
1
def create_singles_match(params_var)
-
3
Match.new player_ids_to_team_ids(params_var)
-
end
-
-
1
def update_singles_match(match, params_var)
-
7
match.update player_ids_to_team_ids(params_var)
-
end
-
-
# Change hash to contain team ids rather than player ids.
-
1
def player_ids_to_team_ids(params_var)
-
10
new_params = player_id_to_team_id(params_var, 'first')
-
10
player_id_to_team_id(new_params, 'second')
-
end
-
-
# Requests to create a singles match includes first_player_id and
-
# second_player_id.
-
# However, match model expects first_team_id and second_team_id.
-
# So, replace first_player_id with first_team_id, for example.
-
# If necessary, Player.#singles_team! will create a team to
-
# represent a singles player.
-
1
def player_id_to_team_id(params_var, prefix)
-
20
player_id_sym = "#{prefix}_player_id".to_sym
-
20
player_id = params_var[player_id_sym]
-
20
unless player_id.blank?
-
14
params_var["#{prefix}_team_id".to_sym] =
-
Player.find(player_id).singles_team!.id
-
14
params_var.except! player_id_sym
-
end
-
20
params_var
-
end
-
end
-
# Controller for players
-
#
-
# * Renders a list of all players
-
# * Renders one player
-
# * Creates a new player
-
# * Updates a player
-
# * Deletes a player
-
# * Serializes a Player using V1::PlayerSerializer
-
#
-
1
class V1::PlayersController < ApplicationController
-
1
before_action :authorize_user!, only: [:update, :create, :destroy]
-
1
before_action :set_player, only: [:show, :update, :destroy]
-
-
# Get a list of all players,
-
# sorted by player name
-
# * *Response*
-
# * serialized array of players
-
1
def index
-
2
@players = Player.order 'lower(name)'
-
2
render json: @players, serializer: V1::ApplicationArraySerializer
-
end
-
-
# Get a particular player
-
# * *Params*
-
# * +:id+ - player id
-
# * *Response*
-
# * serialized Player
-
1
def show
-
3
render json: @player
-
end
-
-
# Create a player
-
# * *Request*
-
# * +:name+ - player name
-
# * *Response*
-
# * serialized Player or HTTP error
-
1
def create
-
7
@player = Player.new(player_params)
-
-
7
if @player.save
-
3
render json: @player, status: :created, location: @player
-
else
-
4
render json: {errors: @player.errors}, status: :unprocessable_entity
-
end
-
end
-
-
# Update a player
-
# * *Params*
-
# * +:id+ - player id
-
# * *Request*
-
# * +:name+ - different player name
-
# * *Response*
-
# * serialized Player or HTTP error
-
1
def update
-
7
if @player.update(player_params)
-
3
render json: @player, status: :ok
-
else
-
4
render json: {errors: @player.errors}, status: :unprocessable_entity
-
end
-
end
-
-
# Delete a player
-
# A player in a match or on a team may not be
-
# deleted.
-
# * *Params*
-
# * +:id+ - player id
-
# * *Response*
-
# * +:no_content+ or HTTP error
-
1
def destroy
-
5
if @player.destroy
-
1
head :no_content
-
else
-
4
render json: {errors: @player.errors}, status: :unprocessable_entity
-
end
-
end
-
-
1
private
-
-
1
def set_player
-
18
@player = Player.find(params[:id])
-
end
-
-
1
def player_params
-
14
params[:player].permit :name
-
end
-
-
end
-
# Controller for a user session
-
#
-
# * Validates credentials and returns user attributes
-
# * Regenerates the authentication token of a user
-
#
-
1
class V1::SessionsController < ApplicationController
-
-
# Login a user
-
# Validate user credentials. If valid, respond with
-
# an authentication token for the user.
-
# * *Request*
-
# * +:username+
-
# * +:password+
-
# * *Response*
-
# * User or HTTP error. See V1::SessionSerializer
-
1
def create
-
3
session = params[:session]
-
3
user_password = session[:password] if session
-
3
user_username = session[:username] if session
-
3
user = user_username.present? && User.find_by(username: user_username)
-
-
3
if user && user.valid_password?(user_password)
-
# The following two lines prevent a user from being logged in
-
# on multiple devices.
-
-
# user.generate_authentication_token!
-
# user.save!
-
-
1
render json: V1::SessionSerializer.new(user, root: false)
-
else
-
2
render json: { errors: 'Invalid username or password' }, status: 422
-
end
-
end
-
-
# Clear the authentication token associated with a user
-
# * *Params*
-
# * +:id+ - authentication token
-
# * *Response*
-
# * +:no_content+ or HTTP error
-
1
def destroy
-
2
user = User.find_by(auth_token: params[:id])
-
2
if user
-
1
user.generate_authentication_token!
-
1
user.save!
-
end
-
2
head :no_content
-
end
-
end
-
# Controller for teams
-
#
-
# * Renders a list of all teams
-
# * Renders a particular team
-
# * Creates a new team
-
# * Updates a team
-
# * Deletes a team
-
# Teams can not be deleted if they are in a match
-
#
-
1
class V1::TeamsController < ApplicationController
-
1
before_action :authorize_user!, only: [:update, :create, :destroy]
-
1
before_action :set_team, only: [:show, :update, :destroy]
-
-
# Get a list of all doubles teams,
-
# sorted by team name.
-
# Singles teams are for internal use so are
-
# not shown to end users.
-
# * *Response*
-
# * List of teams
-
1
def index
-
2
@teams = Team.where(doubles: true).order 'lower(name)'
-
2
render json: @teams, serializer: V1::ApplicationArraySerializer
-
end
-
-
# Get a particular team
-
# * *Params*
-
# * +:id+ - team id
-
# * *Response*
-
# * Team
-
1
def show
-
6
render json: @team
-
end
-
-
# Create a team
-
# * *Request*
-
# * +:name+ - team name
-
# * +:first_player_id+ - first player on team
-
# * +:second_player_id+ - second player on team
-
# * *Response*
-
# * Team
-
1
def create
-
16
json = team_params
-
16
json[:doubles] = true
-
16
@team = Team.new(json)
-
-
16
if @team.save
-
6
render json: @team, status: :created, location: @team
-
else
-
10
render json: {errors: @team.errors}, status: :unprocessable_entity
-
end
-
end
-
-
# Update a team
-
# * *Params*
-
# * +:id+ - team id
-
# * *Request*
-
# * +:name+ - different team name
-
# * +:first_player_id+ - different first player
-
# * +:second_player_id+ - different second player
-
# * *Response*
-
# * Team or HTTP error
-
1
def update
-
4
if @team.update(team_params)
-
2
render json: @team, status: :ok
-
else
-
2
render json: {errors: @team.errors}, status: :unprocessable_entity
-
end
-
end
-
-
# Delete a team
-
# A team in a match may not be
-
# deleted
-
# * *Params*
-
# * +:id+ - team id
-
# * *Response*
-
# * +:no_content+ or HTTP error
-
1
def destroy
-
3
if @team.destroy
-
1
head :no_content
-
else
-
2
render json: {errors: @team.errors}, status: :unprocessable_entity
-
end
-
end
-
-
1
private
-
-
1
def set_team
-
16
@team = Team.find(params[:id])
-
end
-
-
1
def team_params
-
20
params_var = params[:team].permit(:first_player_id,
-
:second_player_id,
-
:name)
-
# team name may be nil
-
20
params_var[:name] = nil if params_var[:name].blank?
-
20
params_var
-
end
-
-
end
-
# Controller for the current user
-
#
-
# Renders the current user using V1::UserSerializer.
-
# The current user is determined
-
# by the authentication token in the request header
-
#
-
1
class V1::UserController < ApplicationController
-
1
before_action :authorize_user!, only: [:show]
-
-
# Get the current user
-
# * *Response*
-
# * serialized User
-
1
def show
-
2
render json: current_user
-
end
-
-
end
-
# Custom exception classes
-
#
-
1
module Exceptions
-
# Base class for application exceptions
-
1
class ApplicationError < StandardError; end
-
# Could not find an object that was needed to perform an operation
-
1
class NotFound < ApplicationError; end
-
# Attempt to execute an operation that is not allowed at this time
-
1
class InvalidOperation < ApplicationError; end
-
# Attempt to execute an operation that is not recognized
-
1
class UnknownOperation < ApplicationError; end
-
end
-
# Executes actions to play a match. Executes the ':win_game' action to
-
# win the current game in progress, for example.
-
1
module MatchPlay
-
-
# Class to help work with actions:
-
# * Indicate whether a particular action is enabled
-
# * Get a hash of all enabled actions
-
# * Execute an action
-
# actions include +:win_game+, +:start_game+, and +:discard_play+.
-
# See Match.play_match!
-
#
-
1
class PlayActions
-
-
# * *Args*
-
# - +match+ -> Match
-
1
def initialize(match)
-
239
@match = match
-
end
-
-
# Lookup the methods to handle a particular action
-
#
-
# * *Args* :
-
# - +action+ -> +:win_game+, +:start_game+ etc.
-
# * *Returns* : Hash or nil
-
# - +:exec+ - method to execute the action
-
# - +:query+ - method to determine if the action is enabled
-
1
def lookup_method(action)
-
6056
action = action.to_sym
-
6056
play_methods_table[action] if play_methods_table.has_key? action
-
end
-
-
# Generate a hash of valid actions
-
#
-
# * *Returns* : Hash
-
# === Example
-
# {
-
# start_game: true,
-
# discard_play: true,
-
# remove_last_change: true
-
# }
-
1
def valid_actions
-
54
result = {}
-
54
play_methods_table.each do |k, v|
-
540
if v[:query].call
-
122
result[k] = true
-
end
-
end
-
54
result
-
end
-
-
1
private
-
-
1
attr_reader :match
-
-
1
def play_methods_table
-
12163
unless @methods
-
239
@methods = {}
-
239
syms = [:start_play,
-
:discard_play, :start_set,
-
:start_game, :start_tiebreak, :remove_last_change,
-
:start_match_tiebreak,
-
:win_game, :win_tiebreak, :win_match_tiebreak]
-
239
syms.each do |sym|
-
2390
@methods[sym] =
-
{
-
exec: nil,
-
query: method("#{sym}?")
-
}
-
end
-
-
239
[:win_game, :win_tiebreak, :win_match_tiebreak].each do |sym|
-
# Methods with team parameter
-
717
@methods[sym][:exec] = lambda do |options|
-
1334
team = team_from_options options if options
-
1334
raise Exceptions::UnknownOperation, "Unknown team for action: #{sym}" unless team
-
1329
method("#{sym}!").call(team)
-
end
-
end
-
# Method with optional player parameter
-
239
[:start_game].each do |sym|
-
239
@methods[sym][:exec] = lambda do |options|
-
1261
player = player_from_options options if options
-
1261
method("#{sym}!").call(player)
-
end
-
end
-
# Methods with no parameter
-
239
syms.each do |sym|
-
2390
if @methods[sym][:exec].nil?
-
1434
@methods[sym][:exec] = lambda do |options|
-
326
method("#{sym}!").call
-
end
-
end
-
end
-
end
-
12163
@methods
-
end
-
-
1
def team_from_options(options)
-
1331
opponent = opponent_from_options options
-
1331
if opponent[:player]
-
6
match.doubles ? nil : opponent[:player].singles_team!
-
else
-
1325
opponent[:team]
-
end
-
end
-
-
1
def player_from_options(options)
-
1239
opponent = opponent_from_options options
-
1239
if opponent[:team]
-
2
opponent[:team].first_player
-
else
-
1237
opponent[:player]
-
end
-
end
-
-
1
def opponent_from_options(options)
-
2570
result = {
-
player: nil,
-
team: nil
-
}
-
2570
opponent = options[:opponent]
-
2570
if opponent
-
1500
if opponent.is_a? Team
-
1325
result[:team] = opponent
-
elsif opponent.is_a? Player
-
175
result[:player] = opponent
-
end
-
end
-
2570
result
-
end
-
-
# Operations to play a match.
-
#
-
# The following methods start a match, start a set, start a game,
-
# win a game, etc. The methods operate on the match at the current point.
-
# For example, win_game! applies to the current game in progress. If a method
-
# is called out of sequence, an exception is raised. Use the "?" methods
-
# to determine if an operation is allowed.
-
#
-
-
# Starts the match. Creates the first set.
-
1
def start_play?
-
306
!match.started
-
end
-
-
1
def start_play!
-
171
unless start_play?
-
1
raise Exceptions::InvalidOperation, 'Can\'t start play'
-
end
-
170
match.started = true
-
170
match.save!
-
170
set = create_new_set
-
170
set.save!
-
end
-
-
# Starts a new set. NOP if called immediately after start_play
-
1
def start_set?
-
193
start.set?
-
end
-
-
1
def start_set!
-
65
unless start.set?
-
1
raise Exceptions::InvalidOperation,
-
"Can\'t start set #{match.match_sets.count}"
-
end
-
64
set = create_new_set
-
64
set.save!
-
64
game = start.game
-
64
game.save!
-
end
-
-
# Start a new game. A game will be added to the current set.
-
# The server will be computed and assigned to the game.
-
1
def start_game?
-
2569
start.game?
-
end
-
-
1
def start_game!(player_server = nil)
-
1261
if player_server
-
171
update_player_server!(player_server)
-
end
-
1259
unless start.game?
-
1
last_set_var = match.last_set
-
1
raise Exceptions::InvalidOperation,
-
"can\'t start game #{last_set_var ? last_set_var.set_games.count : 0}"
-
end
-
1258
game = start.game
-
1255
game.save!
-
1255
game
-
end
-
-
1
def win_game?
-
160
win.game?
-
end
-
-
1
def win_game!(team)
-
1272
unless win.game?
-
1
raise Exceptions::InvalidOperation,
-
"Can\'t win game #{match.last_set ? match.last_set.set_games.count : 0}"
-
end
-
1271
game = win.game team
-
1271
game.save!
-
1271
complete!
-
1271
game
-
end
-
-
# Start a tiebreak at the end of a set.
-
1
def start_tiebreak?
-
112
start.tiebreak?
-
end
-
-
1
def start_tiebreak!
-
36
unless start.tiebreak?
-
2
raise Exceptions::InvalidOperation, 'can\'t start tiebreak'
-
end
-
34
game = start.tiebreak
-
34
game.save!
-
34
game
-
end
-
-
# Win a tiebreak. The winning team must be provided.
-
1
def win_tiebreak?
-
59
win.tiebreak?
-
end
-
-
1
def win_tiebreak!(team)
-
33
unless win.tiebreak?
-
2
raise Exceptions::InvalidOperation, 'can\'t win tiebreak'
-
end
-
31
game = win.tiebreak team
-
31
game.save!
-
31
complete!
-
31
game
-
end
-
-
# Start a match tiebreak. A match tiebreak can be
-
# created after each player has won an equal number of sets.
-
1
def start_match_tiebreak?
-
226
start.match_tiebreak?
-
end
-
-
1
def start_match_tiebreak!
-
38
unless start.match_tiebreak?
-
1
raise Exceptions::InvalidOperation, 'Can\'t start match tiebreak'
-
end
-
37
set = create_new_set
-
37
set.save!
-
37
game = start.tiebreak
-
37
game.save!
-
end
-
-
# Win the match tiebreak.
-
1
def win_match_tiebreak?
-
103
win.match_tiebreak?
-
end
-
-
1
def win_match_tiebreak!(team)
-
24
unless win.match_tiebreak?
-
2
raise Exceptions::InvalidOperation, 'Can\'t win match tiebreak'
-
end
-
22
game = win.match_tiebreak team
-
22
game.save!
-
22
complete!
-
22
game
-
end
-
-
# Discard all of the match scoring.
-
1
def discard_play?
-
59
match.started
-
end
-
-
1
def discard_play!
-
2
unless discard_play?
-
1
raise Exceptions::InvalidOperation, 'Can\'t discard play'
-
end
-
1
match.match_sets.destroy_all
-
1
match.first_player_server = nil
-
1
match.second_player_server = nil
-
1
match.started = false
-
1
match.team_winner = nil
-
1
match.save!
-
end
-
-
# Back out one scoring operation.
-
1
def remove_last_change!
-
14
unless remove_last_change?
-
1
raise Exceptions::InvalidOperation, 'Can\'t remove last change'
-
end
-
13
RemoveLastChange.new(match).remove_last
-
end
-
-
1
def remove_last_change?
-
68
match.started
-
end
-
-
# Utility methods and helpers
-
-
# Update first or second player serving in the match
-
1
def update_player_server!(player)
-
171
raise Exceptions::InvalidOperation, 'Player required' unless player
-
171
raise Exceptions::InvalidOperation, 'Invalid type' unless player.is_a? Player
-
171
if match.first_player_server.nil?
-
140
match.first_player_server = player
-
140
match.save!
-
31
elsif match.second_player_server.nil?
-
31
match.second_player_server = player
-
31
match.save!
-
end
-
end
-
-
1
def create_new_set
-
271
ordinal = match.match_sets.count + 1
-
271
set = MatchSet.new(match_id: match.id,
-
ordinal: ordinal,
-
scoring: match.scoring_of_set(ordinal))
-
271
match.match_sets << set
-
271
set # fluent
-
end
-
-
1
def complete!
-
1324
CompleteSetAndMatch.new(match).complete!
-
end
-
-
1
def start
-
5891
@start ||= StartNext.new(match)
-
end
-
-
1
def win
-
2975
@win ||= WinGame.new(match)
-
end
-
end
-
-
-
# Class to remove the last scoring change. Removing the last scoring change may
-
# involve deleting games or sets, or changing attribute values.
-
1
class RemoveLastChange
-
-
# * *Args*
-
# - +match+ -> Match
-
1
def initialize(match)
-
13
@match = match
-
end
-
-
-
# Execute +:remove_last_change+ action by updating and/or
-
# deleting entities.
-
1
def remove_last
-
13
@save_list = []
-
13
@destroy_list = []
-
13
if match.team_winner
-
2
remove_match_complete
-
elsif match.last_set
-
11
remove_last_set_change(match.last_set)
-
end
-
13
save_list.each(&:save!)
-
13
destroy_list.each(&:destroy)
-
end
-
-
-
1
private
-
-
1
attr_reader :match
-
# List of entities that need to be saved in order
-
# to remove the last change
-
1
attr_reader :save_list
-
-
# List of entities that need to be destroyed in
-
# order to remove the last change
-
1
attr_reader :destroy_list
-
-
1
def remove_match_complete
-
2
match.team_winner = nil
-
2
save_list << match
-
2
remove_last_set_complete match.last_set
-
end
-
-
1
def remove_last_set_change(last_set_var)
-
11
if last_set_var.team_winner
-
1
remove_last_set_complete(last_set_var)
-
10
elsif last_set_var.set_games.count == 0
-
# First set may have zero games. Other sets always start with one.
-
1
undo_start_match(last_set_var)
-
else
-
9
remove_last_game_change(last_set_var.last_game)
-
end
-
end
-
-
1
def remove_last_set_complete(last_set_var)
-
3
last_set_var.team_winner = nil
-
3
save_list << last_set_var
-
3
remove_last_game_change(last_set_var.last_game)
-
end
-
-
1
def undo_start_match(last_set_var)
-
1
match.started = false
-
1
save_list << match
-
1
destroy_list << last_set_var
-
end
-
-
1
def remove_last_game_change(last_game_var)
-
12
last_set_var = last_game_var.match_set
-
12
if last_game_var.team_winner
-
# Undo won
-
4
last_game_var.team_winner = nil
-
4
save_list << last_game_var
-
else
-
# Undo start game
-
# Discard set if first game in set but not first game in match
-
8
last = if last_set_var.ordinal > 1 && last_set_var.set_games.count == 1
-
2
last_set_var
-
else
-
6
last_game_var
-
end
-
8
destroy_list << last
-
8
save_list << match if undo_first_servers(last_set_var)
-
end
-
end
-
-
1
def undo_first_servers(last_set_var)
-
8
if match.match_sets.count == 1
-
6
games_count = last_set_var.set_games.count
-
6
match.second_player_server = nil if games_count <= 2
-
6
match.first_player_server = nil if games_count <= 1
-
6
true
-
end
-
end
-
end
-
-
# Class to help with starting games, tiebreaks, sets, and match tiebreaks
-
1
class StartNext
-
-
# * *Args*
-
# - +match+ -> Match
-
1
def initialize(match)
-
185
@match = match
-
end
-
-
# Indicate if a game can be started
-
1
def game?
-
3828
start_game_kind? :game
-
end
-
-
# Indicate if a tiebreak game can be started
-
1
def tiebreak?
-
148
start_game_kind? :tiebreak
-
end
-
-
# Indicate if a set can be started
-
1
def set?
-
258
start_set_kind? :set
-
end
-
-
# Indicate if the match tiebreak set can be started
-
1
def match_tiebreak?
-
264
start_set_kind? :tiebreak
-
end
-
-
# Start the next game
-
1
def game
-
1322
start_game_kind :game
-
end
-
-
# Start the next tiebreak game
-
1
def tiebreak
-
71
start_game_kind :tiebreak
-
end
-
-
1
private
-
-
1
attr_reader :match
-
-
1
def start_game_kind?(kind)
-
3976
last_set_var = match.last_set
-
3976
if last_set_var && last_set_var.state == :in_progress &&
-
no_game_in_progress(last_set_var)
-
3762
(kind == :tiebreak) ==
-
3762
last_set_var.tiebreak_game?(last_set_var.set_games.count + 1)
-
end
-
end
-
-
1
def no_game_in_progress(last_set_var)
-
3925
last_game = last_set_var.set_games.last
-
3925
last_game.nil? || last_game.state == :finished
-
end
-
-
1
def start_set_kind?(kind)
-
522
last_set_var = match.last_set
-
522
if match.state == :in_progress && no_set_in_progress?(last_set_var)
-
246
(kind == :tiebreak) ==
-
246
match.tiebreak_set?(match.match_sets.count + 1)
-
end
-
end
-
-
1
def no_set_in_progress?(last_set_var)
-
472
last_set_var.nil? || last_set_var.state == :complete
-
end
-
-
1
def start_game_kind(kind)
-
1393
last_set_var = match.last_set
-
1393
raise Exceptions::InvalidOperation, 'No set' unless last_set_var
-
1393
game = SetGame.new(ordinal: match.next_game_ordinal,
-
tiebreaker: kind == :tiebreak)
-
1393
assign_game_server(game) unless game.tiebreak?
-
1390
last_set_var.set_games << game
-
1390
game
-
end
-
-
1
def assign_game_server(game)
-
1322
game.player_server = NextServer.new(match).player
-
1322
if game.player_server.nil?
-
3
raise Exceptions::InvalidOperation, 'Next player server unknown'
-
end
-
end
-
end
-
-
# Class to determine which player serves next
-
1
class NextServer
-
-
# * *Args*
-
# - +match+ -> Match
-
1
def initialize(match)
-
1322
@match = match
-
end
-
-
# Determine the serving player. In a singles match, serving alternates between
-
# the two opponent players. In doubles, serving alternates between each opponent team and
-
# the players on each team.
-
# * *Returns* : Player
-
1
def player
-
# Count total games, ignoring tiebreaks
-
1322
games_played = match.set_games.all.reduce(0) do |sum, game|
-
10274
sum + (game.tiebreak? ? 0 : 1)
-
end
-
1322
if match.doubles
-
367
next_doubles_server games_played
-
else
-
955
next_singles_server games_played
-
end
-
end
-
-
1
private
-
-
1
attr_reader :match
-
-
1
def next_doubles_server(games_played)
-
367
case games_played % 4
-
when 0
-
116
match.first_player_server
-
when 1
-
98
match.second_player_server
-
when 2
-
78
other_player_on_team match.first_player_server
-
when 3
-
75
other_player_on_team match.second_player_server
-
end
-
end
-
-
1
def other_player_on_team(player)
-
153
team = match.team_of_player player
-
153
if team
-
153
if team.first_player == player
-
150
team.second_player
-
else
-
3
team.first_player
-
end
-
end
-
end
-
-
1
def next_singles_server(games_played)
-
955
if games_played.even?
-
498
match.first_player_server
-
else
-
457
other_singles_player match.first_player_server
-
end
-
end
-
-
1
def other_singles_player(player)
-
457
first = match.first_player
-
457
if player == first
-
455
match.second_player
-
else
-
2
first
-
end
-
end
-
end
-
-
# Class to help with winning games
-
1
class WinGame
-
-
# * *Args*
-
# - +match+ -> Match
-
1
def initialize(match)
-
159
@match = match
-
end
-
-
# Indicate if a game can be won (a game is in progress)
-
1
def game?
-
1432
win_game_kind? :game
-
end
-
-
# Indicate if a tiebreak game can be won (a tiebreak game is in progress).
-
1
def tiebreak?
-
92
win_game_kind?(:tiebreak)
-
end
-
-
# Indicate if a match tiebreak game can be completed (a match tiebreak is in progress)
-
1
def match_tiebreak?
-
127
win_game_kind?(:match_tiebreak)
-
end
-
-
# Win a tiebreak game at the end of a set
-
1
def tiebreak(team)
-
31
win_game_kind team, :tiebreak
-
end
-
-
# Win a normal game
-
1
def game(team)
-
1271
win_game_kind team, :game
-
end
-
-
# Win a tiebreak game in a match tiebreaker
-
1
def match_tiebreak(team)
-
22
win_game_kind team, :match_tiebreak
-
end
-
-
1
private
-
-
1
attr_reader :match
-
-
1
def win_game_kind?(kind)
-
1651
current_game_kind kind
-
end
-
-
1
def win_game_kind(team, kind)
-
1324
game = current_game_kind kind
-
1324
raise Exceptions::NotFound, 'Game not found' unless game
-
1324
raise Exceptions::InvalidOperation, 'Invalid type' unless team.is_a? Team
-
1324
game.team_winner_id = team.id
-
1324
game
-
end
-
-
1
def current_game_kind(kind)
-
2975
result = nil
-
2975
if match.started
-
2927
last_set_var = match.last_set
-
2927
last_game = last_set_var.last_game if last_set_var
-
2927
if last_game && last_game.state == :in_progress
-
2794
if last_set_var.tiebreak?
-
108
result = last_game if kind == :match_tiebreak
-
elsif last_game.tiebreak?
-
64
result = last_game if kind == :tiebreak
-
else
-
2622
result = last_game if kind == :game
-
end
-
end
-
end
-
2975
result
-
end
-
end
-
-
# Class to help with completing a set and the match. A set or the match may be completed when
-
# there is a winner.
-
1
class CompleteSetAndMatch
-
-
# * *Args*
-
# - +match+ -> Match
-
1
def initialize(match)
-
1324
@match = match
-
end
-
-
# Complete the current set if enough games have been won.
-
# Then, complete the match if enough sets have been won.
-
1
def complete!
-
1324
complete_set_play! if complete_set?
-
1324
complete_match_tiebreak! if complete_match_tiebreak?
-
1324
complete_play! if complete_play?
-
end
-
-
1
private
-
-
1
attr_reader :match
-
-
# Indicate if a set has a winner.
-
1
def complete_set?
-
1441
completed_kind? :set unless match.min_sets_to_play == 1
-
end
-
-
# Indicate if a match tiebreak has a winner.
-
1
def complete_match_tiebreak?
-
1346
completed_kind? :tiebreak
-
end
-
-
1
def completed_kind?(kind)
-
2593
last_set_var = match.last_set
-
2593
last_set_finished = last_set_var && last_set_var.compute_team_winner
-
2593
if last_set_finished
-
425
tiebreak = last_set_var.tiebreak?
-
425
(kind == :tiebreak) == tiebreak
-
end
-
end
-
-
1
def complete_set_play!
-
117
if complete_set?
-
117
last_set_var = match.last_set
-
117
last_set_var.team_winner = last_set_var.compute_team_winner
-
117
last_set_var.save!
-
end
-
end
-
-
1
def complete_match_tiebreak!
-
22
if complete_match_tiebreak?
-
22
last_set_var = match.last_set
-
22
last_set_var.team_winner = last_set_var.compute_team_winner
-
22
last_set_var.save!
-
end
-
end
-
-
1
def complete_play!
-
34
if complete_play?
-
34
if match.min_sets_to_play == 1
-
8
last_set_var = match.last_set
-
8
last_set_var.team_winner = last_set_var.compute_team_winner
-
8
last_set_var.save!
-
end
-
34
match.team_winner = match.compute_team_winner
-
34
match.save!
-
end
-
end
-
-
# Complete the match.
-
1
def complete_play?
-
1358
if match.compute_team_winner && !match.completed?
-
68
if match.match_sets.count > 1
-
# If multiple sets, then last set must be in complete state
-
52
match.last_set.completed?
-
else
-
16
true
-
end
-
end
-
end
-
-
end
-
end
-
# Validate changes to a Match
-
1
module MatchValidation
-
-
# Help validate the changes to match attributes
-
# * *Args*
-
# - +match+ -> Match
-
# * *Params*
-
# * +:errors+ - hash
-
# * Errors will be added to this hash
-
1
def self.validate(match, errors)
-
3555
Validator.new(match).validate(errors)
-
end
-
-
1
private
-
-
# Help validate
-
# the changes to match attributes
-
1
class Validator
-
-
# * *Args*
-
# - +match+ -> Match
-
1
def initialize(match)
-
3555
@match = match
-
end
-
-
# Check for various error conditions:
-
# * Both opponents may not be the same team or player
-
# * The server of the second doubles game must be valid
-
# * Must not be on the same team as the server of the first
-
# * The +:scoring+ kind must be known
-
# * The opponents must be known
-
# * The attributes must be permitted to change
-
# * Many attributes can not be changed after the match has started
-
# * opponents
-
# * doubles
-
# * scoring kind
-
# * *Params*
-
# * +:errors+ - hash
-
# * Errors will be added to this hash
-
1
def validate(errors)
-
3555
that_first_and_second_opponents_different(errors)
-
3555
that_player_servers_on_different_teams(errors)
-
3555
that_scoring_is_known(errors)
-
3555
that_can_change_after_start_play(errors)
-
3555
that_teams_or_players_are_present(errors)
-
end
-
-
1
private
-
-
1
attr_reader :match
-
-
1
def that_first_and_second_opponents_different(errors)
-
3555
if match.doubles
-
1095
that_doubles_teams_different(errors)
-
1095
that_doubles_players_different(errors)
-
else
-
2460
that_singles_players_different(errors)
-
end
-
end
-
-
1
def that_doubles_teams_different(errors)
-
1095
unless match.first_team.nil? || match.first_team != match.second_team
-
1
errors.add(:second_team, 'must not be the same as first team')
-
end
-
end
-
-
1
def that_doubles_players_different(errors)
-
1095
unless match.first_team == match.second_team || match.first_team.nil? || match.second_team.nil?
-
1086
arr = [match.first_team.first_player, match.first_team.second_player,
-
match.second_team.first_player, match.second_team.second_player]
-
1086
arr = arr.compact.map(&:id)
-
# All id's unique?
-
1086
if arr.uniq.length != arr.length
-
2
errors.add(:second_team, 'must not have players from the first team')
-
end
-
end
-
end
-
-
1
def that_singles_players_different(errors)
-
unless match.first_player.nil? ||
-
2460
match.first_player != match.second_player
-
1
errors.add(:second_player,
-
'must not be the same as first player')
-
end
-
end
-
-
1
def that_player_servers_on_different_teams(errors)
-
3555
first = match.team_of_player(match.first_player_server)
-
3555
second = match.team_of_player(match.second_player_server)
-
3555
if first && first == second
-
3
errors.add(:first_server,
-
'may not be in the same team as second server')
-
end
-
end
-
-
1
def that_scoring_is_known(errors)
-
3555
unless match.scoring.blank?
-
3551
s = [:one_eight_game, :two_six_game_ten_point, :three_six_game]
-
2
errors.add(:scoring,
-
3551
'invalid value') unless s.include? match.scoring.to_sym
-
end
-
end
-
-
1
def that_can_change_after_start_play(errors)
-
3555
if match.started
-
3314
ValidateChangeMatch.new(match).validate(errors)
-
end
-
end
-
-
1
def that_teams_or_players_are_present(errors)
-
3555
fields = if match.doubles
-
1095
[:first_team, :second_team]
-
else
-
2460
[:first_player, :second_player]
-
end
-
3555
fields.each do |sym|
-
7110
value = match.send(sym)
-
7110
errors.add sym, 'can\'t be blank' unless value
-
7110
if value && sym.to_s.end_with?('team')
-
2182
errors.add sym, 'must be doubles team' unless value.doubles
-
end
-
end
-
end
-
-
# Class to validate changes to a match after play has started.
-
# The match opponents may not be changed, for example.
-
1
class ValidateChangeMatch
-
# * *Args*
-
# - +match+ -> Match
-
1
def initialize(match)
-
3314
@match = match
-
end
-
-
# Check for invalid changes to the match. Add error messages to a hash.
-
# * *Args*
-
# - +errors+ -> hash
-
1
def validate(errors)
-
3314
find_invalid_changes do |sym|
-
10
errors.add sym, 'can\'t be changed after match has started'
-
end
-
end
-
-
1
private
-
-
1
attr_reader :errors
-
1
attr_reader :match
-
-
# Once the match has started, some attributes must not be changed.
-
1
def find_invalid_changes
-
3314
if match.started && !match.started_changed?
-
3144
doubles_var = match.doubles
-
3144
if match.first_team_id_changed?
-
2
yield doubles_var ? :first_team : :first_player
-
end
-
3144
if match.second_team_id_changed?
-
2
yield doubles_var ? :second_team : :second_player
-
end
-
3144
yield :doubles if match.doubles_changed?
-
3144
yield :scoring if match.scoring_changed?
-
3147
find_invalid_change_servers { |sym| yield sym }
-
end
-
end
-
-
1
def find_invalid_change_servers
-
3144
first_set_var = match.first_set
-
if first_set_var &&
-
(match.first_player_server_id_changed? ||
-
3144
match.second_player_server_id_changed?)
-
182
wins = first_set_var.set_games
-
.where('team_winner_id IS NOT NULL').count
-
# find_invalid_change_server_after_wins(wins) { |sym| yield sym }
-
182
if wins >= 1 && match.first_player_server_id_changed?
-
2
yield :first_player_server_id
-
end
-
182
if match.doubles && wins >= 2 && match.second_player_server_id_changed?
-
1
yield :second_player_server_id
-
end
-
end
-
end
-
-
end
-
end
-
end
-
# Model for a tennis match
-
# == Overview
-
# * A match may be a singles match or a doubles match
-
# * A match has two opponent teams:
-
# * Doubles teams with four players in total
-
# * Singles teams with two players in total
-
# * A match has a state:
-
# * +:not_started+
-
# * +:in_progress+
-
# * +:complete+
-
# * A match may have first servers
-
# First server are the players that serve
-
# the first game or two. For a singles match, there is a
-
# first server for the first game. For a doubles match, there is a first
-
# server for the first two games; one from each team.
-
# * A match has sets (see MatchSet). Sets have games (see SetGame).
-
# * A match has scoring:
-
# * +:two_six_game_ten_point+
-
# * +:one_eight_game+
-
# * +:three_six_game+
-
# * A complete match has a winner
-
#
-
# == Schema Information
-
#
-
# Table name: matches
-
#
-
# id :integer not null, primary key
-
# created_at :datetime not null
-
# updated_at :datetime not null
-
# first_team_id :integer not null
-
# second_team_id :integer not null
-
# scoring :string not null
-
# started :boolean default(FALSE), not null
-
# doubles :boolean default(FALSE), not null
-
# first_player_server_id :integer
-
# second_player_server_id :integer
-
# title :string
-
# team_winner_id :integer
-
# play_version :integer
-
#
-
1
class Match < ActiveRecord::Base
-
1
belongs_to :first_player_server,
-
class_name: 'Player', foreign_key: :first_player_server_id
-
1
belongs_to :second_player_server,
-
class_name: 'Player', foreign_key: :second_player_server_id
-
1
belongs_to :first_team, class_name: 'Team', foreign_key: :first_team_id
-
1
belongs_to :second_team, class_name: 'Team', foreign_key: :second_team_id
-
1
belongs_to :team_winner, class_name: 'Team', foreign_key: :team_winner_id
-
1
has_many :match_sets, dependent: :destroy
-
1
has_many :set_games, through: :match_sets
-
-
# Store nil instead of "". The schema does not allow duplicate titles,
-
# including duplicate ""
-
3556
before_validation { self.title = nil if self.title.blank? }
-
# If a title is not provided when match is created, generate one
-
275
before_create { self.title = next_match_title if self.title.blank? }
-
-
# Some of these rules mimic constraints in the schema
-
1
validates :scoring, presence: true # schema
-
1
validates_uniqueness_of :title, allow_nil: true # schema
-
1
validate do
-
3555
MatchValidation::validate self, errors
-
end
-
-
# Execute an action on the match, such as +:win_game+
-
#
-
# * *Args* :
-
# - +action+ -> the action to execute
-
# - +params+ -> hash of action parameters
-
# * *Raises* :
-
# - +UnknownOperation+ -> if the action is unknown
-
# - +InvalidOperation+ -> if the action is not permitted
-
#
-
# === Action
-
# * +:start_play+ - Start playing
-
# * +:discard_play+ - Discard all scoring
-
# * +:start_set+ - Start the next set
-
# * +:start_game+ - Start the next game
-
# When starting the first game in a singles match, or the first
-
# two games in a doubles match, +params+ identifies the player to
-
# serve the game.
-
# * +:start_tiebreak+ - Start game tiebreak
-
# * +:remove_last_change+ - Back up to the previous state
-
# * +:start_match_tiebreak+ - Start the match tiebreak
-
# * +:win_game+ - Win the current game
-
# * +:win_tiebreak+ - Win the current set tiebreak.
-
# * +:win_match_tiebreak+ - Win the current match tiebreak.
-
# For the 3 +:win_*+ actions, +params+ identifies the
-
# doubles team or singles team to win
-
# === Params
-
# * +:version+ Version number from client.
-
# If provided, it will be compared to the
-
# current match play version number. An exception will be
-
# raised if the values are not equal. The purpose of the version
-
# number is to detect when a client is out of sync.
-
# * +:opponent+ - Team or Player
-
# This options is used with +:win_*+ actions to identify the winning team or player. It is also
-
# used with the +:start_game+ action to identify the serving player
-
#
-
1
def play_match!(action, params = nil)
-
2927
method = play_actions.lookup_method(action)
-
2927
if method
-
2925
ActiveRecord::Base.transaction do
-
2925
version = params[:version] if params
-
2925
version = version.to_i if version
-
2925
if version && version != self.play_version
-
4
raise Exceptions::InvalidOperation,
-
"Unable to #{action.to_s.tr('_', ' ')}. " +
-
if version < self.play_version
-
2
'Client is out of sync.'
-
else
-
2
'Unexpected match version number.'
-
end
-
end
-
2921
method[:exec].call params
-
# Version number is used to detect when client has is out of sync
-
2898
self.play_version = next_version_number
-
2898
self.save!
-
end
-
else
-
2
raise Exceptions::UnknownOperation, "Unknown action: #{action}"
-
end
-
end
-
-
# Indicate if an action can be executed.
-
# +:win_game+ can be executed if a game has started, for example.
-
1
def play_match?(action)
-
3129
methods = play_actions.lookup_method(action)
-
3129
if methods
-
3128
methods[:query].call
-
else
-
1
raise Exceptions::UnknownOperation, "Unknown action: #{action}"
-
end
-
end
-
-
# Generate a hash of valid actions
-
# * *Returns* : Hash
-
# === Example
-
# {
-
# start_game: true,
-
# discard_play: true,
-
# remove_last_change: true
-
# }
-
1
def valid_actions
-
54
play_actions.valid_actions
-
end
-
-
# Indicate if the match is completed.
-
1
def completed?
-
2168
team_winner
-
end
-
-
# Get the first player opponent in a singles match
-
# * *Returns* : Player
-
1
def first_player
-
7959
singles_player_of_team first_team
-
end
-
-
# Set the first player opponent in a singles match.
-
# A new team may be created to hold the player
-
1
def first_player=(player)
-
193
self.first_team = singles_player_team(player)
-
end
-
-
# Get the second player opponent in a singles match
-
# * *Returns* : Player
-
1
def second_player
-
5399
singles_player_of_team second_team
-
end
-
-
# Set the second player opponent in a singles match.
-
# A new team may be created to hold the player
-
1
def second_player=(player)
-
192
self.second_team = singles_player_team(player)
-
end
-
-
# Get the state of the match
-
# * :complete
-
# * :in_progress
-
# * :not_started
-
1
def state
-
681
if completed?
-
43
:complete
-
638
elsif started
-
534
:in_progress
-
else
-
104
:not_started
-
end
-
end
-
-
# Calculate the number of sets won by a team
-
#
-
# * *Args* :
-
# - +team+ -> Team
-
# * *Returns* : Integer
-
1
def sets_won(team)
-
2729
match_sets.reduce(0) do |sum, set|
-
3889
winner = set.compute_team_winner
-
3889
sum + (winner && winner == team ? 1 : 0)
-
end
-
end
-
-
# Get the first set of the match
-
# * *Returns* : MatchSet
-
1
def first_set
-
3144
match_sets.first
-
end
-
-
# Get the last set of the match
-
# * *Returns* : MatchSet
-
1
def last_set
-
14561
match_sets.last
-
end
-
-
# Get the minimum number of sets that will be played in this match
-
# The minimum is 2 for +:three_six_game+ scoring, for example
-
# * *Returns* : Integer
-
1
def min_sets_to_play
-
4204
max = max_sets_to_play
-
4204
raise Exceptions::ApplicationError, 'Unexpected game count' if max.even?
-
4204
(max + 1) / 2
-
end
-
-
# Get the maximum number of sets that will be played in this match.
-
# The maximum is 3 for +:three_six_game+ scoring, for example
-
# * *Returns* : Integer
-
1
def max_sets_to_play
-
4204
case scoring.to_sym
-
when :two_six_game_ten_point
-
2998
3
-
when :one_eight_game
-
610
1
-
else # :three_six_game
-
596
3
-
end
-
end
-
-
# Get the ordinal of the next game to play in the
-
# match. The lowest ordinal is 1.
-
# * *Returns* : Integer
-
1
def next_game_ordinal
-
1393
last_set ? last_set.set_games.count + 1 : 0
-
end
-
-
# Indicate whether a set is a match tiebreak
-
# * *Args* :
-
# - +ordinal+ -> Integer
-
# * *Returns* : Boolean
-
1
def tiebreak_set?(ordinal)
-
246
scoring_of_set(ordinal) == :ten_point
-
end
-
-
# Compute the winner of the match based on
-
# the number of sets won by each opponent
-
# * *Returns* : Team or nil
-
1
def compute_team_winner
-
1393
if completed?
-
1
team_winner
-
else
-
1392
if first_team && second_team
-
1392
if sets_won(first_team) == min_sets_to_play
-
81
first_team
-
1311
elsif sets_won(second_team) == min_sets_to_play
-
21
second_team
-
end
-
end
-
end
-
end
-
-
# Determine whether a Team can win the match by
-
# winning one more game
-
# * *Args* :
-
# - +team+ -> Team
-
# * *Returns* : Boolean
-
1
def near_team_winner?(team)
-
26
unless completed?
-
26
sets_won(team) + 1 == min_sets_to_play
-
end
-
end
-
-
# Get the scoring of a set
-
# * *Args* :
-
# - +ordinal+ -> set ordinal
-
# * *Returns* : symbol
-
# * +:six_game+
-
# * +:eight_game+
-
# * +:ten_point+ (tiebreak)
-
1
def scoring_of_set(ordinal)
-
517
case scoring.to_sym
-
when :two_six_game_ten_point
-
364
ordinal == 3 ? :ten_point : :six_game
-
when :one_eight_game
-
83
:eight_game
-
else # :three_six_game
-
70
:six_game
-
end
-
end
-
-
# Get the opponent team that includes a particular player
-
# * *Args* :
-
# - +player+ -> Player
-
# * *Returns* : Team
-
1
def team_of_player(player)
-
7263
if player
-
3840
if first_team.include_player?(player)
-
3027
first_team
-
813
elsif second_team.include_player?(player)
-
813
second_team
-
end
-
end
-
end
-
-
1
private
-
-
1
def singles_player_of_team(team)
-
13358
raise Exceptions::InvalidOperation, 'singles expected' if doubles
-
13358
team.first_player if team && !team.doubles
-
end
-
-
1
def singles_player_team(player)
-
385
raise Exceptions::InvalidOperation, 'singles expected' if doubles
-
385
player.singles_team! if player # force a team
-
end
-
-
# Generate a unique title for a match
-
1
def next_match_title
-
4
"Match#{next_match_number}"
-
end
-
-
# Get a unique number to use when generating a match title
-
1
def next_match_number
-
# This returns a PGresult object
-
# [http://rubydoc.info/github/ged/ruby-pg/master/PGresult]
-
4
result = Match.connection.execute("SELECT nextval('match_number_seq')")
-
4
result[0]['nextval']
-
end
-
-
# A Postgres sequence generates a version number for a match score
-
1
def next_version_number
-
# This returns a PGresult object
-
# [http://rubydoc.info/github/ged/ruby-pg/master/PGresult]
-
2898
result = Match.connection.execute("SELECT nextval('play_version_seq')")
-
2898
result[0]['nextval']
-
end
-
-
1
def play_actions
-
6110
@play_actions ||= MatchPlay::PlayActions.new(self)
-
end
-
-
end
-
# Model for a set in a match
-
# == Overview
-
# * A set belongs to a Match
-
#
-
# * A set has a state:
-
# * +:in_progress+
-
# * +:complete+
-
#
-
# * A set has a scoring kind
-
# * +:eight_game+
-
# * +:ten_point+
-
# * +:six_game+
-
#
-
# +:ten_point+ indicate a match tiebreak set.
-
# A tiebreak set has only one game
-
#
-
# * A set has an ordinal.
-
# The first set in the match has ordinal 1
-
#
-
# * A complete set has a winning team
-
#
-
# == Schema Information
-
#
-
# Table name: match_sets
-
#
-
# id :integer not null, primary key
-
# match_id :integer not null
-
# ordinal :integer not null
-
# created_at :datetime not null
-
# updated_at :datetime not null
-
# scoring :string not null
-
# team_winner_id :integer
-
#
-
-
1
class MatchSet < ActiveRecord::Base
-
1
has_many :set_games, dependent: :destroy
-
1
belongs_to :first_player_server,
-
class_name: 'Player', foreign_key: :first_player_server_id
-
1
belongs_to :second_player_server,
-
class_name: 'Player', foreign_key: :second_player_server_id
-
1
belongs_to :team_winner,
-
class_name: 'Team', foreign_key: :team_winner_id
-
1
belongs_to :match
-
1
validates :ordinal, :scoring, :match, presence: true
-
1
validate :that_scoring_is_known
-
7386
default_scope { order('ordinal ASC') }
-
-
# Indicate whether the match has a winner
-
# * *Returns* : Boolean
-
1
def completed?
-
11254
team_winner
-
end
-
-
# Get the state of the match
-
# * *Returns* : state
-
# * +:in_progress+
-
# * +:complete+
-
1
def state
-
4485
if completed?
-
304
:complete
-
else
-
4181
:in_progress
-
end
-
end
-
-
# Compute the winner of the set, if any, based
-
# on the games won and the scoring kind
-
# * *Returns* : Team or nil
-
1
def compute_team_winner
-
6629
if completed?
-
1610
team_winner
-
else
-
5019
lookup = lookup_games_won
-
# pass games won by each team
-
5019
calc_winner_team(lookup[match.first_team_id][0],
-
lookup[match.second_team_id][0])
-
end
-
end
-
-
# Indicate whether a team can win the set if the team
-
# wins one more game
-
# * *Args* :
-
# - +team+ -> Team
-
# * *Returns* : Boolean
-
1
def near_team_winner?(team)
-
88
unless completed?
-
72
lookup = lookup_games_won
-
72
first_won = lookup[match.first_team_id][0]
-
72
second_won = lookup[match.second_team_id][0]
-
72
first_won += 1 if team.id == match.first_team_id
-
72
second_won += 1 if team.id == match.second_team_id
-
72
calc_winner_team(first_won, second_won)
-
end
-
end
-
-
# Get the last game of the set
-
# * *Returns* : Game
-
1
def last_game
-
2971
set_games.last
-
end
-
-
# Get the minimum number of games that an opponent
-
# must win in order to win the set, based on the scoring
-
# kind.
-
# * *Returns* : count
-
1
def win_threshold
-
17588
case scoring.to_sym
-
when :eight_game
-
2501
8
-
when :ten_point
-
124
1
-
else # :six_game
-
14963
6
-
end
-
end
-
-
# Indicate if this set is a match tiebreak
-
# * *Returns* : Boolean
-
1
def tiebreak?
-
3238
scoring.to_sym == :ten_point
-
end
-
-
# Indicate whether a particular game is a tiebreak.
-
# A game may be a tiebreak because the set is tied
-
# (e.g. 6-6), or because the set is a match tiebreak.
-
# * *Args* :
-
# - +game_ordinal+ -> ordinal
-
# * *Returns* : Boolean
-
1
def tiebreak_game?(game_ordinal)
-
3762
tiebreak_ordinal = (win_threshold == 1) ? 1 : win_threshold * 2 + 1
-
3762
game_ordinal == tiebreak_ordinal
-
end
-
-
# Clear cached scores. For internal use
-
1
def score_changed
-
4118
@games_won_by_team = nil
-
end
-
-
1
private
-
-
1
def that_scoring_is_known
-
706
unless scoring.blank?
-
705
s = [:six_game, :eight_game, :ten_point]
-
705
errors.add(:scoring, 'invalid value') unless s.include? scoring.to_sym
-
end
-
end
-
-
1
def calc_winner_team(first_team_wins, second_team_wins)
-
5091
won = if win_threshold >= 2
-
4967
calc_winner_by_games(first_team_wins, second_team_wins)
-
else
-
124
[first_team_wins, second_team_wins].max == 1
-
end
-
5091
if won
-
503
first_team_wins > second_team_wins ? match.first_team : match.second_team
-
end
-
end
-
-
1
def calc_winner_by_games(first_team_wins, second_team_wins)
-
4967
threshold = win_threshold
-
4967
minmax = [first_team_wins, second_team_wins].minmax
-
4967
win_by_two = (first_team_wins - second_team_wins).abs >= 2 &&
-
minmax[1] >= threshold
-
4967
win_tiebreak = minmax == [threshold, threshold + 1]
-
4967
win_by_two || win_tiebreak
-
end
-
-
1
def lookup_games_won
-
5091
if @games_won_by_team
-
# cache for slight performance improvement
-
3557
return @games_won_by_team
-
end
-
1534
lookup = {}
-
1534
sum_wins do |first_wins, second_wins|
-
1534
lookup = { match.first_team_id => first_wins,
-
match.second_team_id => second_wins }
-
end
-
1534
@games_won_by_team = lookup
-
1534
lookup
-
end
-
-
1
def sum_wins
-
1534
first_wins = [0]
-
1534
second_wins = [0]
-
1534
sum_set_games do |sum1, sum2|
-
7373
first_wins << sum1
-
7373
second_wins << sum2
-
end
-
1534
first_wins[0] = first_wins.last
-
1534
second_wins[0] = second_wins.last
-
1534
yield first_wins, second_wins
-
end
-
-
1
def sum_set_games
-
1534
sum1 = sum2 = 0
-
1534
set_games.each do |game|
-
7393
break unless game.team_winner
-
7373
if game.team_winner == match.first_team
-
4005
sum1 += 1
-
else
-
3368
sum2 += 1
-
end
-
7373
yield sum1, sum2
-
end
-
end
-
end
-
# Model for a player
-
# == Overview
-
# * A player may be on a team
-
# * A player may be the first or second server in a match
-
# * A player may be the server of a game
-
#
-
# == Schema Information
-
#
-
# Table name: players
-
#
-
# id :integer not null, primary key
-
# name :string not null
-
# created_at :datetime not null
-
# updated_at :datetime not null
-
#
-
1
class Player < ActiveRecord::Base
-
1
validates :name, presence: true
-
1
validates_uniqueness_of :name
-
1
before_destroy :that_can_destroy
-
-
# Find the singles team associated with this player
-
# * *Returns* : Team or nil
-
1
def singles_team
-
420
Team.find_by_doubles_and_first_player_id(false, self.id)
-
end
-
-
# Force a player to have an associated singles team. Creates a new
-
# Team if needed.
-
# * *Returns* : Team
-
1
def singles_team!
-
405
team = singles_team
-
405
unless team
-
398
team = Team.new
-
398
team.doubles = false
-
398
team.first_player = self
-
398
team.save!
-
end
-
405
team
-
end
-
-
1
private
-
-
1
def that_can_destroy
-
9
clean_singles_team
-
9
if on_any_team?
-
6
errors.add :errors, 'Can\'t delete a player in a match or on a team'
-
6
false
-
end
-
end
-
-
1
def on_any_team?
-
9
Team.where('first_player_id=? OR second_player_id=?', id, id).exists?
-
end
-
-
1
def clean_singles_team
-
9
team = singles_team
-
9
if team
-
unless Match.where('first_team_id=? OR second_team_id=?',
-
2
team.id, team.id).exists?
-
1
team.destroy!
-
end
-
end
-
end
-
end
-
# Model for a game
-
#
-
# == Overview
-
#
-
# * A game belongs to a MatchSet
-
# * A game has a state:
-
# * +:in_progress+
-
# * +:finished+
-
# * A game has an ordinal. The first game in the set has ordinal 1
-
# * A game may have a winning team
-
# * A game may be a tiebreak
-
# A tiebreak is a special kind of game that occurs at the end of a set
-
# * A non-tiebreak game has a serving player
-
#
-
# == Schema Information
-
#
-
# Table name: set_games
-
#
-
# id :integer not null, primary key
-
# ordinal :integer not null
-
# match_set_id :integer not null
-
# team_winner_id :integer
-
# created_at :datetime not null
-
# updated_at :datetime not null
-
# player_server_id :integer
-
# tiebreaker :boolean default(FALSE), not null
-
#
-
1
class SetGame < ActiveRecord::Base
-
1
belongs_to :match_set
-
1
belongs_to :team_winner, class_name: 'Team', foreign_key: :team_winner_id
-
1
belongs_to :player_server,
-
class_name: 'Player', foreign_key: :player_server_id
-
1
validates :ordinal, :match_set, presence: true
-
11334
default_scope { order('ordinal ASC') }
-
4110
after_save { match_set.score_changed }
-
10
after_destroy { match_set.score_changed }
-
-
# Get the state of a game
-
# * *Returns* : symbol
-
# * +:in_progress+ or
-
# * +:finished+
-
1
def state
-
6485
team_winner_id ? :finished : :in_progress
-
end
-
-
# Indicate if the game is a tiebreak
-
# * *Returns* : Boolean
-
1
def tiebreak?
-
14753
tiebreaker
-
end
-
end
-
# Model for a team
-
#
-
# == Overview
-
#
-
# * A team may be an opponent in a match
-
# * A team may be the winner of a game, a match or a set
-
# * A doubles team has two players
-
# * A singles team has one player
-
# Singles teams are created as needed to allow a
-
# player to be an opponent in a match.
-
#
-
# == Schema Information
-
#
-
# Table name: teams
-
#
-
# id :integer not null, primary key
-
# name :string
-
# first_player_id :integer not null
-
# second_player_id :integer
-
# created_at :datetime not null
-
# updated_at :datetime not null
-
# doubles :boolean default(FALSE), not null
-
#
-
1
class Team < ActiveRecord::Base
-
1
belongs_to :first_player,
-
class_name: 'Player', foreign_key: :first_player_id
-
1
belongs_to :second_player,
-
class_name: 'Player', foreign_key: :second_player_id
-
745
before_validation { self.name = nil if self.name.blank? }
-
# redundant
-
# validates :first_player_id, presence: true
-
1
validates_uniqueness_of :name, allow_nil: true
-
1
validate :that_is_valid_first_player
-
1
validate :that_is_valid_second_player
-
1
validate :that_is_unique_player_pair
-
1
validate :that_can_change_player
-
1
before_destroy :that_can_destroy_team
-
-
# If a name is not provided when match is created, generate name
-
719
before_create { self.name = next_team_name if self.doubles && self.name.blank? }
-
-
# Indicate whether the team includes all players in a list
-
# * *Args* :
-
# - +players+ -> array of players
-
# * *Returns* : Boolean
-
1
def include_players?(players)
-
4653
players == [first_player, second_player] & players.compact
-
end
-
-
# Indicate whether the team includes a particular player
-
# * *Args* :
-
# - +player+ -> Player
-
# * *Returns* : Boolean
-
1
def include_player?(player)
-
4653
include_players?([player])
-
end
-
-
1
private
-
-
1
def that_can_destroy_team
-
6
if team_in_match?
-
3
errors.add :errors, 'Can\'t delete a team in a match'
-
3
false # discard destroy
-
end
-
end
-
-
1
def that_can_change_player
-
744
first_changed = first_player_id_changed?
-
744
second_changed = second_player_id_changed?
-
744
if (first_changed || second_changed) and team_playing_match?
-
2
message = 'can\'t be changed when in a match that has started'
-
2
errors.add :first_player, message if first_changed
-
2
errors.add :second_player, message if second_changed
-
end
-
end
-
-
1
def team_playing_match?
-
738
matches_of_team.where('started').exists?
-
end
-
-
1
def team_in_match?
-
6
matches_of_team.exists?
-
end
-
-
1
def matches_of_team
-
744
Match.where('first_team_id=? OR second_team_id=?', id, id)
-
end
-
-
1
def that_is_valid_first_player
-
744
if first_player.nil?
-
6
errors.add(:first_player, if first_player_id.blank?
-
3
'can\'t be blank'
-
else
-
3
'not found'
-
end)
-
end
-
end
-
-
1
def that_is_valid_second_player
-
744
if doubles
-
346
if second_player.nil?
-
6
errors.add(:second_player, if second_player_id.blank?
-
3
'must be specified'
-
else
-
3
'not found'
-
end)
-
else
-
340
errors.add(:second_player, 'must not be the same as first player') if second_player == first_player
-
end
-
else
-
# not doubles
-
398
errors.add(:second_player, 'not allowed') if second_player
-
end
-
end
-
-
1
def that_is_unique_player_pair
-
744
if doubles && first_player_id && second_player_id
-
340
player_ids = [first_player_id, second_player_id]
-
340
existing_team = Team.where(first_player_id: player_ids)
-
.where(second_player_id: player_ids).first
-
340
unless existing_team.nil? || existing_team.id == self.id
-
3
errors.add(:error, 'A team with these players already exists')
-
end
-
end
-
end
-
-
# Generate a unique title for a team
-
1
def next_team_name
-
2
"Team#{next_team_number}"
-
end
-
-
# Get a unique number to use when generating a team title
-
1
def next_team_number
-
# This returns a PGresult object
-
# [http://rubydoc.info/github/ged/ruby-pg/master/PGresult]
-
2
result = Team.connection.execute("SELECT nextval('team_number_seq')")
-
2
result[0]['nextval']
-
end
-
end
-
# Model for a user
-
#
-
# == Overview
-
#
-
# A user has a name, password and authentication token
-
#
-
# == Schema Information
-
#
-
# Table name: users
-
#
-
# id :integer not null, primary key
-
# username :string default(""), not null
-
# encrypted_password :string default(""), not null
-
# reset_password_token :string
-
# reset_password_sent_at :datetime
-
# remember_created_at :datetime
-
# sign_in_count :integer default(0), not null
-
# current_sign_in_at :datetime
-
# last_sign_in_at :datetime
-
# current_sign_in_ip :string
-
# last_sign_in_ip :string
-
# created_at :datetime
-
# updated_at :datetime
-
# auth_token :string default("")
-
#
-
1
class User < ActiveRecord::Base
-
1
validates :auth_token, uniqueness: true
-
1
validates :username, uniqueness: true
-
1
validates :username, presence: true
-
-
1
devise :database_authenticatable
-
-
1
before_create :generate_authentication_token!
-
-
# Set auth_token attribute to a new value
-
1
def generate_authentication_token!
-
begin
-
132
self.auth_token = Devise.friendly_token
-
132
end while self.class.exists?(auth_token: auth_token)
-
end
-
-
end
-
# Base class for this application's serializers.
-
# Disable JSON root object
-
1
class V1::ApplicationArraySerializer < ActiveModel::ArraySerializer
-
1
self.root = false
-
-
end
-
# Base class for this application's serializers.
-
# Disable JSON root object
-
1
class V1::ApplicationSerializer < ActiveModel::Serializer
-
1
self.root = false
-
-
end
-
# Serialize a Match score with the following information:
-
# * Attributes of the match (See V1::MatchSerializer)
-
# * +:id+
-
# * +:title+
-
# * +:scoring+
-
# * +:doubles+
-
# * +:state+
-
# * +:winner+
-
# * Attributes related to keeping score
-
# * +:sets+ - The sets in the match. See V1::MatchSetSerializer.
-
# * +:actions+ - The actions that may be executed on the match
-
# * +:errors+ - Errors that occurred when executing an action
-
# * +:server+ - The Player id's that may serve the next game
-
# * +:near_winner+
-
# * The Team or Player id's that are near to winning a set
-
# * The Team or Player id's that are near to winning the match
-
#
-
1
class V1::MatchScoreboardSerializer < V1::MatchSerializer
-
1
attributes :id, :title, :scoring, :doubles, :state, :winner, :sets, :actions, :errors, :servers,
-
:near_winners
-
-
# Serialize the sets of the match. See
-
# V1::MatchSetSerializer
-
#
-
# * *Returns* :
-
# - array of serialized sets
-
#
-
1
def sets
-
53
V1::ApplicationArraySerializer.new(object.match_sets, each_serializer: V1::MatchSetSerializer)
-
end
-
-
# Serialize actions that may be executed on the match.
-
# See Match.valid_actions
-
# * *Returns* : Hash
-
# === Example
-
# {
-
# start_game: true,
-
# discard_play: true,
-
# remove_last_change: true
-
# }
-
#
-
1
def actions
-
53
object.valid_actions
-
end
-
-
# Serialize the id's of teams or players that are near to winning a set
-
# or the match. Player id's are serialized for singles matches;
-
# Team id's for doubles matches.
-
#
-
# * *Returns* : Hash
-
# === Example
-
# (
-
# set: [10],
-
# match[]
-
# }
-
#
-
1
def near_winners
-
53
result = {
-
set: [],
-
match: []
-
}
-
53
first = object.first_team
-
53
second = object.second_team
-
53
last_set_var = object.last_set
-
53
if first && second && last_set_var
-
38
add_near_winner first, last_set_var, result
-
38
add_near_winner second, last_set_var, result
-
end
-
-
53
result
-
end
-
-
-
# Serialize a list of player id's that may serve the first or second game.
-
# (One of these players must be passed as a parameter when executing the
-
# +:start_game+ action)
-
#
-
# * *Returns* :
-
# - an array of player ids
-
#
-
# * Example list before starting a doubles match:
-
#
-
# \[10, 20, 30, 40\]
-
#
-
# * Example list before starting the second game of a doubles match:
-
#
-
# \[30, 40\]
-
#
-
# * Example list before starting the first game of a singles match:
-
#
-
# \[10, 20\]
-
#
-
# * Value for all other cases:
-
#
-
# \[\]
-
#
-
1
def servers
-
53
result = []
-
53
result.push(object.first_player_server.id) if object.first_player_server
-
53
result.push(object.second_player_server.id) if object.second_player_server
-
53
result
-
end
-
-
1
private
-
-
1
def add_near_winner(team, set, result)
-
76
if set.near_team_winner? team
-
16
id = opponent_id team
-
16
result[:set].push id
-
16
if object.near_team_winner? team
-
16
result[:match].push id
-
end
-
end
-
end
-
end
-
# Serialize a Match with the following attributes:
-
# * +:id+
-
# * +:title+
-
# * +:scoring+ kind
-
# * +:doubles+
-
# * +:state+
-
# * +:winner+
-
# * +:first_team+ (when doubles)
-
# * +:second_team+ (when doubles)
-
# * +:first_player+ (when not doubles)
-
# * +:second_player+ (when not doubles)
-
#
-
1
class V1::MatchSerializer < V1::ApplicationSerializer
-
1
attributes :id, :title, :scoring, :doubles, :state, :winner
-
-
# Serialize the player or team id of the match winner,
-
# if any.
-
#
-
# * *Returns* : id
-
#
-
1
def winner
-
148
if object.team_winner.nil?
-
nil
-
else
-
22
opponent_id object.team_winner
-
end
-
end
-
-
# Indicate dynamic attributes to the serializer. For a doubles match,
-
# serialize first and second players. For a singles match, serialize the first
-
# and second teams.
-
#
-
# * *Returns* : Hash of attributes
-
#
-
1
def attributes
-
95
data = super
-
95
if object.doubles
-
48
data[:first_team] = first_team
-
48
data[:second_team] = second_team
-
else
-
47
data[:first_player] = first_player
-
47
data[:second_player] = second_player
-
end
-
95
data
-
end
-
-
# Serialize the first player
-
#
-
# * *Returns* : V1::PlayerSerializer
-
#
-
1
def first_player
-
47
V1::PlayerSerializer.new(object.first_team.first_player)
-
end
-
-
# Serialize the first team
-
#
-
# * *Returns* : V1::TeamSerializer
-
#
-
1
def first_team
-
48
V1::TeamSerializer.new(object.first_team)
-
end
-
-
# Serialize the second player
-
#
-
# * *Returns* : See V1::PlayerSerializer
-
#
-
1
def second_player
-
47
V1::PlayerSerializer.new(object.second_team.first_player)
-
end
-
-
# Serialize the second team. See V1::OpponentTeamSerializer.
-
#
-
# * *Returns* : V1::TeamSerializer
-
#
-
1
def second_team
-
48
V1::TeamSerializer.new(object.second_team)
-
end
-
-
1
protected
-
-
# Get the id for an opponent in a match.
-
# For a doubles match, get a team id. For a singles match,
-
# get a player id
-
# * *Args* :
-
# - +team+ -> opponent Team
-
# * *Returns* : id
-
1
def opponent_id(team)
-
38
if object.doubles
-
19
team.id
-
else
-
19
team.first_player.id
-
end
-
end
-
-
end
-
# Serialize a MatchSet with the following attributes:
-
# * +:scoring+
-
# * +:state+
-
# * +:winner+
-
# * +:games+
-
#
-
1
class V1::MatchSetSerializer < V1::ApplicationSerializer
-
1
attributes :scoring, :winner, :games, :state
-
-
# Serialize the games of the set.
-
#
-
# * *Returns* :
-
# - array of V1::SetGameSerializer
-
#
-
1
def games
-
70
V1::ApplicationArraySerializer.new(object.set_games, each_serializer: V1::SetGameSerializer)
-
end
-
-
# Serialize the Player or Team id of the winner
-
#
-
# * *Returns* : Player id or Team id or nil
-
#
-
1
def winner
-
70
if object.team_winner.nil?
-
nil
-
else
-
40
if object.match.doubles
-
20
object.team_winner.id
-
else
-
20
object.team_winner.first_player.id
-
end
-
end
-
end
-
-
end
-
# Serializes a Player with the following attributes:
-
# * +:id+
-
# * +:name+
-
#
-
1
class V1::PlayerSerializer < V1::ApplicationSerializer
-
1
attributes :id, :name
-
-
end
-
# Serializes a logged in User. The following attributes
-
# are serialized:
-
# * +:id+
-
# * +:username+
-
# * +:auth_token+
-
#
-
1
class V1::SessionSerializer < V1::ApplicationSerializer
-
1
attributes :id, :username, :auth_token
-
end
-
# Serializes a SetGame with the following attributes:
-
# * +:winner+ - Id of a Team or a Player
-
# * +:server+ - Id of a Player
-
# * +:tiebreak+ - true if this game is a tiebreak
-
#
-
1
class V1::SetGameSerializer < V1::ApplicationSerializer
-
1
attributes :winner, :server, :tiebreak
-
-
# Serialize the Player or Team id of the game winner
-
#
-
# * *Returns* : Team id or Player id or nil
-
#
-
1
def winner
-
400
if object.team_winner.nil?
-
nil
-
else
-
384
if object.match_set.match.doubles
-
192
object.team_winner.id
-
else
-
192
object.team_winner.first_player.id
-
end
-
end
-
end
-
-
# Indicate a tiebreak game
-
#
-
# * *Returns* : true or nil
-
#
-
1
def tiebreak
-
400
object.tiebreak? ? true : nil
-
end
-
-
# Serialize the Player id of the game server.
-
#
-
# * *Returns* : Player id or nil
-
#
-
1
def server
-
400
object.player_server_id
-
end
-
-
end
-
# Serializes a Team with the following attributes:
-
# * +:id+
-
# * +:first_player+
-
# * +:second_player+
-
#
-
1
class V1::TeamSerializer < V1::ApplicationSerializer
-
1
attributes :id, :name, :first_player, :second_player
-
-
# Serialize the first player on the team
-
# * *Returns* : V1::PlayerSerializer
-
1
def first_player
-
118
V1::PlayerSerializer.new(object.first_player)
-
end
-
-
# Serialize the second player on the team
-
# * *Returns* : V1::PlayerSerializer
-
1
def second_player
-
118
V1::PlayerSerializer.new(object.second_player)
-
end
-
-
end
-
# Serializes the current User with the following attributes:
-
# * +:id+
-
# * +:username+
-
#
-
1
class V1::UserSerializer < V1::ApplicationSerializer
-
1
attributes :id, :username
-
-
end
-
1
require File.expand_path('../boot', __FILE__)
-
-
1
require 'rails/all'
-
-
# Require the gems listed in Gemfile, including any gems
-
# you've limited to :test, :development, or :production.
-
1
Bundler.require(*Rails.groups)
-
-
1
module TennisNg
-
1
class Application < Rails::Application
-
# Settings in config/environments/* take precedence over those specified here.
-
# Application configuration should go into files in config/initializers
-
# -- all .rb files in that directory are automatically loaded.
-
-
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
-
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
-
# config.time_zone = 'Central Time (US & Canada)'
-
-
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
-
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
-
# config.i18n.default_locale = :de
-
-
# Do not swallow errors in after_commit/after_rollback callbacks.
-
1
config.active_record.raise_in_transactional_callbacks = true
-
end
-
end
-
1
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
-
-
1
require 'bundler/setup' # Set up gems listed in the Gemfile.
-
# Load the Rails application.
-
1
require File.expand_path('../application', __FILE__)
-
-
# Initialize the Rails application.
-
1
Rails.application.initialize!
-
1
Rails.application.configure do
-
# Settings specified here will take precedence over those in config/application.rb.
-
-
# The test environment is used exclusively to run your application's
-
# test suite. You never need to work with it otherwise. Remember that
-
# your test database is "scratch space" for the test suite and is wiped
-
# and recreated between test runs. Don't rely on the data there!
-
1
config.cache_classes = true
-
-
# Do not eager load code on boot. This avoids loading your whole application
-
# just for the purpose of running a single test. If you are using a tool that
-
# preloads Rails for running tests, you may have to set it to true.
-
1
config.eager_load = false
-
-
# Configure static file server for tests with Cache-Control for performance.
-
1
config.serve_static_files = true
-
1
config.static_cache_control = 'public, max-age=3600'
-
-
# Show full error reports and disable caching.
-
1
config.consider_all_requests_local = true
-
1
config.action_controller.perform_caching = false
-
-
# Raise exceptions instead of rendering exception templates.
-
1
config.action_dispatch.show_exceptions = false
-
-
# Disable request forgery protection in test environment.
-
1
config.action_controller.allow_forgery_protection = false
-
-
# Tell Action Mailer not to deliver emails to the real world.
-
# The :test delivery method accumulates sent emails in the
-
# ActionMailer::Base.deliveries array.
-
1
config.action_mailer.delivery_method = :test
-
-
# Randomize the order test cases are executed.
-
1
config.active_support.test_order = :random
-
-
# Print deprecation notices to the stderr.
-
1
config.active_support.deprecation = :stderr
-
-
# Raises error for missing translations
-
# config.action_view.raise_on_missing_translations = true
-
end
-
# Use this hook to configure devise mailer, warden hooks and so forth.
-
# Many of these configuration options can be set straight in your model.
-
1
Devise.setup do |config|
-
# The secret key used by Devise. Devise uses this key to generate
-
# random tokens. Changing this key will render invalid all existing
-
# confirmation, reset password and unlock tokens in the database.
-
# config.secret_key = '12c3a022d553cbe4b214d7fb246a9ae6e8b16d6a3b0faaea71a9da8176e4200e762423e50f9d7ac51797f39b91d4fe71d513bfadbae777aefaa95b655b3aa491'
-
-
# ==> Mailer Configuration
-
# Configure the e-mail address which will be shown in Devise::Mailer,
-
# note that it will be overwritten if you use your own mailer class
-
# with default "from" parameter.
-
1
config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com'
-
-
# Configure the class responsible to send e-mails.
-
# config.mailer = 'Devise::Mailer'
-
-
# ==> ORM configuration
-
# Load and configure the ORM. Supports :active_record (default) and
-
# :mongoid (bson_ext recommended) by default. Other ORMs may be
-
# available as additional gems.
-
1
require 'devise/orm/active_record'
-
-
# ==> Configuration for any authentication mechanism
-
# Configure which keys are used when authenticating a user. The default is
-
# just :email. You can configure it to use [:username, :subdomain], so for
-
# authenticating a user, both parameters are required. Remember that those
-
# parameters are used only when authenticating and not when retrieving from
-
# session. If you need permissions, you should implement that in a before filter.
-
# You can also supply a hash where the value is a boolean determining whether
-
# or not authentication should be aborted when the value is not present.
-
# config.authentication_keys = [ :email ]
-
1
config.authentication_keys = [:username]
-
-
# Configure parameters from the request object used for authentication. Each entry
-
# given should be a request method and it will automatically be passed to the
-
# find_for_authentication method and considered in your model lookup. For instance,
-
# if you set :request_keys to [:subdomain], :subdomain will be used on authentication.
-
# The same considerations mentioned for authentication_keys also apply to request_keys.
-
# config.request_keys = []
-
-
# Configure which authentication keys should be case-insensitive.
-
# These keys will be downcased upon creating or modifying a user and when used
-
# to authenticate or find a user. Default is :email.
-
# config.case_insensitive_keys = [ :email ]
-
1
config.case_insensitive_keys = [ :username ]
-
-
# Configure which authentication keys should have whitespace stripped.
-
# These keys will have whitespace before and after removed upon creating or
-
# modifying a user and when used to authenticate or find a user. Default is :email.
-
# config.strip_whitespace_keys = [ :email ]
-
1
config.strip_whitespace_keys = [ :username ]
-
-
# Tell if authentication through request.params is enabled. True by default.
-
# It can be set to an array that will enable params authentication only for the
-
# given strategies, for example, `config.params_authenticatable = [:database]` will
-
# enable it only for database (email + password) authentication.
-
# config.params_authenticatable = true
-
-
# Tell if authentication through HTTP Auth is enabled. False by default.
-
# It can be set to an array that will enable http authentication only for the
-
# given strategies, for example, `config.http_authenticatable = [:database]` will
-
# enable it only for database authentication. The supported strategies are:
-
# :database = Support basic authentication with authentication key + password
-
# config.http_authenticatable = false
-
-
# If http headers should be returned for AJAX requests. True by default.
-
# config.http_authenticatable_on_xhr = true
-
-
# The realm used in Http Basic Authentication. 'Application' by default.
-
# config.http_authentication_realm = 'Application'
-
-
# It will change confirmation, password recovery and other workflows
-
# to behave the same regardless if the e-mail provided was right or wrong.
-
# Does not affect registerable.
-
# config.paranoid = true
-
-
# By default Devise will store the user in session. You can skip storage for
-
# particular strategies by setting this option.
-
# Notice that if you are skipping storage for all authentication paths, you
-
# may want to disable generating routes to Devise's sessions controller by
-
# passing skip: :sessions to `devise_for` in your config/routes.rb
-
1
config.skip_session_storage = [:http_auth]
-
-
# By default, Devise cleans up the CSRF token on authentication to
-
# avoid CSRF token fixation attacks. This means that, when using AJAX
-
# requests for sign in and sign up, you need to get a new CSRF token
-
# from the server. You can disable this option at your own risk.
-
# config.clean_up_csrf_token_on_authentication = true
-
-
# ==> Configuration for :database_authenticatable
-
# For bcrypt, this is the cost for hashing the password and defaults to 10. If
-
# using other encryptors, it sets how many times you want the password re-encrypted.
-
#
-
# Limiting the stretches to just one in testing will increase the performance of
-
# your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use
-
# a value less than 10 in other environments. Note that, for bcrypt (the default
-
# encryptor), the cost increases exponentially with the number of stretches (e.g.
-
# a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation).
-
1
config.stretches = Rails.env.test? ? 1 : 10
-
-
# Setup a pepper to generate the encrypted password.
-
# config.pepper = '33c14b410d29eb88ee285a40d8e4ffa962c7155cbf46644825771a11207180d76dffa79671bea55138ed6f00414d51f2f590838fe38b1a4849ac93bf117fac20'
-
-
# ==> Configuration for :confirmable
-
# A period that the user is allowed to access the website even without
-
# confirming their account. For instance, if set to 2.days, the user will be
-
# able to access the website for two days without confirming their account,
-
# access will be blocked just in the third day. Default is 0.days, meaning
-
# the user cannot access the website without confirming their account.
-
# config.allow_unconfirmed_access_for = 2.days
-
-
# A period that the user is allowed to confirm their account before their
-
# token becomes invalid. For example, if set to 3.days, the user can confirm
-
# their account within 3 days after the mail was sent, but on the fourth day
-
# their account can't be confirmed with the token any more.
-
# Default is nil, meaning there is no restriction on how long a user can take
-
# before confirming their account.
-
# config.confirm_within = 3.days
-
-
# If true, requires any email changes to be confirmed (exactly the same way as
-
# initial account confirmation) to be applied. Requires additional unconfirmed_email
-
# db field (see migrations). Until confirmed, new email is stored in
-
# unconfirmed_email column, and copied to email column on successful confirmation.
-
1
config.reconfirmable = true
-
-
# Defines which key will be used when confirming an account
-
# config.confirmation_keys = [ :email ]
-
-
# ==> Configuration for :rememberable
-
# The time the user will be remembered without asking for credentials again.
-
# config.remember_for = 2.weeks
-
-
# If true, extends the user's remember period when remembered via cookie.
-
# config.extend_remember_period = false
-
-
# Options to be passed to the created cookie. For instance, you can set
-
# secure: true in order to force SSL only cookies.
-
# config.rememberable_options = {}
-
-
# ==> Configuration for :validatable
-
# Range for password length.
-
1
config.password_length = 8..128
-
-
# Email regex used to validate email formats. It simply asserts that
-
# one (and only one) @ exists in the given string. This is mainly
-
# to give user feedback and not to assert the e-mail validity.
-
# config.email_regexp = /\A[^@]+@[^@]+\z/
-
-
# ==> Configuration for :timeoutable
-
# The time you want to timeout the user session without activity. After this
-
# time the user will be asked for credentials again. Default is 30 minutes.
-
# config.timeout_in = 30.minutes
-
-
# If true, expires auth token on session timeout.
-
# config.expire_auth_token_on_timeout = false
-
-
# ==> Configuration for :lockable
-
# Defines which strategy will be used to lock an account.
-
# :failed_attempts = Locks an account after a number of failed attempts to sign in.
-
# :none = No lock strategy. You should handle locking by yourself.
-
# config.lock_strategy = :failed_attempts
-
-
# Defines which key will be used when locking and unlocking an account
-
# config.unlock_keys = [ :email ]
-
-
# Defines which strategy will be used to unlock an account.
-
# :email = Sends an unlock link to the user email
-
# :time = Re-enables login after a certain amount of time (see :unlock_in below)
-
# :both = Enables both strategies
-
# :none = No unlock strategy. You should handle unlocking by yourself.
-
# config.unlock_strategy = :both
-
-
# Number of authentication tries before locking an account if lock_strategy
-
# is failed attempts.
-
# config.maximum_attempts = 20
-
-
# Time interval to unlock the account if :time is enabled as unlock_strategy.
-
# config.unlock_in = 1.hour
-
-
# Warn on the last attempt before the account is locked.
-
# config.last_attempt_warning = false
-
-
# ==> Configuration for :recoverable
-
#
-
# Defines which key will be used when recovering the password for an account
-
# config.reset_password_keys = [ :email ]
-
-
# Time interval you can reset your password with a reset password key.
-
# Don't put a too small interval or your users won't have the time to
-
# change their passwords.
-
1
config.reset_password_within = 6.hours
-
-
# ==> Configuration for :encryptable
-
# Allow you to use another encryption algorithm besides bcrypt (default). You can use
-
# :sha1, :sha512 or encryptors from others authentication tools as :clearance_sha1,
-
# :authlogic_sha512 (then you should set stretches above to 20 for default behavior)
-
# and :restful_authentication_sha1 (then you should set stretches to 10, and copy
-
# REST_AUTH_SITE_KEY to pepper).
-
#
-
# Require the `devise-encryptable` gem when using anything other than bcrypt
-
# config.encryptor = :sha512
-
-
# ==> Scopes configuration
-
# Turn scoped views on. Before rendering "sessions/new", it will first check for
-
# "users/sessions/new". It's turned off by default because it's slower if you
-
# are using only default views.
-
# config.scoped_views = false
-
-
# Configure the default scope given to Warden. By default it's the first
-
# devise role declared in your routes (usually :user).
-
# config.default_scope = :user
-
-
# Set this configuration to false if you want /users/sign_out to sign out
-
# only the current scope. By default, Devise signs out all scopes.
-
# config.sign_out_all_scopes = true
-
-
# ==> Navigation configuration
-
# Lists the formats that should be treated as navigational. Formats like
-
# :html, should redirect to the sign in page when the user does not have
-
# access, but formats like :xml or :json, should return 401.
-
#
-
# If you have any extra navigational formats, like :iphone or :mobile, you
-
# should add them to the navigational formats lists.
-
#
-
# The "*/*" below is required to match Internet Explorer requests.
-
# config.navigational_formats = ['*/*', :html]
-
-
# The default HTTP method used to sign out a resource. Default is :delete.
-
1
config.sign_out_via = :delete
-
-
# ==> OmniAuth
-
# Add a new OmniAuth provider. Check the wiki for more information on setting
-
# up on your models and hooks.
-
# config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo'
-
-
# ==> Warden configuration
-
# If you want to use other strategies, that are not supported by Devise, or
-
# change the failure app, you can configure them inside the config.warden block.
-
#
-
# config.warden do |manager|
-
# manager.intercept_401 = false
-
# manager.default_strategies(scope: :user).unshift :some_external_strategy
-
# end
-
-
# ==> Mountable engine configurations
-
# When using Devise inside an engine, let's call it `MyEngine`, and this engine
-
# is mountable, there are some extra configurations to be taken into account.
-
# The following options are available, assuming the engine is mounted as:
-
#
-
# mount MyEngine, at: '/my_engine'
-
#
-
# The router that invoked `devise_for`, in the example above, would be:
-
# config.router_name = :my_engine
-
#
-
# When using omniauth, Devise cannot automatically set Omniauth path,
-
# so you need to do it manually. For the users scope, it would be:
-
# config.omniauth_path_prefix = '/my_engine/users/auth'
-
end
-
# Be sure to restart your server when you modify this file.
-
-
# Configure sensitive parameters which will be filtered from the log file.
-
1
Rails.application.config.filter_parameters += [:password]
-
# Be sure to restart your server when you modify this file.
-
-
# Your secret key for verifying the integrity of signed cookies.
-
# If you change this key, all old signed cookies will become invalid!
-
-
# Make sure the secret is at least 30 characters and all random,
-
# no regular words or you'll be exposed to dictionary attacks.
-
# You can use `rake secret` to generate a secure secret key.
-
-
# Make sure your secret_key_base is kept private
-
# if you're sharing your code publicly.
-
-
# Although this is not needed for an api-only application, rails4
-
# requires secret_key_base or secret_token to be defined, otherwise an
-
# error is raised.
-
# Using secret_token for rails3 compatibility. Change to secret_key_base
-
# to avoid deprecation warning.
-
# Can be safely removed in a rails3 api-only application.
-
1
TennisNg::Application.config.secret_token = 'e84578a39b9aef2b2aac44a0bb1217b32094482f49d68c883cacccf6bf7c7b053ca13713addf57e1e8971dca1898f1907366498aa60d41cbd21c94a013dcc01b'
-
# Be sure to restart your server when you modify this file.
-
#
-
# This file contains settings for ActionController::ParamsWrapper
-
-
# Enable parameter wrapping for JSON.
-
# ActiveSupport.on_load(:action_controller) do
-
# wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
-
# end
-
-
# To enable root element in JSON for ActiveRecord objects.
-
# ActiveSupport.on_load(:active_record) do
-
# self.include_root_in_json = true
-
# end
-
1
require 'api_constraints'
-
-
1
Rails.application.routes.draw do
-
-
1
scope '/api', constraints: { format: 'json' } do
-
1
scope module: :v1, constraints: ApiConstraints.new(version: 1, default: true) do
-
1
get 'user' => 'user#show'
-
1
resources :sessions, only: [:create, :destroy]
-
1
resources :players, except: [:new, :edit]
-
1
resources :teams, except: [:new, :edit]
-
1
resources :matches, except: [:new, :edit]
-
1
resources :match_scoreboard, only: [:show]
-
1
post 'match_scoreboard/:id' => 'match_scoreboard#update'
-
end
-
end
-
-
end
-
-
# Determine if a router request matches an api version number
-
1
class ApiConstraints
-
-
# * *Args* : options
-
# - +version+ -> number
-
# - Version number to match
-
# - +default+ -> Boolean
-
# - If request does not identify a version, default to +version+
-
1
def initialize(options)
-
6
@version = options[:version]
-
6
@default = options[:default]
-
end
-
-
# Determine whether a request matches
-
# an api version number
-
#
-
# * *Args* :
-
# - +request+ -> router request
-
# * *Returns* : Boolean
-
# - +:true+ - the request matches the api version
-
# - +:false+ - the request does not match the api version
-
1
def matches?(request)
-
5
accept = request.headers['Accept']
-
5
if accept.nil?
-
2
@default
-
else
-
3
if @default && !accept.include?(PREFIX)
-
1
true
-
else
-
2
accept.include?("#{PREFIX}#{@version}")
-
end
-
end
-
end
-
-
1
private
-
-
# version prefix string
-
1
PREFIX = 'application/tennis.scoreboard.v'
-
end
-
# Class to help "play" a match. This class is used to generate seed data and test data.
-
#
-
1
class MatchPlayer
-
-
# Class method to play a match.
-
# Calls MatchPlayer.play
-
# * *Args* :
-
# - +match+ -> Match
-
# - +scores+ -> Array
-
# - Array of characters within an array of sets
-
1
def self.play(match, scores)
-
83
MatchPlayer.new(match).play(scores)
-
end
-
-
# Convert an array of numeric scores into an array of
-
# character scores.
-
# This method is convenient for creating scores when the
-
# exact order of wins and losses is not important.
-
# Pass the result of this method to #play
-
#
-
# * *Args* :
-
# - +scores+ -> Array
-
# - Array of numeric pairs within an array of sets
-
# * *Returns* : Array
-
# - Array of characters within an array of sets
-
#
-
# === Scores Example
-
# [[6, 2],[3,2]]
-
#
-
# This array of numeric scores result in the following array of character scores:
-
# [['w', 'l', 'w', 'l', 'w', 'w', 'w', 'w'], ['w', 'l', 'w', 'l', 'w']]
-
1
def self.convert_scores(scores)
-
257
scores.map { |pair| convert_set_score(pair[0], pair[1]) }
-
end
-
-
# * *Args* :
-
# - +match+ -> Match
-
1
def initialize(match)
-
170
@match = match
-
end
-
-
# Play a match
-
# * *Args* :
-
# - +scores+ -> array of set scores
-
# - Array of characters within an array of sets
-
#
-
# === Scores Example
-
# [['l', 'w', 'w', 'w', 'w', 'w', 'w'], ['w', 'l', 'w', 'l', 'w']]
-
#
-
# This array indicates that the first opponent is to win the first set 6-1,
-
# and be ahead 3-2 in the second.
-
#
-
1
def play(scores)
-
83
match.play_match! :start_play
-
# apply_first_servers
-
253
scores.each { |v| play_set(v) }
-
end
-
-
# Start the match by executing the
-
# +:start_play+ action
-
1
def start_play
-
81
match.save!
-
81
if match.play_match? :start_play
-
81
match.play_match! :start_play
-
end
-
end
-
-
# Start the match and start the first game by executing the
-
# +:start_play+ and +:start_game+ actions
-
1
def start_first_game
-
20
start_play
-
20
if match.play_match?(:start_game) && no_games?
-
20
if match.doubles
-
9
match.play_match! :start_game, opponent: match.first_team.first_player
-
else
-
11
match.play_match! :start_game, opponent: match.first_player
-
end
-
end
-
end
-
-
# Start the next set by executing the
-
# +:start_set+ action
-
1
def start_set_game
-
# Starting set also starts first game
-
6
match.play_match! :start_set
-
end
-
-
# Indicate that the first opponent is to win
-
1
FIRST_OPPONENT_WIN = %w(w).freeze
-
# Indicate that the second opponent is to win
-
1
SECOND_OPPONENT_WIN = %w(l).freeze
-
-
1
private
-
-
1
attr_reader :match
-
-
# Play a set
-
1
def play_set(scores)
-
170
start_set
-
170
play_games(scores)
-
end
-
-
# Create an array of 'w' and 'l' that may be passed to #play_set
-
# Parameter values first_wins: 6, second_wins: 1 would return
-
# %w(w l w w w w w).
-
1
def self.convert_set_score(first_wins, second_wins)
-
172
score = []
-
# mix wins and losses so that set is not won prematurely
-
172
min = [first_wins, second_wins].min
-
546
1.upto(min) { score << FIRST_OPPONENT_WIN + SECOND_OPPONENT_WIN }
-
# now, add winning games
-
172
win = if first_wins > second_wins
-
85
FIRST_OPPONENT_WIN
-
else
-
87
SECOND_OPPONENT_WIN
-
end
-
705
1.upto((first_wins - second_wins).abs) { score << win }
-
172
score.flatten
-
end
-
-
1
def no_games?
-
20
match.match_sets.count == 1 && match.match_sets[0].set_games.count == 0
-
end
-
-
1
def start_set
-
170
if match.play_match? :start_match_tiebreak
-
31
match.play_match! :start_match_tiebreak
-
139
elsif match.play_match? :start_set
-
56
match.play_match! :start_set
-
end
-
end
-
-
1
def play_games(scores)
-
170
scores.each do |w_or_l|
-
1271
winner = if w_or_l == 'w'
-
647
match.first_team
-
else
-
624
match.second_team
-
end
-
1271
play_game(winner)
-
end
-
end
-
-
1
def play_game(winner)
-
1271
if match.play_match?(:start_game) || match.play_match?(:win_game)
-
1224
play_normal_game winner
-
47
elsif match.play_match? :win_match_tiebreak
-
19
match.play_match! :win_match_tiebreak, opponent: winner
-
28
elsif match.play_match?(:start_tiebreak) || match.play_match?(:win_tiebreak)
-
28
play_tiebreak winner
-
else
-
invalid_game
-
end
-
end
-
-
1
def play_tiebreak(winner)
-
28
if match.play_match? :start_tiebreak
-
28
match.play_match! :start_tiebreak
-
end
-
28
match.play_match! :win_tiebreak, opponent: winner
-
end
-
-
1
def play_normal_game(winner)
-
1224
if match.play_match? :start_game
-
# May need a first server
-
1168
param = nil
-
1168
if match.doubles
-
297
if match.first_player_server.nil?
-
17
param = match.first_team.first_player
-
elsif match.second_player_server.nil?
-
17
param = match.second_team.first_player
-
end
-
else
-
871
if match.first_player_server.nil?
-
66
param = match.first_player
-
end
-
end
-
1168
match.play_match! :start_game, opponent: param
-
end
-
1224
match.play_match! :win_game, opponent: winner
-
end
-
-
1
def invalid_game
-
raise "Invalid game. Set: #{match.last_set.ordinal},"\
-
"Game: #{match.last_set.last_game.ordinal}"
-
end
-
end
-
1
require 'rails_helper'
-
-
1
module ControllersShared
-
-
1
def self.message_text(id)
-
52
if id.is_a?(Symbol)
-
28
case id
-
when :cant_be_blank
-
12
'can\'t be blank'
-
when :already_taken
-
12
'has already been taken'
-
when :not_found
-
4
'not found'
-
else
-
id.to_s
-
end
-
else
-
24
id.to_s
-
end
-
end
-
-
1
def self.exclude_attribute(obj, exclude)
-
10
attributes = obj.clone
-
10
attributes.delete(exclude)
-
10
attributes
-
end
-
-
1
def self.change_attribute(obj, name, value)
-
8
attributes = obj.clone
-
8
attributes[name] = value
-
8
attributes
-
end
-
-
-
1
RSpec::Matchers.define :include_error do |message|
-
16
match do |json|
-
16
@text = ControllersShared::message_text(message)
-
16
json.key?(:errors) && json[:errors] == @text
-
end
-
-
16
failure_message do
-
"expect response to include error: #{@text}"
-
end
-
-
16
failure_message_when_negated do
-
"do not expect response to include error: #{@text}"
-
end
-
end
-
-
1
RSpec::Matchers.define :include_error_named do |name, message|
-
18
match do |json|
-
18
@text = ControllersShared::message_text(message)
-
18
json.key?(:errors) && json[:errors].key?(name) && json[:errors][name][0] == @text
-
end
-
-
18
description do
-
18
"include error :#{name} with value of \"#{ControllersShared::message_text(message)}\""
-
end
-
-
18
failure_message do
-
"expect response to include error: #{@text}"
-
end
-
-
18
failure_message_when_negated do
-
"do not expect response to include error: #{@text}"
-
end
-
end
-
-
1
RSpec::Matchers.define :have_errors do
-
8
match do |json|
-
8
json.key?(:errors) && !json[:errors].empty?
-
end
-
-
8
failure_message do
-
'expect to have errors'
-
end
-
-
8
failure_message_when_negated do
-
'do not expect to have errors'
-
end
-
end
-
-
1
RSpec.shared_examples 'a response with success code' do |code|
-
52
it { is_expected.to respond_with code }
-
end
-
-
1
RSpec.shared_examples 'a response with error code' do |code|
-
84
it { is_expected.to respond_with code }
-
end
-
-
1
RSpec.shared_examples 'login required' do
-
24
it { expect(json_response).to include_error 'Login required' }
-
-
12
it_behaves_like 'a response with error code', 403
-
end
-
-
1
RSpec.shared_examples 'not authorized' do
-
2
it { expect(json_response).to include_error 'Not authorized' }
-
-
1
it_behaves_like 'a response with error code', 401
-
end
-
-
1
RSpec.shared_examples 'not found' do
-
11
it_behaves_like 'a response with error code', 404
-
end
-
-
1
RSpec.shared_examples 'attribute error' do |name, message|
-
30
it { expect(json_response).to include_error_named name, message }
-
-
15
it_behaves_like 'a response with error code', 422
-
end
-
-
1
RSpec.shared_examples 'general error' do |message|
-
4
it { expect(json_response).to include_error message }
-
-
2
it_behaves_like 'a response with error code', 422
-
end
-
-
1
RSpec.shared_examples 'an error when delete referenced entity' do |message|
-
6
it { expect(json_response).to include_error_named :errors, message }
-
-
6
it { is_expected.to respond_with 422 }
-
end
-
-
1
RSpec.shared_examples 'player response' do
-
6
it { expect(json_response).to include :id, :name }
-
end
-
-
1
RSpec.shared_examples 'player list response' do
-
2
it { expect(json_response[0]).to include :id, :name }
-
end
-
-
1
RSpec.shared_examples 'team response' do
-
4
it { expect(json_response).to include :id, :name, :first_player, :second_player }
-
end
-
-
1
RSpec.shared_examples 'team list response' do
-
2
it { expect(json_response[0]).to include :id, :name, :first_player, :second_player }
-
end
-
-
1
RSpec.shared_examples 'doubles match response' do
-
8
it { expect(json_response).to include :id, :title, :first_team, :second_team, :doubles }
-
end
-
-
1
RSpec.shared_examples 'doubles teams' do
-
1
describe('first_team') do
-
3
subject { Team.find_by_id(json_response[:first_team][:id]) }
-
-
2
it { is_expected.not_to be_nil }
-
-
2
it { expect(subject.doubles).to be_truthy }
-
end
-
-
1
describe('second_team') do
-
3
subject { Team.find_by_id(json_response[:second_team][:id]) }
-
-
2
it { is_expected.not_to be_nil }
-
-
2
it { expect(subject.doubles).to be_truthy }
-
end
-
end
-
-
1
RSpec.shared_examples 'match list response' do
-
2
it { expect(json_response[0]).to include :id, :title, :doubles }
-
end
-
-
1
RSpec.shared_examples 'singles match response' do
-
8
it { expect(json_response).to include :id, :title, :first_player, :second_player, :doubles }
-
end
-
-
1
RSpec.shared_examples 'singles players' do
-
1
describe('first_player') do
-
2
subject { Player.find_by_id(json_response[:first_player][:id]) }
-
-
2
it { is_expected.not_to be_nil }
-
end
-
-
1
describe('second_player') do
-
2
subject { Player.find_by_id(json_response[:second_player][:id]) }
-
-
2
it { is_expected.not_to be_nil }
-
end
-
end
-
-
1
RSpec.shared_examples 'action response' do
-
7
it do
-
7
expect(json_response).to include :id, :title, :doubles,
-
:actions, :errors, :sets, :state, :winner, :servers
-
end
-
-
14
it { is_expected.to respond_with 200 }
-
end
-
-
1
RSpec.shared_examples 'doubles match scoreboard response' do
-
3
it_behaves_like 'action response'
-
6
it { expect(json_response).to include :first_team, :second_team }
-
end
-
-
1
RSpec.shared_examples 'singles match scoreboard response' do
-
4
it_behaves_like 'action response'
-
8
it { expect(json_response).to include :first_player, :second_player }
-
end
-
-
1
RSpec.shared_examples 'accepted action' do
-
10
it { expect(json_response).not_to have_errors }
-
end
-
-
1
RSpec.shared_examples 'denied action' do
-
6
it { expect(json_response).to have_errors }
-
end
-
end
-
-
1
RSpec.configure do |c|
-
1
c.extend ControllersShared, type: :controller
-
end
-
-
-
-
-
1
require 'rails_helper'
-
1
require 'controllers/controllers_shared'
-
-
1
RSpec.describe V1::ApplicationController, { type: :controller } do
-
4
let(:user1) { FactoryGirl.create :user }
-
-
1
controller do
-
1
def index
-
9
authorize_user!
-
end
-
end
-
-
1
def set_authorization(token)
-
6
request.headers['Authorization'] = token
-
end
-
-
1
describe 'authorize user' do
-
1
context 'when no auth token' do
-
4
before { get :index }
-
-
1
it 'should not have user' do
-
1
expect(subject.current_user).to be_nil
-
end
-
-
1
it_behaves_like 'login required'
-
end
-
-
1
context 'when invalid auth token' do
-
1
before do
-
3
set_authorization 'abcd'
-
3
get :index
-
end
-
-
1
it 'should not have user' do
-
1
expect(subject.current_user).to be_nil
-
end
-
-
1
it_behaves_like 'not authorized'
-
end
-
-
1
context 'when valid auth token' do
-
1
before do
-
3
set_authorization user1.auth_token
-
3
get :index
-
end
-
-
1
it 'should have user' do
-
1
expect(subject.current_user).not_to be_nil
-
end
-
-
2
it { is_expected.to be_user_signed_in }
-
-
1
it_behaves_like 'a response with success code', 200
-
end
-
end
-
-
end
-
-
1
require 'rails_helper'
-
1
require 'controllers/controllers_shared'
-
-
1
RSpec.describe V1::MatchScoreboardController, { type: :controller } do
-
-
29
let(:new_user) { FactoryGirl.create :user }
-
15
let(:doubles_match) { FactoryGirl.create :doubles_match }
-
6
let(:singles_match) { FactoryGirl.create :singles_match }
-
3
let(:not_found_match_id) { doubles_match.id + singles_match.id }
-
1
let(:match_title) { doubles_match.title + 'a' }
-
-
1
describe 'GET #show' do
-
1
context 'when doubles' do
-
4
before { get :show, id: doubles_match.id }
-
-
1
it_behaves_like 'doubles match scoreboard response'
-
end
-
-
1
context 'when singles' do
-
4
before { get :show, id: singles_match.id }
-
-
1
it_behaves_like 'singles match scoreboard response'
-
end
-
-
1
context 'when does not exist' do
-
2
before { get :show, id: not_found_match_id }
-
-
1
it_behaves_like 'not found'
-
end
-
end
-
-
1
describe 'PUT/PATCH #update' do
-
1
def post_action(id, action, param = nil)
-
30
params = { action: action }
-
30
params.merge!(param) if param
-
30
post :update, id: id, match_scoreboard: params
-
end
-
-
1
context 'when authorized' do
-
29
before { api_authorization_header new_user.auth_token }
-
-
1
context('when is started') do
-
5
before { post_action doubles_match.id, :start_play }
-
-
1
it_behaves_like 'doubles match scoreboard response'
-
1
it_behaves_like 'accepted action'
-
end
-
-
1
context 'when action is unknown' do
-
-
1
describe 'unknown name' do
-
3
before { post_action doubles_match.id, :xxx }
-
-
1
it_behaves_like 'general error', 'Unknown action: xxx'
-
end
-
-
1
describe 'requires a parameter' do
-
3
before { post_action doubles_match.id, :win_game }
-
-
1
it_behaves_like 'general error', "Unknown team for action: #{:win_game}"
-
end
-
end
-
-
1
context 'when is denied' do
-
2
before { post_action doubles_match.id, :start_tiebreak }
-
-
1
it_behaves_like 'denied action'
-
end
-
-
1
context 'when does not exists' do
-
2
before { post_action not_found_match_id, :start_play }
-
-
1
it_behaves_like 'not found'
-
end
-
-
1
context 'when pass parameter' do
-
1
describe 'to start game' do
-
5
let(:match) { FactoryGirl.build :play_singles_match, start_play: true }
-
1
before do
-
# identify first server
-
4
post_action match.id, :start_game, { player: match.first_player.id, version: match.play_version }
-
end
-
1
it_behaves_like 'singles match scoreboard response'
-
1
it_behaves_like 'accepted action'
-
end
-
-
1
describe 'to win' do
-
1
context 'doubles' do
-
5
let(:match) { FactoryGirl.build :play_doubles_match, start_first_game: true }
-
1
before do
-
# identify winning team
-
4
post_action match.id, :win_game, { team: match.first_team.id }
-
end
-
1
it_behaves_like 'doubles match scoreboard response'
-
1
it_behaves_like 'accepted action'
-
end
-
end
-
-
1
context 'singles' do
-
5
let(:match) { FactoryGirl.build :play_singles_match, start_first_game: true }
-
1
before do
-
# identify winning player
-
4
post_action match.id, :win_game, { player: match.first_player.id }
-
end
-
1
it_behaves_like 'singles match scoreboard response'
-
1
it_behaves_like 'accepted action'
-
end
-
end
-
-
1
context 'when pass scoreboard version' do
-
7
let(:match) { FactoryGirl.build :play_singles_match, start_play: true }
-
-
1
describe 'that is behind' do
-
1
before do
-
1
post_action match.id, :start_game, { player: match.first_player.id, version: match.play_version - 1 }
-
end
-
1
it_behaves_like 'denied action'
-
end
-
-
1
describe 'that is ahead' do
-
1
before do
-
1
post_action match.id, :start_game, { player: match.first_player.id, version: match.play_version + 1 }
-
end
-
1
it_behaves_like 'denied action'
-
end
-
-
1
describe 'that is correct' do
-
1
before do
-
4
post_action match.id, :start_game, { player: match.first_player.id, version: match.play_version }
-
end
-
1
it_behaves_like 'singles match scoreboard response'
-
1
it_behaves_like 'accepted action'
-
end
-
-
end
-
end
-
-
1
context('when not authorized') do
-
3
before { post_action 1, :start_play }
-
-
1
it_behaves_like 'login required'
-
end
-
end
-
end
-
1
require 'rails_helper'
-
1
require 'controllers/controllers_shared'
-
-
1
RSpec.describe V1::MatchesController, { type: :controller } do
-
-
38
let(:new_user) { FactoryGirl.create :user }
-
37
let(:doubles_match_title) { 'doubles match' }
-
22
let(:singles_match_title) { 'singles match' }
-
3
let(:doubles_match2_title) { 'doubles match 2' }
-
20
let(:doubles_match) { FactoryGirl.create :doubles_match, title: doubles_match_title }
-
20
let(:doubles_match_attributes) { FactoryGirl.attributes_for :doubles_match, title: doubles_match_title }
-
3
let(:doubles_match2) { FactoryGirl.create :doubles_match, title: doubles_match2_title }
-
15
let(:singles_match) { FactoryGirl.create :singles_match, title: singles_match_title }
-
4
let(:not_found_match_id) { doubles_match.id + singles_match.id }
-
5
let(:new_doubles_match_title) { 'new doubles' }
-
4
let(:new_singles_match_title) { 'new singles' }
-
8
let(:singles_match_attributes) { FactoryGirl.attributes_for :singles_match, :player_ids, title: singles_match_title }
-
1
let(:player1) { FactoryGirl.create :player, name: 'one' }
-
1
let(:player2) { FactoryGirl.create :player, name: 'two' }
-
-
1
def exclude_doubles_attribute(exclude)
-
6
ControllersShared::exclude_attribute(doubles_match_attributes, exclude)
-
end
-
-
1
def change_doubles_attribute(name, value)
-
2
ControllersShared::change_attribute(doubles_match_attributes, name, value)
-
end
-
-
1
describe 'GET #index' do
-
1
before do
-
2
doubles_match
-
2
get :index
-
end
-
-
1
it_behaves_like 'match list response'
-
1
it_behaves_like 'a response with success code', 200
-
end
-
-
1
describe 'GET #show' do
-
1
context 'when doubles exists' do
-
3
before { get :show, id: doubles_match.id }
-
-
1
it_behaves_like 'doubles match response'
-
1
it_behaves_like 'a response with success code', 200
-
end
-
-
1
context 'when singles exists' do
-
3
before { get :show, id: singles_match.id }
-
-
1
it_behaves_like 'singles match response'
-
1
it_behaves_like 'a response with success code', 200
-
end
-
-
1
context 'when does not exists' do
-
2
before { get :show, id: not_found_match_id }
-
-
1
it_behaves_like 'not found'
-
end
-
end
-
-
1
describe 'POST #create' do
-
1
context 'when authorized' do
-
17
before { api_authorization_header new_user.auth_token }
-
-
1
context 'when doubles is successfully created' do
-
4
before { post :create, { match: doubles_match_attributes } }
-
-
1
it 'should render title' do
-
1
expect(json_response[:title]).to eql doubles_match_attributes[:title]
-
end
-
-
1
it_behaves_like 'doubles match response'
-
1
it_behaves_like 'a response with success code', 201
-
end
-
-
1
context 'when singles is successfully created' do
-
4
before { post :create, { match: singles_match_attributes } }
-
-
1
it 'should render json representation' do
-
1
expect(json_response[:title]).to eql singles_match_attributes[:title]
-
end
-
-
1
it_behaves_like 'singles match response'
-
1
it_behaves_like 'a response with success code', 201
-
end
-
-
1
context 'when title is nil' do
-
1
before do
-
1
post :create, { match: change_doubles_attribute(:title, nil) }
-
end
-
-
1
it 'should render json representation' do
-
1
expect(json_response[:title]).to start_with('Match')
-
end
-
end
-
-
1
context 'when title is blank' do
-
1
before do
-
1
post :create, { match: change_doubles_attribute(:title, '') }
-
end
-
-
1
it 'should generate a title' do
-
1
expect(json_response[:title]).to start_with('Match')
-
end
-
end
-
-
1
context 'when first team is missing' do
-
1
before do
-
2
post :create, { match: exclude_doubles_attribute(:first_team_id) }
-
end
-
-
1
it_behaves_like 'attribute error', :first_team, :cant_be_blank
-
end
-
-
1
context 'when second team is missing' do
-
1
before do
-
2
post :create, { match: exclude_doubles_attribute(:second_team_id) }
-
end
-
-
1
it_behaves_like 'attribute error', :second_team, :cant_be_blank
-
end
-
-
1
context 'when scoring is missing' do
-
1
before do
-
2
post :create, { match: exclude_doubles_attribute(:scoring) }
-
end
-
-
1
it_behaves_like 'attribute error', :scoring, :cant_be_blank
-
end
-
-
1
context 'when title is already taken' do
-
1
before do
-
2
doubles_match
-
2
post :create, { match: doubles_match_attributes }
-
end
-
1
it_behaves_like 'attribute error', :title, :already_taken
-
end
-
end
-
-
1
context 'when not authorized' do
-
1
context 'when is not created' do
-
3
before { post :create, { match: {} } }
-
-
1
it_behaves_like 'login required'
-
end
-
end
-
end
-
-
1
describe 'PUT/PATCH #update' do
-
1
context 'when authorized' do
-
20
before { api_authorization_header new_user.auth_token }
-
-
1
context 'when is successfully updated' do
-
-
1
context 'singles' do
-
1
before do
-
3
patch :update, { id: singles_match.id, match: { title: new_singles_match_title } }
-
end
-
-
1
it 'should render json representation' do
-
1
expect(json_response[:title]).to eql new_singles_match_title
-
end
-
-
1
it_behaves_like 'singles match response'
-
1
it_behaves_like 'a response with success code', 200
-
end
-
-
1
context 'doubles' do
-
1
before do
-
3
patch :update, { id: doubles_match.id, match: { title: new_doubles_match_title } }
-
end
-
-
1
it 'should render json representation' do
-
1
expect(json_response[:title]).to eql new_doubles_match_title
-
end
-
-
1
it_behaves_like 'doubles match response'
-
1
it_behaves_like 'a response with success code', 200
-
end
-
end
-
-
1
context 'when change singles to doubles' do
-
1
before do
-
6
patch :update, { id: singles_match.id, match: doubles_match_attributes }
-
end
-
1
it_behaves_like 'doubles match response'
-
1
it_behaves_like 'doubles teams'
-
1
it_behaves_like 'a response with success code', 200
-
-
end
-
-
1
context 'when change doubles to singles' do
-
1
before do
-
4
patch :update, { id: doubles_match.id, match: singles_match_attributes }
-
end
-
1
it_behaves_like 'singles match response'
-
1
it_behaves_like 'singles players'
-
1
it_behaves_like 'a response with success code', 200
-
-
end
-
-
1
context 'when does not exists' do
-
1
before do
-
1
patch :update, { id: not_found_match_id, match: { title: new_doubles_match_title } }
-
end
-
-
1
it_behaves_like 'not found'
-
end
-
-
1
context 'when title is already taken' do
-
1
before do
-
2
patch :update, { id: doubles_match.id, match: { title: doubles_match2.title } }
-
end
-
1
it_behaves_like 'attribute error', :title, :already_taken
-
end
-
end
-
-
1
context 'when not authorized' do
-
3
before { patch :update, { id: 1, match: { title: 'abc' } } }
-
-
1
it_behaves_like 'login required'
-
end
-
end
-
-
1
describe 'DELETE #destroy' do
-
1
context 'when authorized' do
-
3
before { api_authorization_header new_user.auth_token }
-
-
1
context 'when exists' do
-
2
before { delete :destroy, id: doubles_match.id }
-
-
2
it { is_expected.to respond_with 204 }
-
end
-
-
1
context 'when does not exists' do
-
2
before { delete :destroy, id: not_found_match_id }
-
-
1
it_behaves_like 'not found'
-
end
-
end
-
-
1
context 'when not authorized' do
-
3
before { delete :destroy, id: 1 }
-
-
1
it_behaves_like 'login required'
-
end
-
end
-
end
-
1
require 'rails_helper'
-
1
require 'controllers/controllers_shared'
-
-
1
RSpec.describe V1::PlayersController, { type: :controller } do
-
-
22
let(:new_user) { FactoryGirl.create :user }
-
22
let(:player_name) { 'player1'}
-
3
let(:player2_name) { 'player2'}
-
19
let(:player) { FactoryGirl.create :player, name: player_name }
-
3
let(:player2) { FactoryGirl.create :player, name: player2_name }
-
6
let(:player_attributes) { FactoryGirl.attributes_for :player, name: player_name }
-
5
let(:new_player_name) { 'new player' }
-
4
let(:not_found_player_id) { player.id + 1 }
-
3
let(:doubles_team) { FactoryGirl.create :doubles_team }
-
3
let(:doubles_match) { FactoryGirl.create :doubles_match }
-
-
1
describe 'GET #index' do
-
1
context 'when exists' do
-
1
before do
-
2
player
-
2
get :index
-
end
-
-
1
it_behaves_like 'player list response'
-
1
it_behaves_like 'a response with success code', 200
-
end
-
end
-
-
1
describe 'GET #show' do
-
1
context 'when exists' do
-
1
before do
-
3
get :show, id: player.id
-
end
-
-
1
it_behaves_like 'player response'
-
-
1
it 'should render json representation' do
-
1
expect(json_response[:name]).to eql player.name
-
end
-
-
1
it_behaves_like 'a response with success code', 200
-
end
-
-
1
context 'when does not exists' do
-
1
before do
-
1
get :show, id: not_found_player_id
-
end
-
-
1
it_behaves_like 'not found'
-
end
-
end
-
-
1
describe 'POST #create' do
-
1
context 'when authorized' do
-
1
before do
-
7
api_authorization_header new_user.auth_token
-
end
-
-
1
context 'when is successfully created' do
-
1
before do
-
3
post :create, { player: player_attributes }
-
end
-
-
1
it_behaves_like 'player response'
-
-
1
it 'should render json representation' do
-
1
expect(json_response[:name]).to eql player_attributes[:name]
-
end
-
-
1
it_behaves_like 'a response with success code', 201
-
end
-
-
1
context 'when name is missing' do
-
1
before do
-
2
invalid_model_attributes = {}
-
2
post :create, { player: invalid_model_attributes }
-
end
-
-
1
it_behaves_like 'attribute error', :name, :cant_be_blank
-
end
-
-
1
context 'when name is already taken' do
-
1
before do
-
2
player
-
2
post :create, { player: player_attributes }
-
end
-
-
1
it_behaves_like 'attribute error', :name, :already_taken
-
end
-
end
-
-
1
context 'when not authorized' do
-
1
context 'when is not created' do
-
1
before do
-
2
post :create, { player: {} }
-
end
-
-
1
it_behaves_like 'login required'
-
end
-
end
-
end
-
-
1
describe 'PUT/PATCH #update' do
-
1
context 'when authorized' do
-
1
before do
-
8
api_authorization_header new_user.auth_token
-
end
-
-
1
context 'when is successfully updated' do
-
1
before do
-
3
patch :update, { id: player.id, player: { name: new_player_name } }
-
end
-
-
1
it_behaves_like 'player response'
-
-
1
it 'should render json representation' do
-
1
expect(json_response[:name]).to eql new_player_name
-
end
-
-
1
it_behaves_like 'a response with success code', 200
-
end
-
-
1
context 'when does not exists' do
-
1
before do
-
1
patch :update, { id: not_found_player_id, player: { name: new_player_name } }
-
end
-
-
1
it_behaves_like 'not found'
-
end
-
-
1
context 'when name is missing' do
-
1
before do
-
2
patch :update, { id: player.id, player: { name: '' } }
-
end
-
-
1
it_behaves_like 'attribute error', :name, :cant_be_blank
-
end
-
-
1
context 'when name is already taken' do
-
1
before do
-
2
patch :update, { id: player.id, player: { name: player2.name } }
-
end
-
-
1
it_behaves_like 'attribute error', :name, :already_taken
-
end
-
end
-
-
1
context 'when not authorized' do
-
1
before do
-
2
patch :update, { id: 1, player: { } }
-
end
-
-
1
it_behaves_like 'login required'
-
end
-
-
end
-
-
1
describe 'DELETE #destroy' do
-
1
context 'when authorized' do
-
-
1
before do
-
6
api_authorization_header new_user.auth_token
-
end
-
-
1
context 'when exists' do
-
1
before do
-
1
delete :destroy, id: player.id
-
end
-
-
1
it_behaves_like 'a response with success code', 204
-
end
-
-
1
context 'when does not exists' do
-
1
before do
-
1
delete :destroy, id: not_found_player_id
-
end
-
-
1
it_behaves_like 'not found'
-
end
-
-
1
context 'when player on team' do
-
1
before do
-
2
delete :destroy, id: doubles_team.first_player_id
-
end
-
-
1
it_behaves_like 'an error when delete referenced entity', 'Can\'t delete a player in a match or on a team'
-
end
-
-
1
context 'when player in match' do
-
1
before do
-
2
delete :destroy, id: doubles_match.first_team.first_player_id
-
end
-
-
1
it_behaves_like 'an error when delete referenced entity', 'Can\'t delete a player in a match or on a team'
-
end
-
end
-
-
1
context 'when not authorized' do
-
1
before do
-
2
delete :destroy, id: 1
-
end
-
-
1
it_behaves_like 'login required'
-
end
-
end
-
end
-
1
require 'rails_helper'
-
1
require 'controllers/controllers_shared'
-
-
1
RSpec.describe V1::SessionsController, { type: :controller } do
-
-
5
let(:user) { FactoryGirl.create :user }
-
2
let(:password) { '12345678' }
-
3
let(:unknown_password) { '99999999' }
-
-
1
describe 'POST #create' do
-
-
1
context 'when the credentials are correct' do
-
-
1
before do
-
1
credentials = { username: user.username, password: password }
-
1
post :create, { session: credentials }
-
end
-
-
1
it_behaves_like 'a response with success code', 200
-
end
-
-
1
context 'when the credentials are incorrect' do
-
1
before do
-
2
credentials = { username: user.username, password: unknown_password }
-
2
post :create, { session: credentials }
-
end
-
-
2
it { expect(json_response).to include_error 'Invalid username or password' }
-
-
1
it_behaves_like 'a response with error code', 422
-
end
-
-
end
-
-
1
describe 'DELETE #destroy' do
-
1
before do
-
# sign_in user
-
end
-
-
1
describe 'valid token' do
-
1
before do
-
1
delete :destroy, id: user.auth_token
-
end
-
-
1
it_behaves_like 'a response with success code', 204
-
end
-
-
1
describe 'invalid token' do
-
1
before do
-
1
delete :destroy, id: 'xxxxxxx'
-
end
-
-
# NoOp
-
1
it_behaves_like 'a response with success code', 204
-
end
-
end
-
end
-
1
require 'rails_helper'
-
1
require 'controllers/controllers_shared'
-
-
1
RSpec.describe V1::TeamsController, { type: :controller } do
-
-
26
let(:new_user) { FactoryGirl.create :user }
-
23
let(:doubles_team_name) {'doubles team'}
-
3
let(:doubles_team2_name) {'doubles team 2'}
-
23
let(:player_name) {'player name'}
-
3
let(:player2_name) {'player2 name'}
-
23
let(:doubles_team) { FactoryGirl.create :doubles_team, name: doubles_team_name,
-
first_player_name: player_name }
-
3
let(:doubles_team2) { FactoryGirl.create :doubles_team, name: doubles_team2_name,
-
first_player_name: player2_name }
-
17
let(:doubles_team_attributes) { FactoryGirl.attributes_for :doubles_team }
-
4
let(:not_found_team_id) {doubles_team.id + 1}
-
5
let(:not_found_player_id) {doubles_team.id}
-
3
let(:doubles_match) { FactoryGirl.create :doubles_match }
-
2
let(:team_name) { doubles_team.name + 'a'}
-
-
1
def exclude_team_attribute(exclude)
-
4
ControllersShared::exclude_attribute(doubles_team_attributes, exclude)
-
end
-
-
1
def change_team_attribute(name, value)
-
6
ControllersShared::change_attribute(doubles_team_attributes, name, value)
-
end
-
-
1
describe 'GET #index' do
-
1
before do
-
2
doubles_team
-
2
get :index
-
end
-
-
1
it_behaves_like 'team list response'
-
1
it_behaves_like 'a response with success code', 200
-
end
-
-
1
describe 'GET #show' do
-
1
context 'when exists' do
-
4
before { get :show, id: doubles_team.id }
-
-
1
it_behaves_like 'team response'
-
-
1
it 'should render the json representation' do
-
1
expect(json_response[:name]).to eql doubles_team.name
-
end
-
-
1
it_behaves_like 'a response with success code', 200
-
end
-
-
1
context 'when exists doubles team' do
-
4
before { get :show, id: doubles_team.id }
-
-
1
it_behaves_like 'team response'
-
-
1
it 'should render the json representation' do
-
1
expect(json_response[:name]).to eql doubles_team.name
-
end
-
-
1
it_behaves_like 'a response with success code', 200
-
end
-
-
1
context 'when does not exists' do
-
2
before { get :show, id: not_found_team_id }
-
-
1
it_behaves_like 'not found'
-
end
-
end
-
-
1
describe 'POST #create' do
-
1
context 'when authorized' do
-
17
before { api_authorization_header new_user.auth_token }
-
-
1
context 'when is successfully created' do
-
1
before do
-
2
post :create, { team: doubles_team_attributes }
-
end
-
-
1
it 'should render the json representation' do
-
1
expect(json_response[:name]).to eql doubles_team_attributes[:name]
-
end
-
-
1
it_behaves_like 'a response with success code', 201
-
end
-
-
1
context 'when doubles team is successfully created' do
-
3
before { post :create, { team: doubles_team_attributes } }
-
-
1
it 'should render the json representation' do
-
1
expect(json_response[:name]).to eql doubles_team_attributes[:name]
-
end
-
-
1
it_behaves_like 'a response with success code', 201
-
end
-
-
1
context 'when name is blank' do
-
1
before do
-
1
post :create, { team: change_team_attribute(:name, '') }
-
end
-
-
1
it 'should generate a name' do
-
1
expect(json_response[:name]).to start_with('Team')
-
end
-
-
end
-
-
1
context 'when name is nil' do
-
1
before do
-
1
post :create, { team: change_team_attribute(:name, nil) }
-
end
-
-
1
it 'should generate a name' do
-
1
expect(json_response[:name]).to start_with('Team')
-
end
-
end
-
-
1
context 'when first player is missing' do
-
1
before do
-
2
post :create, { team: exclude_team_attribute(:first_player_id) }
-
end
-
-
1
it_behaves_like 'attribute error', :first_player, :cant_be_blank
-
end
-
-
1
context 'when second player is missing' do
-
1
before do
-
2
post :create, { team: exclude_team_attribute(:second_player_id) }
-
end
-
-
1
it_behaves_like 'attribute error', :second_player, 'must be specified'
-
end
-
-
1
context 'when name is already taken' do
-
1
before do
-
2
doubles_team
-
2
post :create, { team: doubles_team_attributes }
-
end
-
-
1
it_behaves_like 'attribute error', :name, :already_taken
-
end
-
-
1
context 'when first player not found' do
-
1
before do
-
2
post :create, { team: change_team_attribute(:first_player_id, not_found_player_id) }
-
end
-
-
1
it_behaves_like 'attribute error', :first_player, :not_found
-
end
-
-
1
context 'when second player is not found' do
-
1
before do
-
2
post :create, { team: change_team_attribute(:second_player_id, not_found_player_id) }
-
end
-
-
1
it_behaves_like 'attribute error', :second_player, :not_found
-
end
-
end
-
-
1
context 'when not authorized' do
-
1
context 'when is not created' do
-
3
before { post :create, { team: {} } }
-
-
1
it_behaves_like 'login required'
-
end
-
end
-
end
-
-
1
describe 'PUT/PATCH #update' do
-
1
context 'when authorized' do
-
6
before { api_authorization_header new_user.auth_token }
-
-
1
context 'when is successfully updated' do
-
3
let(:team_name) { doubles_team.name + 'a'}
-
1
before do
-
2
patch :update, { id: doubles_team.id, team: { name: team_name } }
-
end
-
-
1
it 'should render the json representation' do
-
1
expect(json_response[:name]).to eql team_name
-
end
-
-
1
it_behaves_like 'a response with success code', 200
-
end
-
-
1
context 'when does not exists' do
-
1
before do
-
1
patch :update, { id: not_found_team_id, team: { name: team_name } }
-
end
-
-
1
it_behaves_like 'not found'
-
end
-
-
1
context 'when name is already taken' do
-
1
before do
-
2
patch :update, { id: doubles_team.id, team: { name: doubles_team2.name } }
-
end
-
-
1
it_behaves_like 'attribute error', :name, :already_taken
-
end
-
end
-
-
1
context 'when not authorized' do
-
3
before { patch :update, { id: 1, team: {} } }
-
-
1
it_behaves_like 'login required'
-
end
-
-
end
-
-
1
describe 'DELETE #destroy' do
-
1
context 'when authorized' do
-
5
before { api_authorization_header new_user.auth_token }
-
-
1
context 'when exists' do
-
2
before { delete :destroy, id: doubles_team.id }
-
-
1
it_behaves_like 'a response with success code', 204
-
end
-
-
1
context 'when does not exists' do
-
2
before { delete :destroy, id: not_found_team_id }
-
-
1
it_behaves_like 'not found'
-
end
-
-
1
context 'when team in match' do
-
3
before { delete :destroy, id: doubles_match.first_team_id }
-
-
1
it_behaves_like 'an error when delete referenced entity', 'Can\'t delete a team in a match'
-
end
-
end
-
-
1
context 'when not authorized' do
-
3
before { delete :destroy, id: 1 }
-
-
1
it_behaves_like 'login required'
-
end
-
end
-
end
-
1
require 'rails_helper'
-
1
require 'controllers/controllers_shared'
-
-
1
RSpec.describe V1::UserController, { type: :controller } do
-
3
let(:username1) { 'one' }
-
1
let(:username2) { 'two' }
-
1
let(:password) { '12345768' }
-
3
let(:user1) { FactoryGirl.create :user, username: username1 }
-
1
let(:user1_attributes) { FactoryGirl.attributes_for :user, username: username1 }
-
-
1
describe 'GET #show' do
-
1
context 'when is authorized' do
-
3
before { api_authorization_header user1.auth_token }
-
3
before { get :show }
-
-
1
it 'should render the json representation' do
-
1
expect(json_response[:username]).to eql user1.username
-
end
-
-
1
it_behaves_like 'a response with success code', 200
-
end
-
-
1
context 'when is not logged in' do
-
3
before { get :show }
-
-
1
it_behaves_like 'login required'
-
end
-
end
-
-
end
-
-
1
require 'match_player'
-
# Create matches in progress
-
1
FactoryGirl.define do
-
1
factory :play_singles_match, class: Match, parent: :singles_match do
-
1
transient do
-
1
scores []
-
1
start_play false
-
1
start_first_game false
-
1
start_set_game false
-
end
-
-
1
after(:build) do |subject, factory|
-
113
after_build(subject, factory)
-
end
-
end
-
-
1
factory :play_doubles_match, class: Match, parent: :doubles_match do
-
1
transient do
-
1
scores []
-
1
start_play false
-
1
start_first_game false
-
1
start_set_game false
-
end
-
-
1
after(:build) do |subject, factory|
-
46
after_build(subject, factory)
-
end
-
end
-
end
-
-
1
def after_build(subject, factory)
-
159
if factory.start_play
-
61
MatchPlayer.new(subject).start_play
-
end
-
159
if factory.start_first_game
-
20
MatchPlayer.new(subject).start_first_game
-
end
-
159
if factory.scores.count > 0
-
77
scores = MatchPlayer.convert_scores(factory.scores)
-
77
MatchPlayer.play(subject, scores)
-
end
-
159
if factory.start_set_game
-
6
MatchPlayer.new(subject).start_set_game
-
end
-
end
-
-
-
# == Schema Information
-
#
-
# Table name: match_sets
-
#
-
# id :integer not null, primary key
-
# match_id :integer not null
-
# ordinal :integer not null
-
# created_at :datetime not null
-
# updated_at :datetime not null
-
# scoring :string not null
-
# team_winner_id :integer
-
#
-
-
1
FactoryGirl.define do
-
-
1
factory :match_set, class: MatchSet do
-
1
transient do
-
1
match_title 'factory match'
-
end
-
-
1
ordinal 1
-
1
scoring 'six_game'
-
1
match_id do
-
17
(Match.find_by(title: match_title) || FactoryGirl.create(:singles_match, title: match_title)).id
-
end
-
end
-
-
end
-
-
# == Schema Information
-
#
-
# Table name: matches
-
#
-
# id :integer not null, primary key
-
# created_at :datetime not null
-
# updated_at :datetime not null
-
# first_team_id :integer not null
-
# second_team_id :integer not null
-
# scoring :string not null
-
# started :boolean default(FALSE), not null
-
# doubles :boolean default(FALSE), not null
-
# first_player_server_id :integer
-
# second_player_server_id :integer
-
# title :string
-
# team_winner_id :integer
-
# play_version :integer
-
#
-
-
1
FactoryGirl.define do
-
-
1
factory :doubles_match, class: Match do
-
1
transient do
-
1
first_player_name 'factory first player'
-
1
second_player_name 'factory second player'
-
1
third_player_name 'factory third player'
-
1
fourth_player_name 'factory fourth player'
-
1
first_team_name 'factory first team'
-
1
second_team_name 'factory second team'
-
end
-
-
1
title 'doubles match'
-
1
scoring :one_eight_game
-
1
doubles true
-
1
first_team_id do
-
(Team.find_by(name: first_team_name) || FactoryGirl.create(:doubles_team,
-
name: first_team_name,
-
first_player_name: first_player_name,
-
141
second_player_name: second_player_name)).id
-
end
-
1
second_team_id do
-
(Team.find_by(name: second_team_name) || FactoryGirl.create(:doubles_team,
-
name: second_team_name,
-
first_player_name: third_player_name,
-
141
second_player_name: fourth_player_name)).id
-
end
-
-
end
-
-
1
factory :singles_match, class: Match do
-
1
transient do
-
1
first_player_name 'first'
-
1
second_player_name 'second'
-
end
-
-
1
title 'singles match'
-
1
scoring :one_eight_game
-
1
doubles false
-
-
1
first_player do
-
197
MatchFactoryHelper::find_or_create_player(first_player_name)
-
end
-
-
1
second_player do
-
197
MatchFactoryHelper::find_or_create_player(second_player_name)
-
end
-
-
# used with FactoryGirl.attributes_for
-
1
trait :player_ids do
-
1
first_player_id do
-
7
p1 = MatchFactoryHelper::find_or_create_player(first_player_name)
-
7
p1.id
-
end
-
1
second_player_id do
-
7
p2 = MatchFactoryHelper::find_or_create_player(second_player_name)
-
7
p2.id
-
end
-
end
-
end
-
end
-
-
1
module MatchFactoryHelper
-
1
def self.find_or_create_player(player_name)
-
408
(Player.find_by(name: player_name) || FactoryGirl.create(:player, name: player_name))
-
end
-
end
-
-
-
# == Schema Information
-
#
-
# Table name: players
-
#
-
# id :integer not null, primary key
-
# name :string not null
-
# created_at :datetime not null
-
# updated_at :datetime not null
-
#
-
-
1
FactoryGirl.define do
-
1
factory :player do
-
1
name 'factory player'
-
end
-
end
-
# == Schema Information
-
#
-
# Table name: set_games
-
#
-
# id :integer not null, primary key
-
# ordinal :integer not null
-
# match_set_id :integer not null
-
# team_winner_id :integer
-
# created_at :datetime not null
-
# updated_at :datetime not null
-
# player_server_id :integer
-
# tiebreaker :boolean default(FALSE), not null
-
#
-
-
1
FactoryGirl.define do
-
-
1
factory :set_game, class: SetGame do
-
1
transient do
-
1
match_title 'factory match'
-
end
-
-
1
ordinal 1
-
1
match_set_id do
-
8
match = Match.find_by(title: match_title) || FactoryGirl.create(:singles_match, title: match_title)
-
8
set = match.match_sets.find_by(ordinal: 1) || FactoryGirl.create(:match_set, match_title: match_title)
-
8
set.id
-
end
-
end
-
-
end
-
-
# == Schema Information
-
#
-
# Table name: teams
-
#
-
# id :integer not null, primary key
-
# name :string
-
# first_player_id :integer not null
-
# second_player_id :integer
-
# created_at :datetime not null
-
# updated_at :datetime not null
-
# doubles :boolean default(FALSE), not null
-
#
-
-
1
FactoryGirl.define do
-
-
1
factory :doubles_team, class: Team do
-
1
transient do
-
1
first_player_name 'factory first player'
-
1
second_player_name 'factory second player'
-
end
-
-
1
name 'doubles team'
-
1
doubles true
-
1
first_player_id do
-
342
(Player.find_by(name: first_player_name) || FactoryGirl.create(:player, name: first_player_name)).id
-
end
-
1
second_player_id do
-
342
(Player.find_by(name: second_player_name) || FactoryGirl.create(:player, name: second_player_name)).id
-
end
-
end
-
-
end
-
-
# == Schema Information
-
#
-
# Table name: users
-
#
-
# id :integer not null, primary key
-
# username :string default(""), not null
-
# encrypted_password :string default(""), not null
-
# reset_password_token :string
-
# reset_password_sent_at :datetime
-
# remember_created_at :datetime
-
# sign_in_count :integer default(0), not null
-
# current_sign_in_at :datetime
-
# last_sign_in_at :datetime
-
# current_sign_in_ip :string
-
# last_sign_in_ip :string
-
# created_at :datetime
-
# updated_at :datetime
-
# auth_token :string default("")
-
#
-
-
1
FactoryGirl.define do
-
1
factory :user do
-
1
username 'factory user'
-
1
password '12345678'
-
1
password_confirmation '12345678'
-
end
-
end
-
1
require 'rails_helper'
-
1
require 'api_constraints'
-
-
1
RSpec.describe ApiConstraints, { type: :controller } do
-
-
1
context 'when no version and default' do
-
2
subject { ApiConstraints.new({ default: true }) }
-
-
1
it 'should match' do
-
1
expect(subject.matches?(request)).to be_truthy
-
end
-
end
-
-
1
context 'when no version and not default' do
-
2
subject { ApiConstraints.new({ default: false }) }
-
-
1
it 'should not match' do
-
1
expect(subject.matches?(request)).to be_falsy
-
end
-
end
-
-
1
context 'when not a version and default' do
-
1
before do
-
1
api_accept_header 'not.a.version'
-
end
-
-
2
subject { ApiConstraints.new({ default: true }) }
-
-
1
it 'should not match' do
-
1
expect(subject.matches?(request)).to be_truthy
-
end
-
-
end
-
-
1
context 'when correct version' do
-
1
before do
-
1
api_accept_header_version 1
-
end
-
-
2
subject { ApiConstraints.new({ version: 1, default: true }) }
-
-
1
it 'should match' do
-
1
expect(subject.matches?(request)).to be_truthy
-
end
-
end
-
-
1
context 'when incorrect version' do
-
1
before do
-
1
api_accept_header_version 2
-
end
-
-
2
subject { ApiConstraints.new({ version: 1, default: true }) }
-
-
1
it 'should not match' do
-
1
expect(subject.matches?(request)).to be_falsy
-
end
-
end
-
-
end
-
1
require 'rails_helper'
-
1
require 'match_player'
-
-
1
RSpec.describe MatchPlayer, { type: :model } do
-
-
1
describe '::convert_scores' do
-
2
subject { MatchPlayer.convert_scores([[1, 2]]) }
-
-
1
it 'should convert scores' do
-
1
expect(subject).to eq([%w(w l l)])
-
end
-
-
end
-
-
1
describe '::play_match' do
-
1
before do
-
6
MatchPlayer.play(match, scores)
-
end
-
-
7
subject { match }
-
-
1
context 'eight game pro set' do
-
3
let(:scores) { MatchPlayer.convert_scores([[0, 8]]) }
-
1
context 'singles' do
-
2
let(:match) { FactoryGirl.build(:singles_match, scoring: :one_eight_game) }
-
-
1
it 'should have winner' do
-
1
expect(subject.team_winner).to_not be_nil
-
end
-
end
-
-
1
context 'doubles' do
-
2
let(:match) { FactoryGirl.build(:doubles_match, scoring: :one_eight_game) }
-
-
1
it 'should have winner' do
-
1
expect(subject.team_winner).to_not be_nil
-
end
-
end
-
end
-
-
1
context 'two sets' do
-
3
let(:scores) { MatchPlayer.convert_scores([[0, 6], [6, 0], [1, 0]]) }
-
1
context 'singles' do
-
2
let(:match) { FactoryGirl.build(:singles_match, scoring: :two_six_game_ten_point) }
-
-
1
it 'should have winner' do
-
1
expect(subject.team_winner).to_not be_nil
-
end
-
end
-
-
1
context 'doubles' do
-
2
let(:match) { FactoryGirl.build(:doubles_match, scoring: :two_six_game_ten_point) }
-
-
1
it 'should have winner' do
-
1
expect(subject.team_winner).to_not be_nil
-
end
-
end
-
end
-
-
1
context 'three sets' do
-
3
let(:scores) { MatchPlayer.convert_scores([[0, 6], [6, 0], [6, 0]]) }
-
1
context 'singles' do
-
2
let(:match) { FactoryGirl.build(:singles_match, scoring: :three_six_game) }
-
-
1
it 'should have winner' do
-
1
expect(subject.team_winner).to_not be_nil
-
end
-
end
-
-
1
context 'doubles' do
-
2
let(:match) { FactoryGirl.build(:doubles_match, scoring: :three_six_game) }
-
-
1
it 'should have winner' do
-
1
expect(subject.team_winner).to_not be_nil
-
end
-
end
-
end
-
end
-
-
1
context 'invalid score' do
-
2
let(:scores) { MatchPlayer.convert_scores([[0, 7]]) }
-
2
let(:match) { FactoryGirl.build(:singles_match, scoring: :three_six_game) }
-
-
1
it 'should raise error' do
-
2
expect{MatchPlayer.play_match(match, scores)}.to raise_error StandardError
-
end
-
end
-
-
end
-
-
-
1
require 'rails_helper'
-
-
# Matches and shared examples for match_play specs
-
-
1
module MatchPlayShared
-
-
1
RSpec::Matchers.define :permit_action do |action|
-
8
match do |m|
-
8
m.play_match?(action)
-
end
-
-
8
description do
-
8
"permit #{action} action"
-
end
-
-
8
failure_message do
-
"expect to permit action #{action}"
-
end
-
-
8
failure_message_when_negated do
-
"expect to not permit action #{action}"
-
end
-
end
-
-
1
RSpec::Matchers.define :have_last_set_in_progress do
-
3
match do |m|
-
3
m.last_set.team_winner.nil? if m.last_set
-
end
-
-
3
failure_message do
-
'expect match to have set in progress'
-
end
-
-
3
failure_message_when_negated do
-
'do not expect match to have set in progress'
-
end
-
end
-
-
1
RSpec::Matchers.define :be_complete do
-
1
match do |m|
-
1
m.state == :complete
-
end
-
-
1
failure_message do
-
'expect to be complete'
-
end
-
-
1
failure_message_when_negated do
-
'do not expect to be complete'
-
end
-
end
-
-
1
RSpec::Matchers.define :permit_win_game do
-
3
match do |m|
-
3
m.play_match?(:win_game) || m.play_match?(:win_tiebreak)
-
end
-
-
3
description do
-
3
'permit win_game or win_tiebreak actions'
-
end
-
-
3
failure_message do
-
'expect to permit win_game or win_tiebreak actions'
-
end
-
-
3
failure_message_when_negated do
-
'do not expect to permit win_game or win_tiebreak actions'
-
end
-
end
-
-
1
RSpec::Matchers.define :have_game_count do |count|
-
4
match do |m|
-
4
m.match_sets.count == 1 && m.match_sets[0].set_games.count == count
-
end
-
-
4
description do
-
4
"have set with #{count} #{count > 1 ? 'games' : 'game'}"
-
end
-
-
4
failure_message do
-
"expect to have set with #{count} game(s)"
-
end
-
-
4
failure_message_when_negated do
-
"do not expect to set with have #{count} games(s)"
-
end
-
end
-
-
1
RSpec::Matchers.define :have_set_size do |size|
-
3
match do |m|
-
3
@actual = nil
-
3
sets = m.match_sets.reject(&:tiebreak?)
-
3
sets.each do |set|
-
6
unless size == set.win_threshold
-
@actual = set.win_threshold
-
end
-
end
-
3
sets.empty? || @actual ? false : true
-
end
-
-
3
description do
-
3
"have #{size} game set"
-
end
-
-
3
failure_message do
-
"expect to have #{size} game set, but have #{@actual} game set"
-
end
-
-
3
failure_message_when_negated do
-
"do not expect to have #{size} game set"
-
end
-
end
-
-
1
RSpec::Matchers.define :have_set_count do |count|
-
6
match do |m|
-
6
@actual = m.match_sets.reject(&:tiebreak?).count
-
6
@actual == count
-
end
-
-
6
description do
-
6
"have #{count} #{count > 1 ? 'sets' : 'set'}"
-
end
-
-
6
failure_message do
-
"expect to have #{count} set(s), but have #{@actual}"
-
end
-
-
6
failure_message_when_negated do
-
"do not expect to have #{count} set(s)"
-
end
-
end
-
-
1
RSpec::Matchers.define :have_match_tiebreak do
-
1
match do |m|
-
1
m.last_set.tiebreak?
-
end
-
-
1
failure_message do
-
'expect to have match tiebreak'
-
end
-
-
1
failure_message_when_negated do
-
'do not expect to have match tiebreak'
-
end
-
end
-
-
1
RSpec::Matchers.define :have_changes do
-
3
match do |m|
-
3
actions = [:discard_play, :remove_last_change]
-
3
actions.each do |action|
-
3
return false unless m.play_match? action
-
end
-
true
-
end
-
-
3
failure_message do
-
'expect to have changes'
-
end
-
-
3
failure_message_when_negated do
-
'do not expect to have changes'
-
end
-
end
-
-
1
RSpec::Matchers.define :have_complete_set do
-
2
match do |m|
-
2
last_set = m.last_set
-
2
last_set && last_set.state == :complete
-
end
-
-
2
failure_message do
-
'expect to have complete set'
-
end
-
-
2
failure_message_when_negated do
-
'do not expect to have complete set'
-
end
-
end
-
-
1
RSpec::Matchers.define :have_game_in_progress do
-
8
match do |m|
-
8
last_game = m.last_set.last_game if m.last_set
-
8
last_game.team_winner.nil? && last_game.player_server if last_game
-
end
-
-
8
failure_message do
-
'expect to have game in progress'
-
end
-
-
8
failure_message_when_negated do
-
'do not expect to have game in progress'
-
end
-
end
-
-
1
RSpec::Matchers.define :have_game_with_winner do
-
2
match do |m|
-
2
last_game = m.last_set.last_game if m.last_set
-
2
last_game.team_winner if last_game
-
end
-
-
2
failure_message do
-
'expect to have game with server'
-
end
-
-
2
failure_message_when_negated do
-
'do not expect to have game with server'
-
end
-
end
-
-
1
RSpec::Matchers.define :be_serving do |player_ordinal, doubles|
-
22
match do |m|
-
22
@expected = nil
-
22
@actual = nil
-
22
@last_game = m.last_set.last_game if m.last_set
-
22
if @last_game && @last_game.team_winner.nil?
-
22
players = [
-
m.doubles ? m.first_team.first_player : m.first_player,
-
m.doubles ? m.first_team.second_player : m.second_player,
-
m.doubles ? m.second_team.first_player : nil,
-
m.doubles ? m.second_team.second_player : nil
-
]
-
22
@expected = players[player_ordinal]
-
22
@actual = @last_game.player_server
-
22
@actual && @actual == @expected
-
else
-
false
-
end
-
end
-
-
22
description do
-
22
player = case player_ordinal
-
when 0
-
9
doubles ? 'first_team.first_player' : 'first_player'
-
when 1
-
8
doubles ? 'first_team.second_player' : 'second_player'
-
when 2
-
3
'second_team.first_player'
-
when 3
-
2
'second_team.second_player'
-
end
-
22
"#{player} be serving"
-
end
-
-
22
failure_message do
-
if @actual && @expected
-
"expected #{@expected.name} to be serving, but #{@actual.name} is serving"
-
elsif @last_game.nil?
-
'expected game in progress'
-
else
-
'expected server'
-
end
-
end
-
-
22
failure_message_when_negated do
-
if @actual
-
"Did not expect #{@actual.name} to be serving"
-
else
-
'Did not expect server'
-
end
-
end
-
end
-
-
1
RSpec.shared_examples 'a game not started' do
-
6
it { is_expected.not_to permit_win_game }
-
end
-
-
1
RSpec.shared_examples 'a match not started' do
-
6
it { is_expected.not_to have_changes }
-
end
-
-
1
RSpec.shared_examples 'a match just started' do
-
3
it_behaves_like 'a game not started'
-
3
it_behaves_like 'a match set in progress'
-
end
-
-
1
RSpec.shared_examples 'a match set in progress' do
-
6
it { is_expected.to have_last_set_in_progress }
-
end
-
-
1
RSpec.shared_examples 'a match with game in progress' do
-
16
it { is_expected.to have_game_in_progress }
-
end
-
-
1
RSpec.shared_examples 'a match with one game' do
-
6
it { is_expected.to have_game_count(1) }
-
end
-
-
1
RSpec.shared_examples 'a match with two games' do
-
2
it { is_expected.to have_game_count(2) }
-
end
-
-
1
RSpec.shared_examples 'a match with game won' do
-
4
it { is_expected.to have_game_with_winner }
-
end
-
-
1
RSpec.shared_examples 'a match can start set tiebreak' do
-
4
it { is_expected.to permit_action :start_tiebreak }
-
end
-
-
1
RSpec.shared_examples 'a match in set tiebreak' do
-
4
it { is_expected.to permit_action :win_tiebreak }
-
end
-
-
1
RSpec.shared_examples 'a match can start match tiebreak' do
-
4
it { is_expected.to permit_action :start_match_tiebreak }
-
end
-
-
1
RSpec.shared_examples 'a match in match tiebreak' do
-
4
it { is_expected.to permit_action :win_match_tiebreak }
-
end
-
-
1
RSpec.shared_examples 'a match with complete set' do
-
4
it { is_expected.to have_complete_set }
-
end
-
-
1
RSpec.shared_examples 'a match complete' do
-
2
it { is_expected.to be_complete }
-
end
-
-
1
RSpec.shared_examples 'a match with first game started' do
-
3
it_behaves_like 'a match with game in progress'
-
3
it_behaves_like 'a match with one game'
-
end
-
-
1
RSpec.shared_examples 'a match with second game started' do
-
1
it_behaves_like 'a match with game in progress'
-
1
it_behaves_like 'a match with two games'
-
end
-
-
1
RSpec.shared_examples 'a match with first player serving' do
-
18
it { is_expected.to be_serving(0, subject.doubles) }
-
end
-
-
1
RSpec.shared_examples 'a match with second player serving' do
-
16
it { is_expected.to be_serving(1, subject.doubles) }
-
end
-
-
1
RSpec.shared_examples 'a match with third player serving' do
-
6
it { is_expected.to be_serving(2, subject.doubles) }
-
end
-
-
1
RSpec.shared_examples 'a match with fourth player serving' do
-
4
it { is_expected.to be_serving(3, subject.doubles) }
-
end
-
-
1
RSpec.shared_examples 'a match with three sets' do
-
2
it { is_expected.to have_set_count(3) }
-
end
-
-
1
RSpec.shared_examples 'a match with two sets' do
-
4
it { is_expected.to have_set_count(2) }
-
end
-
-
1
RSpec.shared_examples 'a match with match tiebreak' do
-
2
it { is_expected.to have_match_tiebreak }
-
end
-
-
1
RSpec.shared_examples 'a match with one set' do
-
6
it { is_expected.to have_set_count(1) }
-
end
-
-
1
RSpec.shared_examples 'a match with six game sets' do
-
4
it { is_expected.to have_set_size(6) }
-
end
-
-
1
RSpec.shared_examples 'a match with eight game sets' do
-
2
it { is_expected.to have_set_size(8) }
-
end
-
-
1
def all_actions
-
[
-
1
:start_play,
-
:discard_play,
-
:start_set,
-
:start_game,
-
:start_tiebreak,
-
:remove_last_change,
-
:start_match_tiebreak,
-
:win_game,
-
:win_tiebreak,
-
:win_match_tiebreak
-
]
-
end
-
-
1
def first_actions
-
[
-
10
:start_play,
-
]
-
end
-
-
1
def win_actions
-
[
-
10
:win_game,
-
:win_tiebreak,
-
:win_match_tiebreak
-
]
-
end
-
-
end
-
-
1
RSpec.configure do |c|
-
1
c.extend MatchPlayShared, match_play_shared: true
-
end
-
-
-
-
1
require 'rails_helper'
-
1
require 'models/match_play_shared'
-
-
# Test start match, start set, win game, etc.
-
-
1
RSpec.describe 'Play', { type: :model, match_play_shared: true, play_actions: true } do
-
1
describe 'any match' do
-
10
subject { FactoryGirl.build(:singles_match) }
-
-
1
it_behaves_like 'a match not started'
-
-
1
it 'should detect unknown action' do
-
2
expect { subject.play_match? :xxx }.to raise_error Exceptions::UnknownOperation
-
end
-
-
1
it 'should respond with valid actions' do
-
1
expect(subject.valid_actions).to eq({ start_play: true })
-
end
-
-
1
context 'when attempt invalid actions' do
-
1
actions = all_actions.reject do |a|
-
10
first_actions.include?(a) or win_actions.include?(a)
-
end
-
1
actions.each do |action|
-
6
context "when #{action}" do
-
6
it 'should deny action' do
-
12
expect { subject.play_match! action }.to raise_error Exceptions::InvalidOperation
-
end
-
end
-
end
-
end
-
-
end
-
-
1
describe 'start match' do
-
9
subject { FactoryGirl.build(:play_singles_match, start_play: true) }
-
-
1
it_behaves_like 'a match just started'
-
-
1
context 'remove last change' do
-
2
before { subject.play_match! :remove_last_change }
-
1
it_behaves_like 'a match not started'
-
end
-
-
1
context 'discard play' do
-
2
before { subject.play_match! :discard_play }
-
1
it_behaves_like 'a match not started'
-
end
-
-
1
it 'should not start again' do
-
2
expect { subject.play_match! :start_play }.to raise_error Exceptions::InvalidOperation
-
end
-
-
1
context 'when win actions are denied' do
-
1
win_actions.each do |action|
-
-
3
context "when #{action}" do
-
3
it 'should deny action' do
-
6
expect { subject.play_match! action, opponent: subject.first_team }.to raise_error Exceptions::InvalidOperation
-
end
-
end
-
end
-
end
-
end
-
-
1
describe 'start game' do
-
1
context 'singles match' do
-
8
subject { FactoryGirl.build(:play_singles_match, start_play: true) }
-
-
1
context 'with first server' do
-
1
before do
-
4
subject.play_match! :start_game, opponent: subject.first_player
-
end
-
-
1
it_behaves_like 'a match with first game started'
-
-
1
context 'remove last change' do
-
3
before { subject.play_match! :remove_last_change }
-
1
it_behaves_like 'a match just started'
-
end
-
end
-
-
1
context 'with first server team' do
-
1
before do
-
2
subject.play_match! :start_game, opponent: subject.first_team
-
end
-
-
1
it_behaves_like 'a match with first game started'
-
-
end
-
-
1
context 'without server' do
-
1
it 'should not start' do
-
2
expect { subject.play_match! :start_game }.to raise_error Exceptions::InvalidOperation
-
end
-
end
-
-
end
-
-
1
context 'doubles match' do
-
12
subject { FactoryGirl.build(:play_doubles_match, start_play: true) }
-
-
1
context 'start first game with first server' do
-
1
before do
-
10
subject.play_match! :start_game, opponent: subject.first_team.first_player
-
end
-
-
1
it_behaves_like 'a match with first game started'
-
-
1
context 'remove last change' do
-
3
before { subject.play_match! :remove_last_change }
-
1
it_behaves_like 'a match just started'
-
end
-
-
1
context 'start second game' do
-
7
before { subject.play_match! :win_game, opponent: subject.first_team }
-
-
1
context 'start second game with second server' do
-
1
before do
-
3
subject.play_match! :start_game, opponent: subject.second_team.first_player
-
end
-
-
1
it_behaves_like 'a match with second game started'
-
-
1
context 'remove last change' do
-
1
before {
-
1
subject.play_match! :remove_last_change
-
1
subject.reload
-
}
-
1
it_behaves_like 'a match with game won'
-
end
-
end
-
-
1
context 'second game without server' do
-
1
it 'should not start' do
-
2
expect { subject.play_match! :start_game }.to raise_error Exceptions::InvalidOperation
-
end
-
end
-
-
1
context 'second game with invalid server' do
-
1
it 'should not allow same server' do
-
2
expect { subject.play_match! :start_game, opponent: subject.first_team.first_player }.to raise_error ActiveRecord::RecordInvalid
-
end
-
-
1
it 'should not allow server from same team' do
-
2
expect { subject.play_match! :start_game, opponent: subject.first_team.second_player }.to raise_error ActiveRecord::RecordInvalid
-
end
-
end
-
end
-
end
-
-
1
context 'first game without server' do
-
1
it 'should not start' do
-
2
expect { subject.play_match! :start_game }.to raise_error Exceptions::InvalidOperation
-
end
-
end
-
end
-
end
-
-
1
describe 'win game' do
-
5
subject { FactoryGirl.build(:play_singles_match, start_first_game: true) }
-
-
1
it_behaves_like 'a match with game in progress'
-
-
1
context 'with player' do
-
3
before { subject.play_match! :win_game, opponent: subject.first_team.first_player }
-
-
1
it_behaves_like 'a match with game won'
-
-
1
context 'remove last change' do
-
2
before { subject.play_match! :remove_last_change }
-
-
1
it_behaves_like 'a match with game in progress'
-
end
-
end
-
-
1
context 'without player' do
-
1
it 'should not win' do
-
2
expect { subject.play_match! :win_game }.to raise_error Exceptions::UnknownOperation
-
end
-
end
-
end
-
-
1
describe 'complete set' do
-
-
1
context 'single set match' do
-
1
subject do
-
2
FactoryGirl.build(:play_singles_match,
-
scoring: :two_six_game_ten_point, scores: [[6, 0], [0, 6]])
-
end
-
-
1
it_behaves_like 'a match with complete set'
-
-
1
context 'remove last change' do
-
1
before {
-
1
subject.play_match! :remove_last_change
-
}
-
1
it_behaves_like 'a match with game in progress'
-
end
-
-
end
-
end
-
-
1
describe 'set tiebreak' do
-
1
subject do
-
7
FactoryGirl.build(:play_singles_match, scoring: :one_eight_game,
-
scores: [[8, 8]])
-
end
-
-
1
it_behaves_like 'a match can start set tiebreak'
-
-
1
context 'start tiebreak' do
-
7
before { subject.play_match! :start_tiebreak }
-
-
1
it_behaves_like 'a match in set tiebreak'
-
-
1
context 'win tiebreak with player' do
-
4
before { subject.play_match! :win_tiebreak, opponent: subject.first_team }
-
-
1
it_behaves_like 'a match with complete set'
-
-
1
context 'remove last change' do
-
2
before { subject.play_match! :remove_last_change }
-
1
it_behaves_like 'a match in set tiebreak'
-
end
-
-
1
it 'should not win tiebreak again' do
-
1
expect {
-
1
subject.play_match! :win_tiebreak, opponent: subject.first_team
-
}.to raise_error Exceptions::InvalidOperation
-
end
-
end
-
-
1
context 'win tiebreak without player' do
-
1
it 'should not win' do
-
2
expect { subject.play_match! :win_tiebreak }.to raise_error Exceptions::UnknownOperation
-
end
-
end
-
-
1
context 'remove last change' do
-
1
before do
-
1
subject.play_match! :remove_last_change
-
1
subject.reload
-
end
-
1
it_behaves_like 'a match can start set tiebreak'
-
end
-
end
-
end
-
-
1
describe 'remove second set' do
-
1
subject do
-
3
FactoryGirl.build(:play_singles_match,
-
scoring: :two_six_game_ten_point, scores: [[6, 0]])
-
end
-
-
1
it_behaves_like 'a match with one set'
-
-
1
context 'start second set' do
-
3
before { subject.play_match! :start_set }
-
-
1
it_behaves_like 'a match with two sets'
-
-
1
context 'remove started set' do
-
1
before do
-
1
subject.play_match! :remove_last_change
-
1
subject.reload
-
end
-
-
1
it_behaves_like 'a match with one set'
-
end
-
end
-
end
-
-
1
describe 'match tiebreak' do
-
1
subject do
-
7
FactoryGirl.build(:play_singles_match,
-
scoring: :two_six_game_ten_point, scores: [[6, 0], [0, 6]])
-
end
-
-
1
it_behaves_like 'a match can start match tiebreak'
-
-
1
context 'start ' do
-
7
before { subject.play_match! :start_match_tiebreak }
-
-
1
it_behaves_like 'a match in match tiebreak'
-
-
1
context 'win with player' do
-
1
before do
-
3
subject.play_match! :win_match_tiebreak, opponent: subject.first_team
-
end
-
-
1
it_behaves_like 'a match complete'
-
-
1
context 'remove last change' do
-
2
before { subject.play_match! :remove_last_change }
-
1
it_behaves_like 'a match in match tiebreak'
-
end
-
-
1
it 'should not win again' do
-
1
expect {
-
1
subject.play_match! :win_match_tiebreak, opponent: subject.first_team
-
}.to raise_error Exceptions::InvalidOperation
-
end
-
end
-
-
1
context 'win without player' do
-
1
it 'should not win' do
-
2
expect { subject.play_match! :win_match_tiebreak }.to raise_error Exceptions::UnknownOperation
-
end
-
end
-
-
1
context 'remove last change' do
-
1
before do
-
1
subject.play_match! :remove_last_change
-
1
subject.reload # clears destroyed entities
-
end
-
1
it_behaves_like 'a match can start match tiebreak'
-
end
-
end
-
end
-
-
1
describe 'players serving first set' do
-
1
context 'singles' do
-
1
context 'alternate order' do # needed for complete code coverage
-
3
let(:winner) { subject.first_player.singles_team }
-
1
subject do
-
3
FactoryGirl.build(:play_singles_match, scoring: :three_six_game, start_play: true)
-
end
-
-
1
context 'first' do
-
1
before do
-
3
subject.play_match! :start_game, opponent: subject.second_player
-
end
-
1
it_behaves_like 'a match with second player serving'
-
-
1
context 'second' do
-
1
before do
-
2
subject.play_match! :win_game, opponent: winner
-
2
subject.play_match! :start_game
-
end
-
-
1
it_behaves_like 'a match with first player serving'
-
-
1
context 'third' do
-
1
before do
-
1
subject.play_match! :win_game, opponent: winner
-
1
subject.play_match! :start_game
-
end
-
-
1
it_behaves_like 'a match with second player serving'
-
end
-
end
-
end
-
end
-
-
1
context 'sequential order' do
-
3
let(:winner) { subject.first_player.singles_team }
-
1
subject do
-
3
FactoryGirl.build(:play_singles_match, scoring: :three_six_game, start_first_game: true)
-
end
-
-
1
context 'first' do
-
1
it_behaves_like 'a match with first player serving'
-
end
-
-
1
context 'second' do
-
1
before do
-
2
subject.play_match! :win_game, opponent: winner
-
2
subject.play_match! :start_game
-
end
-
-
1
it_behaves_like 'a match with second player serving'
-
-
1
context 'third' do
-
1
before do
-
1
subject.play_match! :win_game, opponent: winner
-
1
subject.play_match! :start_game
-
end
-
-
1
it_behaves_like 'a match with first player serving'
-
end
-
end
-
end
-
end
-
-
1
context 'doubles' do
-
1
context 'alternate order' do # needed for complete code coverage
-
5
let(:winner) { subject.first_team }
-
1
subject do
-
5
FactoryGirl.build(:play_doubles_match, scoring: :three_six_game, start_play: true)
-
end
-
-
1
context 'first' do
-
1
before do
-
5
subject.play_match! :start_game, opponent: subject.first_team.second_player
-
end
-
1
it_behaves_like 'a match with second player serving'
-
-
1
context 'second' do
-
1
before do
-
4
subject.play_match! :win_game, opponent: winner
-
4
subject.play_match! :start_game, opponent: subject.second_team.first_player
-
end
-
1
it_behaves_like 'a match with third player serving'
-
-
1
context 'third' do
-
1
before do
-
3
subject.play_match! :win_game, opponent: winner
-
3
subject.play_match! :start_game
-
end
-
1
it_behaves_like 'a match with first player serving'
-
-
1
context 'fourth' do
-
1
before do
-
2
subject.play_match! :win_game, opponent: winner
-
2
subject.play_match! :start_game
-
end
-
1
it_behaves_like 'a match with fourth player serving'
-
-
1
context 'fifth' do
-
1
before do
-
1
subject.play_match! :win_game, opponent: winner
-
1
subject.play_match! :start_game
-
end
-
1
it_behaves_like 'a match with second player serving'
-
end
-
end
-
end
-
end
-
end
-
end
-
-
1
context 'sequential order' do
-
5
let(:winner) { subject.first_team }
-
1
subject do
-
5
FactoryGirl.build(:play_doubles_match, scoring: :three_six_game, start_first_game: true)
-
end
-
-
1
context 'first' do
-
1
it_behaves_like 'a match with first player serving'
-
end
-
-
1
context 'second' do
-
1
before do
-
4
subject.play_match! :win_game, opponent: winner
-
4
subject.play_match! :start_game, opponent: subject.second_team.first_player
-
end
-
-
1
it_behaves_like 'a match with third player serving'
-
-
1
context 'third' do
-
1
before do
-
3
subject.play_match! :win_game, opponent: winner
-
3
subject.play_match! :start_game
-
end
-
-
1
it_behaves_like 'a match with second player serving'
-
-
1
context 'fourth' do
-
1
before {
-
2
subject.play_match! :win_game, opponent: winner
-
2
subject.play_match! :start_game
-
}
-
-
1
it_behaves_like 'a match with fourth player serving'
-
-
1
context 'fifth' do
-
1
before do
-
1
subject.play_match! :win_game, opponent: winner
-
1
subject.play_match! :start_game
-
end
-
-
1
it_behaves_like 'a match with first player serving'
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
-
1
describe 'players serving second set' do
-
1
context 'odd number of games' do
-
1
context 'singles' do
-
1
subject do
-
1
FactoryGirl.build(:play_singles_match, scoring: :three_six_game,
-
scores: [[6, 3]], start_set_game: true)
-
end
-
-
1
it_behaves_like 'a match with second player serving'
-
end
-
1
context 'doubles' do
-
1
subject do
-
1
FactoryGirl.build(:play_doubles_match, scoring: :three_six_game,
-
scores: [[6, 3]], start_set_game: true)
-
end
-
-
1
it_behaves_like 'a match with third player serving'
-
end
-
-
end
-
1
context 'even number of games' do
-
1
context 'singles' do
-
1
subject do
-
1
FactoryGirl.build(:play_singles_match, scoring: :three_six_game,
-
scores: [[7, 5]], start_set_game: true)
-
end
-
-
1
it_behaves_like 'a match with first player serving'
-
end
-
-
1
context 'doubles' do
-
1
subject do
-
1
FactoryGirl.build(:play_doubles_match, scoring: :three_six_game,
-
scores: [[6, 4]], start_set_game: true)
-
end
-
-
1
it_behaves_like 'a match with second player serving'
-
end
-
end
-
-
1
context 'tiebreak' do
-
1
context 'singles' do
-
1
subject do
-
1
FactoryGirl.build(:play_singles_match, scoring: :three_six_game,
-
scores: [[6, 7]], start_set_game: true)
-
end
-
-
1
it_behaves_like 'a match with first player serving'
-
end
-
-
1
context 'doubles' do
-
1
subject do
-
1
FactoryGirl.build(:play_doubles_match, scoring: :three_six_game,
-
scores: [[7, 6]], start_set_game: true)
-
end
-
-
1
it_behaves_like 'a match with first player serving'
-
end
-
end
-
end
-
-
1
describe 'match scoring' do
-
1
describe :one_eight_game do
-
1
subject do
-
2
FactoryGirl.build(:play_singles_match, scoring: :one_eight_game,
-
scores: [[8, 9]])
-
end
-
1
it_behaves_like 'a match with one set'
-
1
it_behaves_like 'a match with eight game sets'
-
end
-
-
1
describe :two_six_game_ten_point do
-
1
subject do
-
3
FactoryGirl.build(:play_singles_match, scoring: :two_six_game_ten_point,
-
scores: [[6, 3], [3, 6], [0, 1]])
-
end
-
1
it_behaves_like 'a match with two sets'
-
1
it_behaves_like 'a match with match tiebreak'
-
1
it_behaves_like 'a match with six game sets'
-
end
-
-
1
describe :three_six_game do
-
1
subject do
-
2
FactoryGirl.build(:play_singles_match, scoring: :three_six_game,
-
scores: [[6, 3], [3, 6], [7, 6]])
-
end
-
1
it_behaves_like 'a match with three sets'
-
1
it_behaves_like 'a match with six game sets'
-
end
-
end
-
-
1
describe 'match version' do
-
-
1
context 'initial value' do
-
2
subject { FactoryGirl.create(:play_singles_match) }
-
-
1
it 'should have nil version initially' do
-
1
expect(subject.play_version).to be_nil
-
end
-
end
-
-
1
context 'after match started' do
-
5
subject { FactoryGirl.build(:play_singles_match, start_play: true) }
-
-
1
it 'should have number' do
-
1
expect(subject.play_version).not_to be_nil
-
end
-
-
1
context 'when use valid version' do
-
2
before { subject.play_match! :start_game, { opponent: subject.first_player, version: subject.play_version } }
-
-
1
it_behaves_like 'a match with game in progress'
-
end
-
-
1
it 'should raise exception when version is behind' do
-
1
expect do
-
1
subject.play_match! :start_game, { opponent: subject.first_player, version: subject.play_version - 1 }
-
end.to raise_error Exceptions::InvalidOperation
-
end
-
-
1
it 'should raise exception when version is ahead' do
-
1
expect do
-
1
subject.play_match! :start_game, { opponent: subject.first_player, version: subject.play_version + 1 }
-
end.to raise_error Exceptions::InvalidOperation
-
end
-
-
end
-
-
end
-
-
1
describe '#compute_team_winner' do
-
1
context 'match complete' do
-
1
subject do
-
1
FactoryGirl.build(:play_singles_match, scoring: :one_eight_game,
-
scores: [[8, 0]])
-
end
-
-
1
it 'should have winner' do
-
1
expect(subject.compute_team_winner).to eql subject.team_winner
-
end
-
-
end
-
end
-
-
1
describe 'near winners' do
-
1
context 'singles' do
-
1
context 'one more set' do
-
1
context 'first player' do
-
1
subject do
-
2
FactoryGirl.build(:play_singles_match,
-
scoring: :two_six_game_ten_point, scores: [[6, 0]])
-
end
-
-
1
it 'should have first player near winner' do
-
1
expect(subject.near_team_winner? subject.first_team).to be_truthy
-
end
-
-
1
it 'should not have second player near winner' do
-
1
expect(subject.near_team_winner? subject.second_team).not_to be_truthy
-
end
-
end
-
-
1
context 'second player' do
-
1
subject do
-
2
FactoryGirl.build(:play_singles_match,
-
scoring: :two_six_game_ten_point, scores: [[0, 6]])
-
end
-
-
1
it 'should not have first player near winner' do
-
1
expect(subject.near_team_winner? subject.first_team).not_to be_truthy
-
end
-
-
1
it 'should have second player near winner' do
-
1
expect(subject.near_team_winner? subject.second_team).to be_truthy
-
end
-
end
-
end
-
-
1
context 'one more game' do
-
1
context 'first player' do
-
1
subject do
-
2
FactoryGirl.build(:play_singles_match,
-
scoring: :two_six_game_ten_point, scores: [[6, 0], [5, 0]])
-
end
-
-
1
it 'should have first player near winner' do
-
1
expect(subject.last_set.near_team_winner? subject.first_team).to be_truthy
-
end
-
-
1
it 'should not have second player near winner' do
-
1
expect(subject.last_set.near_team_winner? subject.second_team).not_to be_truthy
-
end
-
-
end
-
-
1
context 'second player' do
-
1
subject do
-
2
FactoryGirl.build(:play_singles_match,
-
scoring: :two_six_game_ten_point, scores: [[0, 6], [0, 5]])
-
end
-
-
1
it 'should not have first player near winner' do
-
1
expect(subject.last_set.near_team_winner? subject.first_team).not_to be_truthy
-
end
-
-
1
it 'should have second player near winner' do
-
1
expect(subject.last_set.near_team_winner? subject.second_team).to be_truthy
-
end
-
end
-
end
-
-
1
context 'set tiebreak' do
-
1
subject do
-
2
FactoryGirl.build(:play_singles_match,
-
scoring: :two_six_game_ten_point, scores: [[6, 6]])
-
end
-
-
1
it 'should have first player near tiebreak winner' do
-
1
expect(subject.last_set.near_team_winner? subject.first_team).to be_truthy
-
end
-
-
1
it 'should have second player near tiebreak winner' do
-
1
expect(subject.last_set.near_team_winner? subject.second_team).to be_truthy
-
end
-
-
end
-
-
1
context 'match tiebreak' do
-
1
subject do
-
4
FactoryGirl.build(:play_singles_match,
-
scoring: :two_six_game_ten_point, scores: [[6, 0], [0, 6], [0, 0]])
-
end
-
-
1
it 'should have first player near tiebreak winner' do
-
1
expect(subject.last_set.near_team_winner? subject.first_team).to be_truthy
-
end
-
-
1
it 'should have second player near tiebreak winner' do
-
1
expect(subject.last_set.near_team_winner? subject.second_team).to be_truthy
-
end
-
-
1
it 'should have first player near match winner' do
-
1
expect(subject.near_team_winner? subject.first_team).to be_truthy
-
end
-
-
1
it 'should have second player near match winner' do
-
1
expect(subject.near_team_winner? subject.second_team).to be_truthy
-
end
-
-
end
-
end
-
-
1
context 'doubles' do
-
1
context 'one more set' do
-
1
context 'first team needs one more set' do
-
1
subject do
-
2
FactoryGirl.build(:play_singles_match,
-
scoring: :two_six_game_ten_point, scores: [[6, 0]])
-
end
-
-
1
it 'should have first player near winner' do
-
1
expect(subject.near_team_winner? subject.first_team).to be_truthy
-
end
-
-
1
it 'should not have second player near winner' do
-
1
expect(subject.near_team_winner? subject.second_team).not_to be_truthy
-
end
-
-
end
-
-
1
context 'second player needs one more set' do
-
1
subject do
-
2
FactoryGirl.build(:play_singles_match,
-
scoring: :two_six_game_ten_point, scores: [[0, 6]])
-
end
-
-
1
it 'should not have first player near winner' do
-
1
expect(subject.near_team_winner? subject.first_team).not_to be_truthy
-
end
-
-
1
it 'should have second player near winner' do
-
1
expect(subject.near_team_winner? subject.second_team).to be_truthy
-
end
-
end
-
end
-
-
1
context 'one more game' do
-
1
context 'first team needs one more game' do
-
1
subject do
-
2
FactoryGirl.build(:play_singles_match,
-
scoring: :two_six_game_ten_point, scores: [[6, 0], [5, 0]])
-
end
-
-
1
it 'should have first team near winner' do
-
1
expect(subject.last_set.near_team_winner? subject.first_team).to be_truthy
-
end
-
-
1
it 'should not have second team near winner' do
-
1
expect(subject.last_set.near_team_winner? subject.second_team).not_to be_truthy
-
end
-
end
-
-
1
context 'second team needs one more game' do
-
1
subject do
-
2
FactoryGirl.build(:play_singles_match,
-
scoring: :two_six_game_ten_point, scores: [[0, 6], [0, 5]])
-
end
-
-
1
it 'should not have first team near winner' do
-
1
expect(subject.last_set.near_team_winner? subject.first_team).not_to be_truthy
-
end
-
-
1
it 'should have second team near winner' do
-
1
expect(subject.last_set.near_team_winner? subject.second_team).to be_truthy
-
end
-
end
-
end
-
end
-
end
-
end
-
-
# == Schema Information
-
#
-
# Table name: match_sets
-
#
-
# id :integer not null, primary key
-
# match_id :integer not null
-
# ordinal :integer not null
-
# created_at :datetime not null
-
# updated_at :datetime not null
-
# scoring :string not null
-
# team_winner_id :integer
-
#
-
-
1
require 'rails_helper'
-
-
1
RSpec.describe MatchSet, { type: :model } do
-
10
subject { FactoryGirl.build(:match_set) }
-
-
2
it { is_expected.to respond_to(:ordinal) }
-
-
2
it { is_expected.to respond_to(:scoring) }
-
-
2
it { is_expected.to validate_presence_of(:ordinal) }
-
-
2
it { is_expected.to validate_presence_of(:scoring) }
-
-
2
it { is_expected.to validate_presence_of(:match) }
-
-
2
it { is_expected.to respond_to(:match) }
-
-
1
it 'should be valid initially' do
-
1
is_expected.to be_valid
-
end
-
-
1
it 'should validate scoring value' do
-
1
subject.scoring = 'abc'
-
1
is_expected.to_not be_valid
-
end
-
-
1
describe '#destroy!' do
-
1
before do
-
1
subject.save!
-
end
-
-
1
it 'should remove set' do
-
4
expect { subject.destroy! }.to change { MatchSet.count }.by(-1)
-
end
-
end
-
end
-
# == Schema Information
-
#
-
# Table name: matches
-
#
-
# id :integer not null, primary key
-
# created_at :datetime not null
-
# updated_at :datetime not null
-
# first_team_id :integer not null
-
# second_team_id :integer not null
-
# scoring :string not null
-
# started :boolean default(FALSE), not null
-
# doubles :boolean default(FALSE), not null
-
# first_player_server_id :integer
-
# second_player_server_id :integer
-
# title :string
-
# team_winner_id :integer
-
# play_version :integer
-
#
-
-
1
require 'rails_helper'
-
-
1
RSpec.describe Match, { type: :model } do
-
-
5
let(:player_name) {'player'}
-
2
let(:player2_name) {'player2'}
-
3
let(:team_name) {'team'}
-
2
let(:team2_name) {'team2'}
-
3
let(:new_player) { FactoryGirl.create(:player, name: player_name) }
-
3
let(:new_team) { FactoryGirl.create(:doubles_team, name: team_name, first_player_name: player_name) }
-
2
let(:new_team_with_same_player) { FactoryGirl.create(:doubles_team, name: team2_name, first_player_name: player2_name) }
-
-
1
shared_examples 'match' do
-
4
it { is_expected.to respond_to(:title) }
-
-
4
it { is_expected.to respond_to(:doubles) }
-
-
4
it { is_expected.to respond_to(:team_winner) }
-
-
4
it { is_expected.to respond_to(:scoring) }
-
-
4
it { is_expected.to validate_presence_of(:scoring) }
-
-
4
it { is_expected.to respond_to(:first_team) }
-
-
4
it { is_expected.to respond_to(:second_team) }
-
-
4
it { is_expected.to respond_to(:first_player) }
-
-
4
it { is_expected.to respond_to(:second_player) }
-
-
4
it { is_expected.to validate_uniqueness_of(:title).ignoring_case_sensitivity }
-
-
2
it 'should be valid initially' do
-
2
is_expected.to be_valid
-
end
-
-
2
it 'should validate scoring value' do
-
2
subject.scoring = 'abc'
-
2
is_expected.to_not be_valid
-
end
-
-
2
describe '#destroy!' do
-
4
before { subject.save! }
-
-
2
it 'should remove match' do
-
8
expect { subject.destroy! }.to change { Match.count }.by(-1)
-
end
-
end
-
-
2
it 'should generate a default title' do
-
2
subject.title = nil
-
2
subject.save!
-
2
expect(subject.title).to start_with('Match')
-
end
-
-
end
-
-
1
context 'doubles match' do
-
22
subject { FactoryGirl.build(:doubles_match) }
-
-
1
it_behaves_like 'match'
-
-
2
it { is_expected.to validate_presence_of(:first_team) }
-
-
2
it { is_expected.to validate_presence_of(:second_team) }
-
-
1
it 'should validate existence of #first_team' do
-
1
subject.first_team_id = 0
-
1
is_expected.not_to be_valid
-
end
-
-
1
it 'should validate existence of #second_team' do
-
1
subject.second_team_id = 0
-
1
is_expected.not_to be_valid
-
end
-
-
1
it 'should validate that teams can\'t be the same' do
-
1
subject.first_team = subject.second_team
-
1
is_expected.not_to be_valid
-
end
-
-
1
it 'should validate that servers can\'t be the same' do
-
1
subject.first_player_server = subject.first_team.first_player
-
1
subject.second_player_server = subject.first_player_server
-
1
is_expected.not_to be_valid
-
end
-
-
1
it 'should validate that there are four different players' do
-
1
subject.second_team = new_team_with_same_player
-
1
is_expected.not_to be_valid
-
end
-
-
end
-
-
1
context 'singles match' do
-
20
subject { FactoryGirl.build(:singles_match) }
-
-
1
it_behaves_like 'match'
-
-
2
it { is_expected.to validate_presence_of(:first_player) }
-
-
2
it { is_expected.to validate_presence_of(:second_player) }
-
-
1
it 'should validate that #first_team must exist' do
-
1
subject.first_team_id = 0
-
1
is_expected.not_to be_valid
-
end
-
-
1
it 'should validate that #second team must exist' do
-
1
subject.second_team_id = 0
-
1
is_expected.not_to be_valid
-
end
-
-
1
it 'should validate that players can\'t be the same' do
-
1
subject.first_player = subject.second_player
-
1
is_expected.not_to be_valid
-
end
-
end
-
-
1
context 'doubles to singles' do
-
2
subject { FactoryGirl.build(:doubles_match) }
-
-
1
it 'validates that players are missing' do
-
1
subject.doubles = false
-
1
is_expected.not_to be_valid
-
end
-
end
-
-
1
context 'singles to doubles' do
-
2
subject { FactoryGirl.build(:singles_match) }
-
-
1
it 'validates that teams are missing' do
-
1
subject.doubles = true
-
1
is_expected.not_to be_valid
-
end
-
end
-
-
1
context 'match in progress' do
-
1
context 'singles' do
-
7
subject { FactoryGirl.build(:play_singles_match, start_play: true) }
-
-
1
it 'should validate cannot change #scoring' do
-
1
subject.scoring = :three_six_game
-
1
is_expected.to_not be_valid
-
end
-
-
1
it 'should validate cannot change #doubles' do
-
1
subject.doubles = true
-
1
is_expected.to_not be_valid
-
end
-
-
1
it 'should validate cannot change #second_player' do
-
1
subject.second_player = new_player
-
1
is_expected.to_not be_valid
-
end
-
-
1
it 'should validate cannot change #first_player' do
-
1
subject.first_player = new_player
-
1
is_expected.to_not be_valid
-
end
-
-
1
it 'should validate can change server' do
-
1
subject.first_player_server = subject.second_player
-
1
is_expected.to be_valid
-
end
-
-
1
context 'start game' do
-
1
before do
-
1
subject.play_match! :start_game, opponent: subject.first_player
-
1
subject.play_match! :win_game, opponent: subject.first_player.singles_team
-
end
-
-
1
it 'should validate cannot change server' do
-
1
subject.first_player_server = subject.second_player
-
1
is_expected.not_to be_valid
-
end
-
end
-
end
-
-
1
context 'doubles' do
-
8
subject { FactoryGirl.build(:play_doubles_match, start_play: true) }
-
-
1
it 'should validate cannot change #first_team' do
-
1
subject.first_team_id = new_team.id
-
1
is_expected.to_not be_valid
-
end
-
-
1
it 'should validate cannot change #second_team' do
-
1
subject.second_team = new_team
-
1
is_expected.to_not be_valid
-
end
-
-
1
it 'should validate cannot change #scoring' do
-
1
subject.scoring = :three_six_game
-
1
is_expected.to_not be_valid
-
end
-
-
1
it 'should validate can change first server' do
-
1
subject.first_player_server = subject.first_team.first_player
-
1
is_expected.to be_valid
-
end
-
-
1
context 'win first game' do
-
1
before do
-
3
subject.play_match! :start_game, opponent: subject.first_team.first_player
-
3
subject.play_match! :win_game, opponent: subject.first_team
-
end
-
-
1
it 'should validate cannot change first server' do
-
1
subject.first_player_server = subject.first_team.second_player
-
1
is_expected.not_to be_valid
-
end
-
-
1
it 'should validate can change second server' do
-
1
subject.second_player_server = subject.second_team.second_player
-
1
is_expected.to be_valid
-
end
-
-
1
context 'start second game' do
-
1
before do
-
1
subject.play_match! :start_game, opponent: subject.second_team.first_player
-
1
subject.play_match! :win_game, opponent: subject.first_team
-
end
-
-
1
it 'should validate cannot change second server' do
-
1
subject.second_player_server = subject.second_team.second_player
-
1
is_expected.not_to be_valid
-
end
-
end
-
end
-
end
-
end
-
end
-
# == Schema Information
-
#
-
# Table name: players
-
#
-
# id :integer not null, primary key
-
# name :string not null
-
# created_at :datetime not null
-
# updated_at :datetime not null
-
#
-
-
1
require 'rails_helper'
-
-
1
RSpec.describe Player, { type: :model } do
-
9
subject { FactoryGirl.build :player }
-
-
2
it { is_expected.to respond_to(:name) }
-
-
2
it { is_expected.to validate_presence_of(:name) }
-
-
2
it { is_expected.to validate_uniqueness_of(:name).ignoring_case_sensitivity }
-
-
1
describe '#singles_team' do
-
3
before { subject.singles_team! }
-
-
1
it 'should create singles team' do
-
1
expect(subject.singles_team).not_to be_nil
-
end
-
-
1
it 'should destroy singles team' do
-
4
expect { subject.destroy! }.to change { Team.count }.by(-1)
-
end
-
end
-
-
1
describe '#destroy!' do
-
4
before { subject.save! }
-
-
1
it 'should remove player' do
-
4
expect { subject.destroy! }.to change { Player.count }.by(-1)
-
end
-
-
1
context 'when referenced by team' do
-
1
before do
-
1
@team = FactoryGirl.build(:doubles_team, first_player_name: subject.name)
-
1
@team.save!
-
end
-
-
1
it 'should not destroy player' do
-
2
expect { subject.destroy! }.to raise_error(ActiveRecord::RecordNotDestroyed)
-
end
-
end
-
-
1
context 'when referenced by match' do
-
1
before do
-
1
@match = FactoryGirl.build(:singles_match, first_player_name: subject.name)
-
1
@match.save!
-
end
-
-
1
it 'should not destroy player' do
-
2
expect { subject.destroy! }.to raise_error(ActiveRecord::RecordNotDestroyed)
-
end
-
end
-
end
-
end
-
# == Schema Information
-
#
-
# Table name: set_games
-
#
-
# id :integer not null, primary key
-
# ordinal :integer not null
-
# match_set_id :integer not null
-
# team_winner_id :integer
-
# created_at :datetime not null
-
# updated_at :datetime not null
-
# player_server_id :integer
-
# tiebreaker :boolean default(FALSE), not null
-
#
-
-
1
require 'rails_helper'
-
-
1
RSpec.describe SetGame, { type: :model } do
-
9
subject { FactoryGirl.build(:set_game) }
-
-
2
it { is_expected.to respond_to(:ordinal) }
-
-
2
it { is_expected.to respond_to(:match_set) }
-
-
2
it { is_expected.to respond_to(:team_winner) }
-
-
2
it { is_expected.to respond_to(:tiebreaker) }
-
-
2
it { is_expected.to respond_to(:player_server) }
-
-
2
it { is_expected.to validate_presence_of(:ordinal) }
-
-
2
it { is_expected.to validate_presence_of(:match_set) }
-
-
1
describe '#destroy!' do
-
2
before { subject.save! }
-
-
1
it 'should remove set' do
-
4
expect { subject.destroy! }.to change { SetGame.count }.by(-1)
-
end
-
end
-
end
-
# == Schema Information
-
#
-
# Table name: teams
-
#
-
# id :integer not null, primary key
-
# name :string
-
# first_player_id :integer not null
-
# second_player_id :integer
-
# created_at :datetime not null
-
# updated_at :datetime not null
-
# doubles :boolean default(FALSE), not null
-
#
-
-
1
require 'rails_helper'
-
-
1
RSpec.describe Team, { type: :model } do
-
20
subject { FactoryGirl.build(:doubles_team) }
-
-
2
it { is_expected.to respond_to(:name) }
-
-
2
it { is_expected.to respond_to(:first_player) }
-
-
2
it { is_expected.to respond_to(:second_player) }
-
-
2
it { is_expected.to respond_to(:doubles) }
-
-
2
it { is_expected.to validate_uniqueness_of(:name).ignoring_case_sensitivity }
-
-
2
it { is_expected.to validate_presence_of(:first_player) }
-
-
1
it 'should be valid initially' do
-
1
is_expected.to be_valid
-
end
-
-
1
it 'should validate presence of #second_player' do
-
1
subject.second_player = nil
-
1
is_expected.to_not be_valid
-
end
-
-
1
it 'should validate existence of #first_player' do
-
1
subject.first_player_id = 0
-
1
is_expected.not_to be_valid
-
end
-
-
1
it 'should validate existence of #second_player' do
-
1
subject.second_player_id = 0
-
1
is_expected.not_to be_valid
-
end
-
-
1
it 'should validate to have two different players' do
-
1
subject.second_player = subject.first_player
-
1
is_expected.to_not be_valid
-
end
-
-
1
context 'when already have a team with players' do
-
1
it 'should validate duplicate players in same order' do
-
1
FactoryGirl.build(:doubles_team, name: 'other team').save!
-
1
is_expected.to_not be_valid
-
end
-
-
1
it 'should validate duplicate players in reverse order' do
-
FactoryGirl.build(:doubles_team, first_player_name: subject.second_player.name,
-
1
second_player_name: subject.first_player.name).save!
-
1
is_expected.to_not be_valid
-
end
-
end
-
-
1
context 'when in a match' do
-
5
let(:player_name) { 'all new player' }
-
5
let(:new_player) { FactoryGirl.create(:player, name: player_name) }
-
5
let(:match) { FactoryGirl.build(:doubles_match, first_team_name: subject.name) }
-
1
before do
-
4
subject.save!
-
4
match.save!
-
end
-
-
1
context 'when started' do
-
1
before do
-
2
match.play_match! :start_play
-
end
-
1
it 'should validate cannot change #second_player' do
-
1
subject.second_player = new_player
-
1
is_expected.to_not be_valid
-
end
-
-
1
it 'should validate cannot change #first_player' do
-
1
subject.first_player = new_player
-
1
is_expected.to_not be_valid
-
end
-
end
-
-
1
context 'when not started' do
-
1
it 'should validate can change #second_player' do
-
1
subject.second_player = new_player
-
1
is_expected.to be_valid
-
end
-
-
1
it 'should validate can change #first_player' do
-
1
subject.first_player = new_player
-
1
is_expected.to be_valid
-
end
-
end
-
end
-
-
1
describe '#destroy!' do
-
3
before { subject.save! }
-
-
1
it 'should remove team' do
-
4
expect { subject.destroy! }.to change { Team.count }.by(-1)
-
end
-
-
1
context 'when referenced by match' do
-
1
before do
-
1
FactoryGirl.build(:doubles_match, first_team_name: subject.name).save!
-
end
-
-
1
it 'should not remove team' do
-
2
expect { subject.destroy! }.to raise_error(ActiveRecord::RecordNotDestroyed)
-
end
-
end
-
end
-
end
-
# == Schema Information
-
#
-
# Table name: users
-
#
-
# id :integer not null, primary key
-
# username :string default(""), not null
-
# encrypted_password :string default(""), not null
-
# reset_password_token :string
-
# reset_password_sent_at :datetime
-
# remember_created_at :datetime
-
# sign_in_count :integer default(0), not null
-
# current_sign_in_at :datetime
-
# last_sign_in_at :datetime
-
# current_sign_in_ip :string
-
# last_sign_in_ip :string
-
# created_at :datetime
-
# updated_at :datetime
-
# auth_token :string default("")
-
#
-
-
1
require 'rails_helper'
-
-
1
RSpec.describe User, { type: :model } do
-
10
subject { FactoryGirl.build(:user) }
-
-
2
it { is_expected.to respond_to(:username) }
-
-
2
it { is_expected.to respond_to(:password) }
-
-
2
it { is_expected.to respond_to(:password_confirmation) }
-
-
2
it { is_expected.to respond_to(:auth_token) }
-
-
2
it { is_expected.to validate_presence_of(:username) }
-
-
2
it { is_expected.to validate_uniqueness_of(:username).ignoring_case_sensitivity }
-
-
2
it { is_expected.to validate_uniqueness_of(:auth_token).ignoring_case_sensitivity }
-
-
1
describe '#generate_authentication_token!' do
-
1
ATOKEN = 'auniquetoken'
-
-
1
it 'should generate a unique token' do
-
1
allow(Devise).to receive(:friendly_token).and_return(ATOKEN)
-
1
subject.generate_authentication_token!
-
1
expect(subject.auth_token).to eql ATOKEN
-
end
-
-
1
it 'should generate another token when duplicate' do
-
1
existing_user = FactoryGirl.create(:user, auth_token: ATOKEN)
-
1
subject.generate_authentication_token!
-
1
expect(subject.auth_token).not_to eql existing_user.auth_token
-
end
-
end
-
end
-
# This file is copied to spec/ when you run 'rails generate rspec:install'
-
1
ENV['RAILS_ENV'] ||= 'test'
-
1
require File.expand_path('../../config/environment', __FILE__)
-
# Prevent database truncation if the environment is production
-
1
abort("The Rails environment is running in production mode!") if Rails.env.production?
-
1
require 'spec_helper'
-
1
require 'rspec/rails'
-
# Add additional requires below this line. Rails is not loaded until this point!
-
-
# Requires supporting ruby files with custom matchers and macros, etc, in
-
# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
-
# run as spec files by default. This means that files in spec/support that end
-
# in _spec.rb will both be required and run as specs, causing the specs to be
-
# run twice. It is recommended that you do not name files matching this glob to
-
# end with _spec.rb. You can configure this pattern with the --pattern
-
# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
-
#
-
# The following line is provided for convenience purposes. It has the downside
-
# of increasing the boot-up time by auto-requiring all files in the support
-
# directory. Alternatively, in the individual `*_spec.rb` files, manually
-
# require only the support files necessary.
-
#
-
2
Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
-
-
# Checks for pending migration and applies them before tests are run.
-
# If you are not using ActiveRecord, you can remove this line.
-
1
ActiveRecord::Migration.maintain_test_schema!
-
-
1
RSpec.configure do |config|
-
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
-
1
config.fixture_path = "#{::Rails.root}/spec/fixtures"
-
-
# If you're not using ActiveRecord, or you'd prefer not to run each of your
-
# examples within a transaction, remove the following line or assign false
-
# instead of true.
-
1
config.use_transactional_fixtures = true
-
-
# RSpec Rails can automatically mix in different behaviours to your tests
-
# based on their file location, for example enabling you to call `get` and
-
# `post` in specs under `spec/controllers`.
-
#
-
# You can disable this behaviour by removing the line below, and instead
-
# explicitly tag your specs with their type, e.g.:
-
#
-
# RSpec.describe UsersController, :type => :controller do
-
# # ...
-
# end
-
#
-
# The different available types are documented in the features, such as in
-
# https://relishapp.com/rspec/rspec-rails/docs
-
1
config.infer_spec_type_from_file_location!
-
-
# Filter lines from Rails gems in backtraces.
-
1
config.filter_rails_from_backtrace!
-
# arbitrary gems may also be filtered via:
-
# config.filter_gems_from_backtrace("gem name")
-
-
-
# This will pick up all of the fixtures defined in spec/fixtures into your
-
# database and you’ll be able to test with some sample data
-
# (eg. Countries, States, etc.)
-
1
config.global_fixtures = :all
-
-
#Including to test requests
-
1
config.include Request::JsonHelpers, type: :controller
-
1
config.include Request::HeadersHelpers, type: :controller
-
1
config.include Devise::TestHelpers, type: :controller
-
-
1
config.raise_errors_for_deprecations!
-
-
1
config.before(:each, type: :controller) do
-
183
include_default_accept_headers
-
end
-
end
-
-
-
1
Shoulda::Matchers.configure do |config|
-
1
config.integrate do |with|
-
# Choose a test framework:
-
1
with.test_framework :rspec
-
1
with.library :rails
-
end
-
end
-
-
-
1
require 'rails_helper'
-
1
require 'serializers/v1/match_serializer_shared'
-
-
1
RSpec.describe V1::MatchScoreboardSerializer, { type: :serializer, match_serializer_shared: true } do
-
-
1
def scoreboard_json(match)
-
24
serializer = V1::MatchScoreboardSerializer.new(match)
-
24
JSON.parse(serializer.to_json, symbolize_names: true)
-
end
-
-
1
describe 'empty match' do
-
1
context 'doubles' do
-
5
let(:match) { FactoryGirl.create :doubles_match }
-
-
1
subject do
-
4
scoreboard_json(match)
-
end
-
-
1
it_behaves_like 'a doubles scoreboard'
-
-
2
it { is_expected.to eql_match(match) }
-
end
-
-
1
context 'singles' do
-
5
let(:match) { FactoryGirl.create :singles_match }
-
-
1
subject do
-
4
scoreboard_json(match)
-
end
-
-
1
it_behaves_like 'a singles scoreboard'
-
-
2
it { is_expected.to eql_match(match) }
-
end
-
end
-
-
1
describe 'complete match' do
-
1
context 'doubles' do
-
5
let(:match) { FactoryGirl.create :play_doubles_match,
-
scoring: :two_six_game_ten_point,
-
scores: [[7, 6],[4, 6],[1, 0]]}
-
-
1
subject do
-
4
scoreboard_json(match)
-
end
-
-
1
it_behaves_like 'a doubles scoreboard'
-
-
2
it { is_expected.to eql_match(match) }
-
end
-
-
1
context 'singles' do
-
5
let(:match) { FactoryGirl.create :play_singles_match,
-
scoring: :two_six_game_ten_point,
-
scores: [[7, 6], [4, 6], [1, 0]]}
-
-
1
subject do
-
4
scoreboard_json(match)
-
end
-
-
1
it_behaves_like 'a singles scoreboard'
-
-
2
it { is_expected.to eql_match(match) }
-
end
-
end
-
-
1
describe 'near winners' do
-
1
context 'doubles' do
-
5
let(:match) { FactoryGirl.create :play_doubles_match,
-
scoring: :two_six_game_ten_point,
-
scores: [[7, 6],[4, 6], [0, 0]]}
-
-
1
subject do
-
4
scoreboard_json(match)
-
end
-
-
1
it_behaves_like 'a doubles scoreboard'
-
-
2
it { is_expected.to eql_match(match) }
-
end
-
-
1
context 'singles' do
-
5
let(:match) { FactoryGirl.create :play_singles_match,
-
scoring: :two_six_game_ten_point,
-
scores: [[7, 6], [4, 6], [0, 0]]}
-
-
1
subject do
-
4
scoreboard_json(match)
-
end
-
-
1
it_behaves_like 'a singles scoreboard'
-
-
2
it { is_expected.to eql_match(match) }
-
end
-
end
-
-
-
-
end
-
-
-
1
require 'rails_helper'
-
-
# Shared examples for testing match serialization
-
-
1
module V1::MatchSerializerShared
-
-
1
RSpec.shared_examples 'a match' do
-
20
it { is_expected.to include :id, :title, :doubles, :state, :scoring, :winner }
-
end
-
-
1
RSpec.shared_examples 'a doubles match' do
-
5
it_behaves_like 'a match'
-
-
5
it 'should be doubles' do
-
5
expect(subject[:doubles]).to eql(true)
-
end
-
end
-
-
1
RSpec.shared_examples 'a singles match' do
-
5
it_behaves_like 'a match'
-
-
5
it 'should be singles' do
-
5
expect(subject[:doubles]).to eql(false)
-
end
-
end
-
-
1
RSpec.shared_examples 'a scoreboard' do
-
12
it { is_expected.to include :sets, :actions, :errors, :servers, :version, :near_winners }
-
end
-
-
1
RSpec.shared_examples 'a doubles scoreboard' do
-
3
it_behaves_like 'a doubles match'
-
3
it_behaves_like 'a scoreboard'
-
end
-
-
1
RSpec.shared_examples 'a singles scoreboard' do
-
3
it_behaves_like 'a singles match'
-
3
it_behaves_like 'a scoreboard'
-
end
-
-
1
RSpec::Matchers.define :include do |member|
-
48
match do |json|
-
48
json.include? member
-
end
-
-
48
failure_message do
-
"expect to include #{member}"
-
end
-
-
48
failure_message_when_negated do
-
"do not expect to include #{member}"
-
end
-
end
-
-
1
RSpec::Matchers.define :eql_match do |match|
-
10
match do |json|
-
10
@helper = MatchEql.new(json, match)
-
-
10
h = @helper
-
10
h.eql_member :id
-
10
h.eql_member :doubles
-
10
h.eql_member_to_s :scoring
-
10
h.eql_member_to_s :state
-
10
h.eql_member_to_s :title
-
10
if match.doubles
-
5
h.eql_team :first_team
-
5
h.eql_team :second_team
-
5
h.record_result :winner, json[:winner].nil? || json[:winner] == match.team_winner.id
-
else
-
5
h.eql_team :first_player
-
5
h.eql_team :second_player
-
end
-
10
!h.failures?
-
end
-
-
10
description do
-
10
'json response have values from match'
-
end
-
-
10
failure_message do
-
@helper.failure_message
-
end
-
-
10
failure_message_when_negated do
-
'do not expect to match json'
-
end
-
end
-
-
1
class MatchEql
-
-
1
def initialize(json, match)
-
10
@not_equal = []
-
10
@json = json
-
10
@match = match
-
end
-
-
1
attr_reader :not_equal
-
1
attr_reader :json
-
1
attr_reader :match
-
-
1
def eql_member(member)
-
20
record_result member, json[member] == match.send(member)
-
end
-
-
1
def eql_member_to_s(member)
-
30
record_result member, json[member] == match.send(member).to_s
-
end
-
-
1
def eql_team(opponent)
-
20
record_result "#{opponent}.id", json[opponent][:id] == match.send(opponent).id
-
20
record_result "#{opponent}.name", json[opponent][:name] == match.send(opponent).name
-
end
-
-
1
def record_result(member, result)
-
95
not_equal.push member unless result
-
end
-
-
1
def failure_message
-
"expect members to match: #{not_equal.to_s}"
-
end
-
-
1
def failures?
-
10
!not_equal.empty?
-
end
-
end
-
end
-
-
1
RSpec.configure do |c|
-
1
c.extend V1::MatchSerializerShared, match_serializer_shared: true
-
end
-
1
require 'rails_helper'
-
1
require 'serializers/v1/match_serializer_shared'
-
-
1
RSpec.describe V1::MatchSerializer, { type: :serializer, match_serializer_shared: true } do
-
-
1
def match_json(match)
-
12
serializer = V1::MatchSerializer.new(match)
-
12
JSON.parse(serializer.to_json, symbolize_names: true)
-
end
-
-
1
describe 'empty match' do
-
1
context 'doubles' do
-
4
let(:match) { FactoryGirl.create :doubles_match }
-
-
1
subject do
-
3
match_json(match)
-
end
-
-
1
it_behaves_like 'a doubles match'
-
-
2
it { is_expected.to eql_match(match) }
-
end
-
-
1
context 'singles' do
-
4
let(:match) { FactoryGirl.create :singles_match }
-
-
1
subject do
-
3
match_json(match)
-
end
-
-
1
it_behaves_like 'a singles match'
-
-
2
it { is_expected.to eql_match(match) }
-
end
-
end
-
-
1
describe 'complete match' do
-
1
context 'doubles' do
-
4
let(:match) { FactoryGirl.create :play_doubles_match,
-
scoring: :two_six_game_ten_point,
-
scores: [[7, 6],[4, 6],[1, 0]]}
-
-
1
subject do
-
3
match_json(match)
-
end
-
-
1
it_behaves_like 'a doubles match'
-
-
2
it { is_expected.to eql_match(match) }
-
end
-
-
1
context 'singles' do
-
4
let(:match) { FactoryGirl.create :play_singles_match,
-
scoring: :two_six_game_ten_point,
-
scores: [[7, 6], [4, 6], [1, 0]]}
-
-
1
subject do
-
3
match_json(match)
-
end
-
-
1
it_behaves_like 'a singles match'
-
-
2
it { is_expected.to eql_match(match) }
-
end
-
end
-
-
end
-
-
1
require 'rails_helper'
-
-
1
RSpec.describe V1::PlayerSerializer, { type: :serializer } do
-
3
let(:resource) { FactoryGirl.create :player }
-
3
let(:serializer) { V1::PlayerSerializer.new(resource) }
-
-
1
subject do
-
2
JSON.parse(serializer.to_json, symbolize_names: true)
-
end
-
-
1
it 'should have id' do
-
1
expect(subject[:id]).to eql(resource.id)
-
end
-
-
1
it 'should have name' do
-
1
expect(subject[:name]).to eql(resource.name)
-
end
-
end
-
1
require 'rails_helper'
-
-
1
RSpec.describe V1::SessionSerializer, { type: :serializer } do
-
4
let(:resource) { FactoryGirl.create :user }
-
4
let(:serializer) { V1::SessionSerializer.new(resource) }
-
-
1
subject do
-
3
JSON.parse(serializer.to_json, symbolize_names: true)
-
end
-
-
1
it 'should have id' do
-
1
expect(subject[:id]).to eql(resource.id)
-
end
-
-
1
it 'should have username' do
-
1
expect(subject[:username]).to eql(resource.username)
-
end
-
-
1
it 'should have token' do
-
1
is_expected.to include :auth_token
-
end
-
end
-
1
require 'rails_helper'
-
-
1
RSpec.describe V1::TeamSerializer, { type: :serializer } do
-
7
let(:resource) { FactoryGirl.create :doubles_team }
-
7
let(:serializer) { V1::TeamSerializer.new(resource) }
-
-
1
subject do
-
6
JSON.parse(serializer.to_json, symbolize_names: true)
-
end
-
-
1
it 'should have id' do
-
1
expect(subject[:id]).to eql(resource.id)
-
end
-
-
1
it 'should have name' do
-
1
expect(subject[:name]).to eql(resource.name)
-
end
-
-
1
it 'should have first player id' do
-
1
expect(subject[:first_player][:id]).to eql(resource.first_player.id)
-
end
-
-
1
it 'should have first player name' do
-
1
expect(subject[:first_player][:name]).to eql(resource.first_player.name)
-
end
-
-
1
it 'should have second player id' do
-
1
expect(subject[:second_player][:id]).to eql(resource.second_player.id)
-
end
-
-
1
it 'should have second player name' do
-
1
expect(subject[:second_player][:name]).to eql(resource.second_player.name)
-
end
-
end
-
1
require 'rails_helper'
-
-
1
RSpec.describe V1::UserSerializer, { type: :serializer } do
-
4
let(:resource) { FactoryGirl.create :user }
-
4
let(:serializer) { V1::UserSerializer.new(resource) }
-
-
1
subject do
-
3
JSON.parse(serializer.to_json, symbolize_names: true)
-
end
-
-
1
it 'should have id' do
-
1
expect(subject[:id]).to eql(resource.id)
-
end
-
-
1
it 'should have username' do
-
1
expect(subject[:username]).to eql(resource.username)
-
end
-
-
1
it 'should not have token' do
-
1
is_expected.not_to include :auth_token
-
end
-
end
-
1
module Request
-
1
module JsonHelpers
-
1
def json_response
-
95
@json_response ||= JSON.parse(response.body, symbolize_names: true)
-
end
-
end
-
-
1
module HeadersHelpers
-
1
def api_accept_header_version(version = 1)
-
2
api_accept_header "application/tennis.scoreboard.v#{version}"
-
end
-
-
1
def api_accept_header(value)
-
3
request.headers['Accept'] = value
-
end
-
-
1
def api_authorization_header(token)
-
113
request.headers['Authorization'] = token
-
end
-
-
1
def include_default_accept_headers
-
183
api_response_format
-
end
-
-
# private
-
1
def api_response_format(format = Mime::JSON)
-
183
request.headers['Content-Type'] = format.to_s
-
end
-
-
end
-
end