Refactor GridSummon and add documentation and tests
This commit is contained in:
parent
cdfdec0fa7
commit
e5ece0a7a3
2 changed files with 330 additions and 3 deletions
|
|
@ -1,5 +1,15 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
##
|
||||
# Model representing a grid summon within a party.
|
||||
#
|
||||
# A GridSummon is associated with a specific {Summon} and {Party} and is responsible for
|
||||
# enforcing rules on positions, uncap levels, and transcendence steps based on the associated summon’s flags.
|
||||
#
|
||||
# @!attribute [r] summon
|
||||
# @return [Summon] the associated summon.
|
||||
# @!attribute [r] party
|
||||
# @return [Party] the associated party.
|
||||
class GridSummon < ApplicationRecord
|
||||
belongs_to :summon, foreign_key: :summon_id, primary_key: :id
|
||||
|
||||
|
|
@ -8,14 +18,35 @@ class GridSummon < ApplicationRecord
|
|||
inverse_of: :summons
|
||||
validates_presence_of :party
|
||||
|
||||
# Validate that position is provided.
|
||||
validates :position, presence: true
|
||||
validate :compatible_with_position, on: :create
|
||||
|
||||
# Validate that uncap_level and transcendence_step are present and numeric.
|
||||
validates :uncap_level, presence: true, numericality: { only_integer: true }
|
||||
validates :transcendence_step, presence: true, numericality: { only_integer: true }
|
||||
|
||||
# Custom validation to enforce maximum uncap_level based on the associated Summon’s flags.
|
||||
validate :validate_uncap_level_based_on_summon_flags
|
||||
|
||||
validate :no_conflicts, on: :create
|
||||
|
||||
##
|
||||
# Returns the blueprint for rendering the grid summon.
|
||||
#
|
||||
# @return [GridSummonBlueprint] the blueprint class for grid summons.
|
||||
def blueprint
|
||||
GridSummonBlueprint
|
||||
end
|
||||
|
||||
# Returns conflicting summons if they exist
|
||||
##
|
||||
# Returns any conflicting grid summon for the given party.
|
||||
#
|
||||
# If the associated summon has a limit, this method searches the party's grid summons to find
|
||||
# any that conflict based on the summon ID.
|
||||
#
|
||||
# @param party [Party] the party in which to check for conflicts.
|
||||
# @return [GridSummon, nil] the conflicting grid summon if found, otherwise nil.
|
||||
def conflicts(party)
|
||||
return unless summon.limit
|
||||
|
||||
|
|
@ -28,13 +59,74 @@ class GridSummon < ApplicationRecord
|
|||
|
||||
private
|
||||
|
||||
# Validates whether there is a conflict with the party
|
||||
##
|
||||
# Validates the uncap_level based on the associated Summon’s flags.
|
||||
#
|
||||
# This method delegates to specific validation methods for FLB, ULB, and transcendence limits.
|
||||
#
|
||||
# @return [void]
|
||||
def validate_uncap_level_based_on_summon_flags
|
||||
return unless summon
|
||||
|
||||
validate_flb_limit
|
||||
validate_ulb_limit
|
||||
validate_transcendence_limits
|
||||
end
|
||||
|
||||
##
|
||||
# Validates that the uncap_level does not exceed 3 if the associated Summon does not have the FLB flag.
|
||||
#
|
||||
# @return [void]
|
||||
def validate_flb_limit
|
||||
return unless !summon.flb && uncap_level.to_i > 3
|
||||
|
||||
errors.add(:uncap_level, 'cannot be greater than 3 if summon does not have FLB')
|
||||
end
|
||||
|
||||
##
|
||||
# Validates that the uncap_level does not exceed 4 if the associated Summon does not have the ULB flag.
|
||||
#
|
||||
# @return [void]
|
||||
def validate_ulb_limit
|
||||
return unless !summon.ulb && uncap_level.to_i > 4
|
||||
|
||||
errors.add(:uncap_level, 'cannot be greater than 4 if summon does not have ULB')
|
||||
end
|
||||
|
||||
##
|
||||
# Validates the uncap_level and transcendence_step based on whether the associated Summon supports transcendence.
|
||||
#
|
||||
# If the summon does not support transcendence, the uncap_level must not exceed 5 and the transcendence_step must be 0.
|
||||
#
|
||||
# @return [void]
|
||||
def validate_transcendence_limits
|
||||
return if summon.transcendence
|
||||
|
||||
errors.add(:uncap_level, 'cannot be greater than 5 if summon does not have transcendence') if uncap_level.to_i > 5
|
||||
|
||||
return unless transcendence_step.to_i.positive?
|
||||
|
||||
errors.add(:transcendence_step, 'must be 0 if summon does not have transcendence')
|
||||
end
|
||||
|
||||
##
|
||||
# Validates that there are no conflicting grid summons in the party.
|
||||
#
|
||||
# If a conflict is found (i.e. another grid summon exists that conflicts with this one),
|
||||
# an error is added to the :series attribute.
|
||||
#
|
||||
# @return [void]
|
||||
def no_conflicts
|
||||
# Check if the grid summon conflicts with any of the other grid summons in the party
|
||||
errors.add(:series, 'must not conflict with existing summons') unless conflicts(party).nil?
|
||||
end
|
||||
|
||||
# Validates whether the summon can be added to the desired position
|
||||
##
|
||||
# Validates whether the grid summon can be added to the desired position.
|
||||
#
|
||||
# For positions 4 and 5, the associated summon must have subaura; otherwise, an error is added.
|
||||
#
|
||||
# @return [void]
|
||||
def compatible_with_position
|
||||
return unless [4, 5].include?(position.to_i) && !summon.subaura
|
||||
|
||||
|
|
|
|||
235
spec/models/grid_summons_spec.rb
Normal file
235
spec/models/grid_summons_spec.rb
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
# Define a dummy GridSummonBlueprint if it is not already defined.
|
||||
class GridSummonBlueprint; end unless defined?(GridSummonBlueprint)
|
||||
|
||||
RSpec.describe GridSummon, type: :model do
|
||||
describe 'associations' do
|
||||
it 'belongs to a party' do
|
||||
association = described_class.reflect_on_association(:party)
|
||||
expect(association).not_to be_nil
|
||||
expect(association.macro).to eq(:belongs_to)
|
||||
end
|
||||
|
||||
it 'belongs to a summon' do
|
||||
association = described_class.reflect_on_association(:summon)
|
||||
expect(association).not_to be_nil
|
||||
expect(association.macro).to eq(:belongs_to)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'validations' do
|
||||
let(:party) { create(:party) }
|
||||
let(:default_summon) { Summon.find_by!(granblue_id: '2040433000') }
|
||||
|
||||
context 'with valid attributes' do
|
||||
subject do
|
||||
build(:grid_summon,
|
||||
party: party,
|
||||
summon: default_summon,
|
||||
position: 1,
|
||||
uncap_level: 3,
|
||||
transcendence_step: 0,
|
||||
main: false,
|
||||
friend: false,
|
||||
quick_summon: false)
|
||||
end
|
||||
|
||||
it 'is valid' do
|
||||
expect(subject).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context 'with missing required attributes' do
|
||||
it 'is invalid without a position' do
|
||||
grid_summon = build(:grid_summon,
|
||||
party: party,
|
||||
summon: default_summon,
|
||||
position: nil,
|
||||
uncap_level: 3,
|
||||
transcendence_step: 0)
|
||||
expect(grid_summon).not_to be_valid
|
||||
expect(grid_summon.errors[:position].join).to match(/can't be blank/)
|
||||
end
|
||||
|
||||
it 'is invalid without a party' do
|
||||
grid_summon = build(:grid_summon,
|
||||
party: nil,
|
||||
summon: default_summon,
|
||||
position: 1,
|
||||
uncap_level: 3,
|
||||
transcendence_step: 0)
|
||||
grid_summon.validate
|
||||
expect(grid_summon.errors[:party].join).to match(/must exist|can't be blank/)
|
||||
end
|
||||
|
||||
it 'is invalid without a summon' do
|
||||
grid_summon = build(:grid_summon,
|
||||
party: party,
|
||||
summon: nil,
|
||||
position: 1,
|
||||
uncap_level: 3,
|
||||
transcendence_step: 0)
|
||||
expect { grid_summon.valid? }.to raise_error(NoMethodError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with non-numeric values' do
|
||||
it 'is invalid when uncap_level is non-numeric' do
|
||||
grid_summon = build(:grid_summon,
|
||||
party: party,
|
||||
summon: default_summon,
|
||||
position: 1,
|
||||
uncap_level: 'three',
|
||||
transcendence_step: 0)
|
||||
expect(grid_summon).not_to be_valid
|
||||
expect(grid_summon.errors[:uncap_level]).not_to be_empty
|
||||
end
|
||||
|
||||
it 'is invalid when transcendence_step is non-numeric' do
|
||||
grid_summon = build(:grid_summon,
|
||||
party: party,
|
||||
summon: default_summon,
|
||||
position: 1,
|
||||
uncap_level: 3,
|
||||
transcendence_step: 'one')
|
||||
expect(grid_summon).not_to be_valid
|
||||
expect(grid_summon.errors[:transcendence_step]).not_to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'custom validations based on Summon flags' do
|
||||
context 'when the summon does not have FLB flag' do
|
||||
let(:summon_without_flb) { default_summon.tap { |s| s.flb = false } }
|
||||
|
||||
it 'is invalid if uncap_level is greater than 3' do
|
||||
grid_summon = build(:grid_summon,
|
||||
party: party,
|
||||
summon: summon_without_flb,
|
||||
position: 1,
|
||||
uncap_level: 4,
|
||||
transcendence_step: 0)
|
||||
expect(grid_summon).not_to be_valid
|
||||
expect(grid_summon.errors[:uncap_level].join).to match(/cannot be greater than 3/)
|
||||
end
|
||||
|
||||
it 'is valid if uncap_level is 3 or less' do
|
||||
grid_summon = build(:grid_summon,
|
||||
party: party,
|
||||
summon: summon_without_flb,
|
||||
position: 1,
|
||||
uncap_level: 3,
|
||||
transcendence_step: 0)
|
||||
expect(grid_summon).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the summon does not have ULB flag' do
|
||||
let(:summon_without_ulb) do
|
||||
default_summon.tap do |s|
|
||||
s.ulb = false
|
||||
s.flb = true
|
||||
end
|
||||
end
|
||||
|
||||
it 'is invalid if uncap_level is greater than 4' do
|
||||
grid_summon = build(:grid_summon,
|
||||
party: party,
|
||||
summon: summon_without_ulb,
|
||||
position: 1,
|
||||
uncap_level: 5,
|
||||
transcendence_step: 0)
|
||||
expect(grid_summon).not_to be_valid
|
||||
expect(grid_summon.errors[:uncap_level].join).to match(/cannot be greater than 4/)
|
||||
end
|
||||
|
||||
it 'is valid if uncap_level is 4 or less' do
|
||||
grid_summon = build(:grid_summon,
|
||||
party: party,
|
||||
summon: summon_without_ulb,
|
||||
position: 1,
|
||||
uncap_level: 4,
|
||||
transcendence_step: 0)
|
||||
expect(grid_summon).to be_valid
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the summon does not have transcendence flag' do
|
||||
let(:summon_without_transcendence) do
|
||||
# Ensure FLB and ULB are true so that only the transcendence rule applies.
|
||||
default_summon.tap do |s|
|
||||
s.transcendence = false
|
||||
s.flb = true
|
||||
s.ulb = true
|
||||
end
|
||||
end
|
||||
|
||||
it 'is invalid if uncap_level is greater than 5' do
|
||||
grid_summon = build(:grid_summon,
|
||||
party: party,
|
||||
summon: summon_without_transcendence,
|
||||
position: 1,
|
||||
uncap_level: 6,
|
||||
transcendence_step: 0)
|
||||
expect(grid_summon).not_to be_valid
|
||||
expect(grid_summon.errors[:uncap_level].join).to match(/cannot be greater than 5/)
|
||||
end
|
||||
|
||||
it 'is invalid if transcendence_step is greater than 0' do
|
||||
grid_summon = build(:grid_summon,
|
||||
party: party,
|
||||
summon: summon_without_transcendence,
|
||||
position: 1,
|
||||
uncap_level: 5,
|
||||
transcendence_step: 1)
|
||||
expect(grid_summon).not_to be_valid
|
||||
expect(grid_summon.errors[:transcendence_step].join).to match(/must be 0/)
|
||||
end
|
||||
|
||||
it 'is valid if uncap_level is 5 or less and transcendence_step is 0' do
|
||||
grid_summon = build(:grid_summon,
|
||||
party: party,
|
||||
summon: summon_without_transcendence,
|
||||
position: 1,
|
||||
uncap_level: 5,
|
||||
transcendence_step: 0)
|
||||
expect(grid_summon).to be_valid
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'default values' do
|
||||
let(:party) { create(:party) }
|
||||
let(:summon) { Summon.find_by!(granblue_id: '2040433000') }
|
||||
subject do
|
||||
build(:grid_summon,
|
||||
party: party,
|
||||
summon: summon,
|
||||
position: 1,
|
||||
uncap_level: 3,
|
||||
transcendence_step: 0)
|
||||
end
|
||||
|
||||
it 'defaults quick_summon to false' do
|
||||
expect(subject.quick_summon).to be_falsey
|
||||
end
|
||||
|
||||
it 'defaults main to false' do
|
||||
expect(subject.main).to be_falsey
|
||||
end
|
||||
|
||||
it 'defaults friend to false' do
|
||||
expect(subject.friend).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
describe '#blueprint' do
|
||||
it 'returns the GridSummonBlueprint constant' do
|
||||
grid_summon = build(:grid_summon)
|
||||
expect(grid_summon.blueprint).to eq(GridSummonBlueprint)
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in a new issue