From f9488b183bbd41d74d801a9cbdd41410be4ddba5 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Tue, 29 Apr 2014 03:41:21 -0400 Subject: Validate everything that I can. --- app/models/game.rb | 40 ++++++++++++++++- app/models/game_setting.rb | 11 +++++ app/models/match.rb | 32 ++++++++++++-- app/models/tournament.rb | 85 ++++++++++++++++++++++++++++-------- app/models/tournament_setting.rb | 12 +++++ app/models/tournament_stage.rb | 10 +++++ app/models/user.rb | 94 ++++++++++++++++++++-------------------- 7 files changed, 215 insertions(+), 69 deletions(-) (limited to 'app') diff --git a/app/models/game.rb b/app/models/game.rb index c5cb32a..d5622af 100644 --- a/app/models/game.rb +++ b/app/models/game.rb @@ -1,7 +1,45 @@ class Game < ActiveRecord::Base belongs_to :parent, class_name: "Game" + has_many :children, class_name: "Game" - has_many :game_settings + has_many :game_settings + validates_associated :game_settings alias_attribute :settings, :game_settings + + validates(:name, + presence: true, + length: {minimum: 5}, + uniqueness: {case_sensitive: true}) + + validates(:min_players_per_team, + presence: true, + numericality: { + only_integer: true, + less_than_or_equal_to: :max_players_per_team, + }) + validates(:max_players_per_team, + presence: true, + numericality: { + only_integer: true, + greater_than_or_equal_to: :min_players_per_team, + }) + + validates(:min_teams_per_match, + presence: true, + numericality: { + only_integer: true, + less_than_or_equal_to: :max_teams_per_match, + }) + validates(:max_teams_per_match, + presence: true, + numericality: { + only_integer: true, + greater_than_or_equal_to: :min_teams_per_match, + }) + + validate :validate_scoring_method + def validate_scoring_method + (not self.scoring_method.try(:empty?)) and (Tournament.scoring_methods.include? scoring_method) + end end diff --git a/app/models/game_setting.rb b/app/models/game_setting.rb index e701cae..40ab32f 100644 --- a/app/models/game_setting.rb +++ b/app/models/game_setting.rb @@ -3,6 +3,17 @@ class GameSetting < ActiveRecord::Base alias_attribute :value, :default + validates(:vartype, presence: true, numericality: {only_integer: true}) + validates(:type_opt, presence: true, if: :needs_type_opt?) + + def needs_type_opt? + [ + GameSetting.types[:pick_one_radio], + GameSetting.types[:pick_one_dropdown], + GameSetting.types[:pick_several], + ].include? self.vartype + end + def self.types return { :text_short => 0, diff --git a/app/models/match.rb b/app/models/match.rb index 85084f5..7b36777 100644 --- a/app/models/match.rb +++ b/app/models/match.rb @@ -5,6 +5,18 @@ class Match < ActiveRecord::Base belongs_to :winner, class_name: "Team" + # status:integer + before_save { self.status ||= 0 } + + # tournament_stage:references + validates_presence_of :tournament_stage + + # winner:references + # not validated + + ## + # Returns whether or not all the statistics have been collected + # such that the match may be considered finished. def finished? ok = true tournament_stage.scoring.stats_needed.each do |stat| @@ -13,26 +25,35 @@ class Match < ActiveRecord::Base ok end - def win?(player) - winner.players.include? player - end - + ## + # Returns all players involved in this match (from all teams). def users ret = [] self.teams.each{|t| ret.concat(t.users)} return ret end + ## + # Given a sampling class (a class that implements the interface + # described in `/lib/sampling/README.md`), this returns which + # statistics (in an Array of Strings) an instance of the class + # should collect. def stats_from(sampling_class) figure_sampling_methods.map{|stat,klass| (sampling_class==klass) ? stat : nil}.select{|s| not s.nil?} end + ## + # Delagates PUT/PATCH HTTP params to the appropriate sampling + # methods. def handle_sampling(user, params) method_classes.each do |klass| klass.new(self).handle_user_interaction(user, params) end end + ## + # Delagates out rendering forms to the appropriate sampling + # methods. def render_sampling(user) require 'set' html = '' @@ -46,6 +67,9 @@ class Match < ActiveRecord::Base return html.html_safe end + ## + # Calls `Sampling#start` on every sampling method that this match + # uses. def start_sampling method_classes.each do |klass| klass.new(self).start diff --git a/app/models/tournament.rb b/app/models/tournament.rb index b867716..8a96dcc 100644 --- a/app/models/tournament.rb +++ b/app/models/tournament.rb @@ -1,15 +1,60 @@ class Tournament < ActiveRecord::Base belongs_to :game + has_many :tournament_stages + # Don't validate presence of stages; sadly, it seems to break things + #validates_presence_of :tournament_stages + alias_attribute :stages, :tournament_stages + has_many :brackets + has_many :tournament_settings + has_and_belongs_to_many :players, class_name: "User", association_foreign_key: "player_id", join_table: "players_tournaments" + has_and_belongs_to_many :hosts, class_name: "User", association_foreign_key: "host_id", join_table: "hosts_tournaments" + validates_presence_of :hosts - alias_attribute :stages, :tournament_stages + validates_presence_of :game before_save { self.status ||= 0 } + validates(:name, + presence: true, + length: {minimum: 5}, + uniqueness: {case_sensitive: true}) + + validates(:min_players_per_team, + presence: true, + numericality: { + only_integer: true, + less_than_or_equal_to: :max_players_per_team, + }) + validates(:max_players_per_team, + presence: true, + numericality: { + only_integer: true, + greater_than_or_equal_to: :min_players_per_team, + }) + + validates(:min_teams_per_match, + presence: true, + numericality: { + only_integer: true, + less_than_or_equal_to: :max_teams_per_match, + }) + validates(:max_teams_per_match, + presence: true, + numericality: { + only_integer: true, + greater_than_or_equal_to: :min_teams_per_match, + }) + + validate :validate_scoring_method + def validate_scoring_method + (not self.scoring_method.try(:empty?)) and (scoring_methods.include? scoring_method) + end + # Settings ################################################################# def settings @@ -69,16 +114,10 @@ class Tournament < ActiveRecord::Base end end - # Misc. #################################################################### - - def open? - return true - end - # Joining/Leaving ########################################################## def joinable_by?(user) - return (open? and user.can?(:join_tournament) and !players.include?(user)) + return (status==0 and user.can?(:join_tournament) and !players.include?(user)) end def join(user) @@ -100,32 +139,42 @@ class Tournament < ActiveRecord::Base @scoring ||= "Scoring::#{self.scoring_method.camelcase}".constantize end - # YISSSSSS + # Options for configured methods/modules ################################### + # We're conflicted about whether these should be `self.` or not. ########### - def scoring_methods + def self.scoring_methods make_methods "scoring" end + def scoring_methods + self.class.scoring_methods + end def sampling_methods - make_methods("sampling").select do |name| + self.class.make_methods("sampling").select do |name| "Sampling::#{name.camelcase}".constantize.works_with?(self.game) end end - def scheduling_methods + def self.scheduling_methods make_methods "scheduling" end + def scheduling_methods + self.class.scheduling_methods + end - def seeding_methods + def self.seeding_methods make_methods "seeding" end + def seeding_methods + self.class.seeding_methods + end private - def make_methods(dir) - @@methods ||= {} - if @@methods[dir].nil? or Rails.env.development? - @@methods[dir] = Dir.glob("#{Rails.root}/lib/#{dir}/*.rb").map{|filename| File.basename(filename, ".rb") } + def self.make_methods(dir) + @methods ||= {} + if @methods[dir].nil? or Rails.env.development? + @methods[dir] = Dir.glob("#{Rails.root}/lib/#{dir}/*.rb").map{|filename| File.basename(filename, ".rb") } end - return @@methods[dir] + return @methods[dir] end end diff --git a/app/models/tournament_setting.rb b/app/models/tournament_setting.rb index 9efaaea..20d9842 100644 --- a/app/models/tournament_setting.rb +++ b/app/models/tournament_setting.rb @@ -1,6 +1,18 @@ class TournamentSetting < ActiveRecord::Base belongs_to :tournament + validates(:vartype, presence: true, numericality: {only_integer: true}) + validates(:type_opt, presence: true, if: :needs_type_opt?) + + def needs_type_opt? + [ + GameSetting.types[:pick_one_radio], + GameSetting.types[:pick_one_dropdown], + GameSetting.types[:pick_several], + ].include? self.vartype + end + + def self.types GameSetting.types end diff --git a/app/models/tournament_stage.rb b/app/models/tournament_stage.rb index 19b9c23..72aa14c 100644 --- a/app/models/tournament_stage.rb +++ b/app/models/tournament_stage.rb @@ -1,7 +1,17 @@ class TournamentStage < ActiveRecord::Base belongs_to :tournament + validates_presence_of :tournament + has_many :matches + validates(:scheduling_method, + presence: true, + inclusion: {in: Tournament.new.scheduling_methods}) + + validates(:seeding_method, + presence: true, + inclusion: {in: Tournament.new.seeding_methods}) + # A 1-indexed hash of matches def matches_ordered h = {} diff --git a/app/models/user.rb b/app/models/user.rb index b2c7862..a39037c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,6 +1,7 @@ class User < ActiveRecord::Base - before_save :default_values - + ################################################################## + # Relationships # + ################################################################## has_and_belongs_to_many :tournaments_played, class_name: "Tournament", foreign_key: "player_id", join_table: "players_tournaments" has_and_belongs_to_many :tournaments_hosted, class_name: "Tournament", foreign_key: "host_id", join_table: "hosts_tournaments" has_and_belongs_to_many :teams @@ -8,20 +9,49 @@ class User < ActiveRecord::Base has_many :statistics has_many :remote_usernames - apply_simple_captcha + ################################################################## + # Attributes # + ################################################################## - acts_as_messageable + # name:string + validates(:name, presence: true, length: { maximum: 50 }) + # email:string:uniq + before_save { self.email = email.downcase } + validates(:email, + presence: true, + format: {with: /\A\S+@\S+\.\S+\z/i}, + uniqueness: { case_sensitive: false }) + + # user_name:string_uniq + validates(:user_name, + presence: true, + length:{maximum: 50}, + format: {with: /\A[a-zA-Z0-9 _\-]+\z/}, + uniqueness: {case_sensitive: false }) + + # password_digest:string + has_secure_password validations: false # maps :password and :password_confirmation to :password_digest + validates(:password, + length: { minimum: 6 }, + confirmation: true, + unless: Proc.new { |u| u.password.try(:empty?) and not u.password_digest.try(:empty?) }) + + # permissions:integer + before_save { self.permissions ||= Server.first.default_user_permissions } + + ################################################################## + # XXX: hard-coded-ish. It makes me feel dirty. # + ################################################################## + apply_simple_captcha + acts_as_messageable def mailboxer_email(object) return nil end - before_save { self.email = email.downcase } - before_save { self.user_name = user_name } - - def default_values - self.permissions ||= Server.first.default_user_permissions - end + ################################################################## + # remote_usernames # + ################################################################## def set_remote_username(game, data) remote = self.remote_usernames.where(:game => game).first @@ -46,6 +76,10 @@ class User < ActiveRecord::Base end end + ################################################################## + # Permissions # + ################################################################## + def self.permission_bits return { :create_tournament => (2**1), @@ -104,7 +138,6 @@ class User < ActiveRecord::Base end end - # A representation of the permission bits as a mock-array. def abilities @abilities ||= Abilities.new(self) @@ -122,7 +155,7 @@ class User < ActiveRecord::Base # easy to modify them using a form. class Abilities def initialize(user) - @user = user +o @user = user end def [](ability) return @user.can?(ability) @@ -146,40 +179,9 @@ class User < ActiveRecord::Base end end - # VAILD_EMAIL is the regex used to validate a user given email. - VALID_EMAIL_REG = /\A\S+@\S+\.\S+\z/i - - # VALID_USER_NAME checks to make sure a user's user_name - # is in the proper format. - VALID_USER_NAME_REG = /\A[a-zA-Z0-9 _\-]+\z/ - - # The following lines put a user account through a series of - # validations in order to make sure all of their information - # is in the proper format. - # - # validates :symbol_to_be_validated - # - # - presence: determines whether or not a symbol is filled or not - # - length: ensures there is a length limit on the symbol - # - format: checks the format of given information to ensure - # validity - validates(:name, presence: true, length: { maximum: 50 }) - validates(:email, presence: true, format: {with: - VALID_EMAIL_REG}, - uniqueness: { case_sensitive: false }) - validates(:user_name, presence: true, length:{maximum: 50}, - format: {with: VALID_USER_NAME_REG }, - uniqueness: {case_sensitive: false }) - - # Instead of adding password and password_confirmation - # attributes, requiring the presence of a password, - # requiring that pw and pw_com match, and add an authenticate - # method to compare an encrypted password to the - # password_digest to authenticate users, I can just add - # has_secure_password which does all of this for me. - has_secure_password - - validates :password, length: { minimum: 6 } + ################################################################## + # Null-object pattern # + ################################################################## class NilUser def nil? -- cgit v1.2.3