Module: SoftValidation

Extended by:
ActiveSupport::Concern
Included in:
AssertedDistribution, BiologicalAssociation, CharacterState, Citation, CollectingEvent, CollectionObject, CollectionProfile, Container, ControlledVocabularyTerm, Descriptor, Document, Documentation, Extract, Identifier::Global, Image, Loan, ObservationMatrixRow, ObservationMatrixRowItem, Otu, RangedLotCategory, Serial, Source, TaxonName, TaxonNameClassification, TaxonNameRelationship, TypeMaterial
Defined in:
lib/soft_validation.rb,
lib/soft_validation/soft_validation.rb,
lib/soft_validation/soft_validations.rb,
lib/soft_validation/soft_validation_method.rb

Overview

Vaguely inspired by concepts from by svn://rubyforge.org/var/svn/softvalidations, but not as elegant.

Soft validations are a means to tie warnings or suggestions to instances of data. Soft validations do not prevent an instance from being saved. They are not intended to be bound to AR callbacks, but this may be possible ultimately. They may be used to alert the user to data issues that need to be addressed, or alert the programmer who is batch parsing data as to the quality of the incoming data, etc..

There are 2 stages to defining a soft validation. First index and provide an option general description of the soft validation itself using the `soft_validate` macro in the class. Second, add the method (logic) that is called, and a set of message that the user will see when the logic passes or fails. To state in another way:

  • The name and description (intent) of the soft validation is optionally provided with the macro setting the soft validation (`Klass.soft_validate()`.

  • The human messages ('there is a problem here!', 'the problem is fixed', 'we tried to fix, but failed!') are defined with the method logic itself. This is intentionally done to

keep the intent of the logic close to the consequences of the logic.

Devloper tips:

  • Protonym.soft_validation( ) <- all technical metadata and a gross description (the intent), optionally, goes here

  • @protonym.sv_xyz( ) <- all human guidance (warning, outcomes) goes here, including the attribute to point to in the UI

  • fix method names should not be exposed to the UI

Usage:

class Foo < ApplicationRecord
  include SoftValidation
  soft_validate(:a_soft_validation_method, fix: :cook_cheezburgers)

  # Validations can be assigned to a set (only one), and validations in a set
  # can be called individually.
  soft_validate(:other_soft_validation_method, set: :some_set)
  soft_validate(:yet_another_method, set: :some_other_set )
  soft_validate(:described_method, name: 'the validation for X', description: 'this validation does Z')
  soft_validate(:a_third_method, resolution: [:route_name, route_name2]) # Resolution is route name (without _path/_url), id: and model_id: are added to the route.

  soft_validate(:a_fourth_example, fix: :fix_method) # the detected issue can be fully resolved by calling this instance method

  $hungry = true # demo only, don't use $globals

  def a_soft_validation_method
    soft_validations.add(:base, 'hungry!',                          # :base or a model attribute (column)
      success_message: 'no longer hungry, cooked a cheezeburger',
      failure_message: 'oh no, cat ate your cheezeburger'
    ) if $hungry
  end

  def cook_cheezburgers
    $hungry = false
  end
end

f = Foo.new

f.soft_validations.validated?             # => false
f.soft_validations.fixes_run?             # => false
f.soft_validations.fixed?                 # => false
f.soft_validations.complete?              # => false

f.soft_validate                           # => true
f.soft_validated?                         # => true
f.soft_fixed?                             # => false
f.soft_valid?                             # => false  # true if there are no SoftValidations produced

f.soft_validations.soft_validations                        # => [soft_validation, soft_validation1 ... ]
f.soft_validations.soft_validations.size                   # => 1
f.soft_validations.soft_validations.first                  # => A SoftValidation instance

# SoftValidation attributes
f.soft_validations.soft_validations.first.attribute          # => :base
f.soft_validations.soft_validations.first.message            # => 'hungry!'
f.soft_validations.soft_validations.first.success_message    # => 'no longer hungry, cooked a cheezeburger'
f.soft_validations.soft_validations.first.failure_message    # => 'oh no, cat ate your cheezeburger'

f.soft_validations.soft_validations.first.fixed?           # => false
f.soft_validations.soft_validations.first.result_message     # => 'fix not yet run'

f.fix_soft_validations                    # => true
f.soft_fixed?                             # => true
f.soft_valid?                             # => false !! There is still a SoftValidation generated, will be true next time it's run

f.soft_validations.fixes_run                               # => true
f.soft_validations.soft_validations.first.fixed?           # => true
f.soft_validations.soft_validations.first.result_message   # => 'no longer hungry, cooked a cheezeburger'
f.soft_validations.on(:base)               # => [soft_validation, ... ]
f.soft_validations.messages                # => ['hungry!']
f.soft_validations.messages_on(:base)      # => ['hungry!']

f.clear_soft_validations

f.soft_validate(only_sets: [:default])           # only run this set, the set of soft validations not assigned to a set
f.soft_validate(only_sets: [:some_other_set])    # only run these sets of validations
f.soft_validate(except_set: [:some_other_set])   # run non-flagged except soft validations in these sets
f.soft_validate(only_methods: :some_method)      # run only this soft validation (all other params ignored)
f.soft_validate(except_methods: [:some_method])  # run result except these soft validation methods
f.soft_validate(fixable: true)                   # run all soft validations that have a fix
f.soft_validate(fixable: false)                  # run all soft validations without a fix
f.soft_validate(flagged: true)                   # run all, *including* methods flagged by developers as "a-typical", there is no flagged: false, as it is default)

Defined Under Namespace

Modules: ClassMethods Classes: SoftValidation, SoftValidationError, SoftValidationMethod, SoftValidations

Constant Summary collapse

ANCESTORS_WITH_SOFT_VALIDATIONS =

An index of the soft validators in superclasses

Hash.new do |h, klass|
  h[klass.name] = (klass.ancestors.select {|a| a.respond_to?(:soft_validates?) && a.soft_validates?} - [klass]) # a < ApplicationRecord && would be faster but requires AR in spec
end

Instance Method Summary collapse

Instance Method Details

#clear_soft_validationsNil

Returns:

  • (Nil)


309
310
311
# File 'lib/soft_validation.rb', line 309

def clear_soft_validations
  @soft_validation_result = nil
end

#fix_for(soft_validation_method) ⇒ Object

TODO: should be here?



374
375
376
# File 'lib/soft_validation.rb', line 374

def fix_for(soft_validation_method)
  soft_validation_methods[soft_validation_method]&.fix
end

#fix_soft_validationsObject

The validation set to fix is set prior to running the fix, at the first step. It can be refined/restricted there as needed, letting specific contexts (e.g. access in controller) defined the scope.



341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'lib/soft_validation.rb', line 341

def fix_soft_validations
  return false if !soft_validated?
  soft_validations.soft_validations.each do |v|
    if fix = fix_for(v.soft_validation_method)
      if self.send(fix)
        v.fixed = :fixed
      else
        v.fixed = :fix_error
      end
    else
      v.fixed = :no_fix_available
    end
  end
  soft_validations.fixes_run = true
  true
end

#soft_fixed?Boolean

Returns:

  • (Boolean)


364
365
366
# File 'lib/soft_validation.rb', line 364

def soft_fixed?
  soft_validations.fixes_run?
end

#soft_valid?Boolean

Returns:

  • (Boolean)


369
370
371
# File 'lib/soft_validation.rb', line 369

def soft_valid?
  soft_validations.complete?
end

#soft_validate(**options) ⇒ true

Run a set of soft validations.

  • by default all validations except those with `flagged: true` are run

  • when only|except_methods are set then these further restrict the scope of tests run

  • except_methods will exclude methods from any result (i.e. sets are allowed)

Returns:

  • (true)


321
322
323
324
325
326
327
328
329
330
331
# File 'lib/soft_validation.rb', line 321

def soft_validate(**options)
  clear_soft_validations
  soft_validations

  soft_validators(**options).each do |sv_method|
    self.send(sv_method)
  end

  soft_validations.validated = true
  true
end

#soft_validated?Boolean

Returns:

  • (Boolean)


359
360
361
# File 'lib/soft_validation.rb', line 359

def soft_validated?
  soft_validations.validated?
end

#soft_validationsSoftValidations

Returns:



304
305
306
# File 'lib/soft_validation.rb', line 304

def soft_validations
  @soft_validation_result ||= SoftValidations.new(self)
end

#soft_validators(**options) ⇒ Object

See Also:

  • Class.soft_validators


334
335
336
# File 'lib/soft_validation.rb', line 334

def soft_validators(**options)
  self.class.soft_validators(**options)
end