Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Patch autosave associations to support conditional validations #2381

Merged
merged 2 commits into from
May 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions app/controllers/external_users/claims_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class ResourceClassNotDefined < StandardError; end

before_action :set_and_authorize_claim, only: %i[show edit update unarchive clone_rejected destroy summary
confirmation show_message_controls messages disc_evidence]
before_action :set_form_step, only: %i[edit]
before_action :set_form_step, only: %i[edit update]
before_action :redirect_unless_editable, only: %i[edit update]
before_action :generate_form_id, only: %i[new edit]
before_action :initialize_submodel_counts
Expand Down Expand Up @@ -134,6 +134,7 @@ def unarchive

def new
@claim = resource_klass.new
@claim.form_step = params[:step] || @claim.submission_stages.first
authorize! :new, @claim
load_offences_and_case_types
build_nested_resources
Expand All @@ -154,6 +155,7 @@ def edit

def create
@claim = resource_klass.new(params_with_external_user_and_creator)
@claim.form_step ||= @claim.submission_stages.first
authorize! :create, @claim
result = if submitting_to_laa?
Claims::CreateClaim.call(@claim)
Expand Down Expand Up @@ -266,8 +268,9 @@ def set_and_authorize_claim

def set_form_step
return unless @claim
return unless params[:step].present?
@claim.form_step = params[:step]
@claim.form_step = params[:step] ||
params.key?(:claim) && claim_params[:form_step] ||
@claim.submission_stages.first
end

def claim_params
Expand Down
14 changes: 12 additions & 2 deletions app/models/claim/advocate_claim.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,18 @@ module Claim
class AdvocateClaim < BaseClaim
route_key_name 'advocates_claim'

has_many :basic_fees, foreign_key: :claim_id, class_name: 'Fee::BasicFee', dependent: :destroy, inverse_of: :claim
has_many :fixed_fees, foreign_key: :claim_id, class_name: 'Fee::FixedFee', dependent: :destroy, inverse_of: :claim
has_many :basic_fees,
foreign_key: :claim_id,
class_name: 'Fee::BasicFee',
dependent: :destroy,
inverse_of: :claim,
validate: proc { |claim| claim.from_api? || claim.form_step.nil? || claim.form_step == :basic_fees }
has_many :fixed_fees,
foreign_key: :claim_id,
class_name: 'Fee::FixedFee',
dependent: :destroy,
inverse_of: :claim,
validate: proc { |claim| claim.from_api? || claim.form_step.nil? || claim.form_step == :fixed_fees }

accepts_nested_attributes_for :basic_fees, reject_if: all_blank_or_zero, allow_destroy: true
accepts_nested_attributes_for :fixed_fees, reject_if: all_blank_or_zero, allow_destroy: true
Expand Down
7 changes: 6 additions & 1 deletion app/models/claim/advocate_interim_claim.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ class AdvocateInterimClaim < BaseClaim
unless: proc { |c| c.disable_for_state_transition.eql?(:all) }
validates_with ::Claim::AdvocateInterimClaimSubModelValidator

has_one :warrant_fee, foreign_key: :claim_id, class_name: 'Fee::WarrantFee', dependent: :destroy, inverse_of: :claim
has_one :warrant_fee,
foreign_key: :claim_id,
class_name: 'Fee::WarrantFee',
dependent: :destroy,
inverse_of: :claim,
validate: proc { |claim| claim.from_api? || claim.form_step.nil? || claim.form_step == :interim_fees }

accepts_nested_attributes_for :warrant_fee, allow_destroy: false

Expand Down
1 change: 0 additions & 1 deletion app/models/claim/base_claim.rb
Original file line number Diff line number Diff line change
Expand Up @@ -683,7 +683,6 @@ def build_associations

def default_values
self.source ||= 'web'
self.form_step ||= submission_stages.first
end

def default_case_transferred_from_another_court
Expand Down
7 changes: 6 additions & 1 deletion app/models/claim/interim_claim.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,12 @@ class InterimClaim < BaseClaim
validates_with ::Claim::LitigatorSupplierNumberValidator
validates_with ::Claim::InterimClaimSubModelValidator

has_one :interim_fee, foreign_key: :claim_id, class_name: 'Fee::InterimFee', dependent: :destroy, inverse_of: :claim
has_one :interim_fee,
foreign_key: :claim_id,
class_name: 'Fee::InterimFee',
dependent: :destroy,
inverse_of: :claim,
validate: proc { |claim| claim.from_api? || claim.form_step.nil? || claim.form_step == :interim_fees }

accepts_nested_attributes_for :interim_fee, reject_if: :all_blank, allow_destroy: false

Expand Down
10 changes: 8 additions & 2 deletions app/models/claim/litigator_claim.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,19 @@ class LitigatorClaim < BaseClaim
validates_with ::Claim::LitigatorSupplierNumberValidator, on: :create
validates_with ::Claim::LitigatorClaimSubModelValidator

has_one :fixed_fee, foreign_key: :claim_id, class_name: 'Fee::FixedFee', dependent: :destroy, inverse_of: :claim
has_one :fixed_fee,
foreign_key: :claim_id,
class_name: 'Fee::FixedFee',
dependent: :destroy,
inverse_of: :claim,
validate: proc { |claim| claim.from_api? || claim.form_step.nil? || claim.form_step == :fixed_fees }
has_one :warrant_fee, foreign_key: :claim_id, class_name: 'Fee::WarrantFee', dependent: :destroy, inverse_of: :claim
has_one :graduated_fee,
foreign_key: :claim_id,
class_name: 'Fee::GraduatedFee',
dependent: :destroy,
inverse_of: :claim
inverse_of: :claim,
validate: proc { |claim| claim.from_api? || claim.form_step.nil? || claim.form_step == :graduated_fees }

accepts_nested_attributes_for :fixed_fee, reject_if: :all_blank, allow_destroy: false
accepts_nested_attributes_for :warrant_fee, reject_if: :all_blank, allow_destroy: false
Expand Down
16 changes: 14 additions & 2 deletions app/models/claim/transfer_claim.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,20 @@ module Claim
class TransferClaim < BaseClaim
route_key_name 'litigators_transfer_claim'

has_one :transfer_detail, foreign_key: :claim_id, class_name: Claim::TransferDetail, dependent: :destroy
has_one :transfer_fee, foreign_key: :claim_id, class_name: Fee::TransferFee, dependent: :destroy, inverse_of: :claim
has_one :transfer_detail,
foreign_key: :claim_id,
class_name: Claim::TransferDetail,
dependent: :destroy,
inverse_of: :claim,
validate: proc { |claim|
claim.from_api? || claim.form_step.nil? || claim.form_step == :transfer_fee_details
}
has_one :transfer_fee,
foreign_key: :claim_id,
class_name: Fee::TransferFee,
dependent: :destroy,
inverse_of: :claim,
validate: proc { |claim| claim.from_api? || claim.form_step.nil? || claim.form_step == :transfer_fees }

accepts_nested_attributes_for :transfer_detail, reject_if: :all_blank, allow_destroy: false
accepts_nested_attributes_for :transfer_fee, reject_if: :all_blank, allow_destroy: false
Expand Down
4 changes: 0 additions & 4 deletions app/presenters/claim/base_claim_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ def show_sidebar?
transfer_fee_details].include? claim.current_step
end

def can_be_saved_as_draft?
claim.draft? && !%i[case_details transfer_fee_details].include?(claim.current_step)
end

# NOTE: this is an interim solution for what probably should be
# some sort of DSL to describe what fields are required for a given section
# for that section to be considered completed
Expand Down
8 changes: 6 additions & 2 deletions app/validators/claim/base_claim_sub_model_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,14 @@ def validate(record)

private

def validate_all_steps?(record)
record.from_api? || record.form_step.nil?
end

def associations_for_has_many_validations(record)
# NOTE: keeping existent validation for API purposes
# The form validations just validate the fields for the current step
return (has_many_association_names_for_steps[record.form_step] || []) unless record.from_api?
return (has_many_association_names_for_steps[record.form_step] || []) unless validate_all_steps?(record)
has_many_association_names_for_steps.select do |k, _v|
record.submission_current_flow.map(&:to_sym).include?(k)
end.values.flatten
Expand All @@ -42,7 +46,7 @@ def validate_presence_of_association(association_name, options = {})
def associations_for_has_one_validations(record)
# NOTE: keeping existent validation for API purposes
# The form validations just validate the fields for the current step
return (has_one_association_names_for_steps[record.form_step] || []) unless record.from_api?
return (has_one_association_names_for_steps[record.form_step] || []) unless validate_all_steps?(record)
has_one_association_names_for_steps.select do |k, _v|
record.submission_current_flow.map(&:to_sym).include?(k)
end.values.flatten
Expand Down
3 changes: 2 additions & 1 deletion app/validators/claim/base_claim_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ def self.mandatory_fields
def step_fields_for_validation
# NOTE: keeping existent validation for API purposes
# The form validations just validate the fields for the current step
return (self.class.fields_for_steps[@record.form_step] || []) unless @record.from_api?
return (self.class.fields_for_steps[@record.form_step] || []) unless @record.from_api? || @record.form_step.nil?
return self.class.fields_for_steps.values.flatten if !@record.from_api? && @record.form_step.nil?
self.class.fields_for_steps.select do |k, _v|
@record.submission_current_flow.map(&:to_sym).include?(k)
end.values.flatten
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@
- commit = claim.next_step? ? 'commit_continue' : 'commit_submit_claim'
= f.submit t('buttons.continue'), name: commit, class: 'button left btn-spacer-20'

- if claim.can_be_saved_as_draft?
= f.submit t('buttons.save_a_draft'), name: 'commit_save_draft', class: 'button-gray-3 left'
= f.submit t('buttons.save_a_draft'), name: 'commit_save_draft', class: 'button-gray-3 left'
38 changes: 38 additions & 0 deletions config/initializers/01_autosave_association_patch.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# NOTE: This is a MAJOR shoehorn patch to get around the convoluted way the validations in this application work.
# Its main intent is to solve the problem of having validations for a claim per step/stage and because of the fact
# that Rails autosaves and validates associations that have been defined as with 'accept_nested_attributes_for'.
# What that causes is that for a given step/stage that does not require a given association to be validated, as long as
# the association itself exists, Rails will automatically save and validate it which is not the expected behaviour here.
#
# The initial approach was to define the association with the option validate: Proc.new { |claim| claim.condition_to_validate? }
# Unfortunately, the association definition support Procs or lambdas but does not evaluate them, just checks if the value is not false.
#
# The next approach was to define the association with the option validate: false and have an explicity validation for the association
# conditional to the step/stage it belongs:
#
# validates_associated :association_name, if: Proc.new { |claim| claim.condition_to_validate? }
#
# This approach works as intended with a caveat: the errors produced are not attached to the parent object anymore (as they were with the
# default autosave/validaton set in the association definition)
#
# So, that gets us here, patching the autosave association to check if the validate option is a proc, and if so evaluated it to determine
# if the associated records needs to be validated or not.
#
# WARNING: This patch will likely cause any further Rails updates/upgrades to break, so do keep that in mind!!!
#
# This should probably be re-thinked going forward, so a cleaner/less disruptive solution is choosen instead.
# This is not a recommended approach to take!!!, but rather a last resort approach to get around a very unsual behaviour (saving invalid records into the database).

module ActiveRecord
module AutosaveAssociation
private
alias old_association_valid? association_valid?

def association_valid?(reflection, record, index=nil)
if reflection.options[:validate].is_a?(Proc)
return true unless reflection.options[:validate].call(self)
end
old_association_valid?(reflection, record, index)
end
end
end
9 changes: 8 additions & 1 deletion features/step_definitions/authorise_claim_steps.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
Given(/^there is a claim allocated to the case worker with case number '(.*?)'$/) do |case_number|
@claim = create(:allocated_claim, external_user: @advocate, case_number: case_number)
# TODO: this does not create a valid submitted claim
# creates some sort of claim with some information that happens
# to be in the submitted state
# To go around this I'm preserving old functionality for the factory by
# setting the form_step.
# Going forward this should be properly fixed with an actual factory that is valid
# for a submitted claim
@claim = create(:allocated_claim, external_user: @advocate, case_number: case_number, form_step: :case_details)
@case_worker.claims << @claim
end

Expand Down
9 changes: 8 additions & 1 deletion features/step_definitions/claim_allocation_steps.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,14 @@
@claims = []

case_numbers.each do |case_number|
@claims << create(:submitted_claim, case_number: case_number)
# TODO: this does not create a valid submitted claim
# creates some sort of claim with some information that happens
# to be in the submitted state
# To go around this I'm preserving old functionality for the factory by
# setting the form_step.
# Going forward this should be properly fixed with an actual factory that is valid
# for a submitted claim
@claims << create(:submitted_claim, case_number: case_number, form_step: :case_details)
end
end

Expand Down
Loading