From 83c6b2397adaa77d3d697fae43b82a5ab6ffb822 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Mon, 2 Jan 2023 21:24:53 -0800 Subject: [PATCH] Refactor weapon conflicts into model validations --- app/blueprints/api/v1/conflict_blueprint.rb | 4 +- .../api/v1/grid_weapons_controller.rb | 119 +++++++++--------- app/models/grid_weapon.rb | 31 +++-- app/models/weapon.rb | 5 + 4 files changed, 77 insertions(+), 82 deletions(-) diff --git a/app/blueprints/api/v1/conflict_blueprint.rb b/app/blueprints/api/v1/conflict_blueprint.rb index eccfab1..fa036e5 100644 --- a/app/blueprints/api/v1/conflict_blueprint.rb +++ b/app/blueprints/api/v1/conflict_blueprint.rb @@ -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| diff --git a/app/controllers/api/v1/grid_weapons_controller.rb b/app/controllers/api/v1/grid_weapons_controller.rb index 4f23fb1..aa0ad4d 100644 --- a/app/controllers/api/v1/grid_weapons_controller.rb +++ b/app/controllers/api/v1/grid_weapons_controller.rb @@ -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 diff --git a/app/models/grid_weapon.rb b/app/models/grid_weapon.rb index 92afa49..2b31eea 100644 --- a/app/models/grid_weapon.rb +++ b/app/models/grid_weapon.rb @@ -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 diff --git a/app/models/weapon.rb b/app/models/weapon.rb index 85a2e8f..c18a313 100644 --- a/app/models/weapon.rb +++ b/app/models/weapon.rb @@ -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