Refactor weapon conflicts into model validations

This commit is contained in:
Justin Edmund 2023-01-02 21:24:53 -08:00
parent c39abfe8d6
commit 83c6b2397a
4 changed files with 77 additions and 82 deletions

View file

@ -18,8 +18,8 @@ module Api
end
view :weapons do
field :conflicts, if: ->(_fn, _obj, options) { options.key?(:conflict_weapons) } do |_, options|
GridWeaponBlueprint.render_as_hash(options[:conflict_weapons], view: :nested)
field :conflicts, if: ->(_fn, _obj, options) { options.key?(:conflict_weapon) } do |_, options|
GridWeaponBlueprint.render_as_hash(options[:conflict_weapon], view: :nested)
end
field :incoming, if: ->(_fn, _obj, options) { options.key?(:incoming_weapon) } do |_, options|

View file

@ -5,70 +5,20 @@ module Api
class GridWeaponsController < Api::V1::ApiController
before_action :set, except: %w[create update_uncap_level destroy]
attr_reader :party, :incoming_weapon
before_action :find_party, only: :create
before_action :find_incoming_weapon, only: :create
def create
# BUG: I can create grid weapons even when I'm not logged in on an authenticated party
party = Party.find(weapon_params[:party_id])
render_unauthorized_response if current_user && (party.user != current_user)
incoming_weapon = Weapon.find(weapon_params[:weapon_id])
incoming_weapon.limit
# Set up conflict_position in case it is used
conflict_position = nil
if [9, 10, 11].include?(weapon_params[:position].to_i) && ![11, 16, 17, 28, 29].include?(incoming_weapon.series)
raise Api::V1::IncompatibleWeaponForPositionError.new(weapon: incoming_weapon)
end
# 1. If the weapon has a limit
# 2. If the weapon does not match a weapon already in grid
# 3. If the incoming weapon has a limit and other weapons of the same series are in grid
if incoming_weapon.limit && incoming_weapon.limit.positive?
conflict_weapon = party.weapons.find do |weapon|
weapon if incoming_weapon.series == weapon.weapon.series ||
([2, 3].include?(incoming_weapon.series) && [2, 3].include?(weapon.weapon.series))
end
if conflict_weapon
if conflict_weapon.weapon.id != incoming_weapon.id
return render json: ConflictBlueprint.render(nil, view: :weapons,
conflict_weapons: [conflict_weapon],
incoming_weapon: incoming_weapon,
incoming_position: weapon_params[:position])
else
# Destroy the original grid weapon
# TODO: Use conflict_position to alert the client that that position has changed
conflict_position = conflict_weapon.position
GridWeapon.destroy(conflict_weapon.id)
end
end
end
# Destroy the existing item before adding a new one
if (grid_weapon = GridWeapon.where(
party_id: party.id,
position: weapon_params[:position]
).first)
GridWeapon.destroy(grid_weapon.id)
end
# Create the GridWeapon with the desired parameters
weapon = GridWeapon.new
weapon.attributes = weapon_params.merge(party_id: party.id, weapon_id: incoming_weapon.id)
if weapon.position == -1
party.element = weapon.weapon.element
party.save!
end
# Render the new weapon and any weapons changed
if weapon.save!
render json: GridWeaponBlueprint.render(weapon, view: :full,
root: :grid_weapon,
meta: {
replaced: conflict_position
}),
status: :created
if weapon.validate
save_weapon(weapon)
else
handle_conflict(weapon)
end
end
@ -149,8 +99,7 @@ module Api
end
def find_incoming_weapon
@incoming_weapon = Weapon.find(weapon_params[:weapon_id])
@incoming_weapon.limit
@incoming_weapon = Weapon.find_by(id: weapon_params[:weapon_id])
end
def find_party
@ -166,7 +115,7 @@ module Api
# Render the conflict view as a string
def render_conflict_view(conflict_weapon, incoming_weapon, incoming_position)
ConflictBlueprint.render(nil, view: :weapons,
conflict_weapons: [conflict_weapon],
conflict_weapon: conflict_weapon,
incoming_weapon: incoming_weapon,
incoming_position: incoming_position)
end
@ -177,6 +126,50 @@ module Api
meta: { replaced: conflict_position })
end
def save_weapon(weapon)
# Check weapon validation and delete existing grid weapon
# if one already exists at position
if (grid_weapon = GridWeapon.where(
party_id: party.id,
position: weapon_params[:position]
).first)
GridWeapon.destroy(grid_weapon.id)
end
# Set the party's element if the grid weapon is being set as mainhand
if weapon.position == -1
party.element = weapon.weapon.element
party.save!
end
# Render the weapon if it can be saved
return unless weapon.save
output = GridWeaponBlueprint.render(weapon, view: :full, root: :grid_weapon)
render json: output, status: :created
end
def handle_conflict(weapon)
conflict_weapon = weapon.conflicts(party)
if conflict_weapon.weapon.id != incoming_weapon.id
# Render conflict view if the underlying canonical weapons
# are not identical
output = render_conflict_view([conflict_weapon], incoming_weapon, weapon_params[:position])
render json: output
else
# Move the original grid weapon to the new position
# to preserve keys and other modifications
old_position = conflict_weapon.position
conflict_weapon.position = weapon_params[:position]
if conflict_weapon.save
output = render_grid_weapon_view(conflict_weapon, old_position)
render json: output
end
end
end
def set
@weapon = GridWeapon.where('id = ?', params[:id]).first
end

View file

@ -8,8 +8,8 @@ class GridWeapon < ApplicationRecord
belongs_to :weapon_key2, class_name: 'WeaponKey', foreign_key: :weapon_key2_id, optional: true
belongs_to :weapon_key3, class_name: 'WeaponKey', foreign_key: :weapon_key3_id, optional: true
validate :compatible_with_position
validate :no_conflicts
validate :compatible_with_position, on: :create
validate :no_conflicts, on: :create
# Helper methods
def blueprint
@ -24,6 +24,18 @@ class GridWeapon < ApplicationRecord
[weapon_key1, weapon_key2, weapon_key3].compact
end
# Returns conflicting weapons if they exist
def conflicts(party)
return unless weapon.limit
party.weapons.find do |party_weapon|
id_match = weapon.id == party_weapon.id
series_match = weapon.series == party_weapon.weapon.series
both_opus_or_draconic = weapon.opus_or_draconic? && party_weapon.weapon.opus_or_draconic?
weapon if (series_match || both_opus_or_draconic) && !id_match
end
end
private
# Conflict management methods
@ -47,19 +59,4 @@ class GridWeapon < ApplicationRecord
# Check if the grid weapon conflicts with any of the other grid weapons in the party
errors.add(:series, 'must not conflict with existing weapons') unless conflicts(party).nil?
end
# Returns conflicting weapons if they exist
def conflicts(party)
return unless limit
party.weapons.find do |weapon|
series_match = series == weapon.weapon.series
weapon if series_match || opus_or_draconic? && weapon.opus_or_draconic?
end
end
# Returns whether the weapon is included in the Draconic or Dark Opus series
def opus_or_draconic?
[2, 3].include?(series)
end
end

View file

@ -31,4 +31,9 @@ class Weapon < ApplicationRecord
def compatible_with_key?(key)
key.series == series
end
# Returns whether the weapon is included in the Draconic or Dark Opus series
def opus_or_draconic?
[2, 3].include?(series)
end
end