lib/volt/models/helpers/change_helpers.rb

Summary

Maintainability
A
35 mins
Test Coverage
# ModelChangeHelpers handle validating and persisting the data in a model
# when it is changed.  #run_changed will be called from the model.
module Volt
  module Models
    module Helpers
      module ChangeHelpers
        def self.included(base)
          base.setup_action_helpers_in_class(:before_save, :before_validate)
        end

        # Called when something in the model changes.  Saves
        # the model if there is a persistor, and changes the
        # model to not be new.
        #
        # @return [Promise|nil] a promise for when the save is
        #         complete
        def run_changed(attribute_name = nil)
          # no_validate mode should only be used internally.  no_validate mode is a
          # performance optimization that prevents validation from running after each
          # change when assigning multile attributes.
          unless Volt.in_mode?(:no_validate)
            # Run the validations for all fields
            result = nil
            return validate!.then do
              # Buffers are allowed to be in an invalid state
              unless buffer?
                # First check that all local validations pass.  Any time any
                # validations fail, the model is in an invalid state and won't
                # persist.  However, we want to be able to move the model
                # towards a valid state one change at a time.
                if error_in_changed_attributes?
                  # Some errors are present, revert changes
                  revert_changes!

                  # After we revert, we need to validate again to get the error messages back
                  # TODO: Could probably cache the previous errors.
                  result = validate!.then do
                    # Reject the promise with the errors
                    Promise.new.reject(errs)
                  end
                else
                  result = persist_changes(attribute_name)
                end
              end

              # Return result inside of the validate! promise
              result.then { self }
            end
          end

          # Didn't run validations
          self.then
        end


        private
        # Should only be called from run_changed.  Saves the changes back to the persistor
        # and clears the tracked changes.
        def persist_changes(attribute_name)
          # No errors, tell the persistor to handle the change (usually save)
          result = nil

          # Don't save right now if we're in a nosave block
          unless Volt.in_mode?(:no_save)
            # Call the before_save callback
            # skip validations when running before_save, this prevents n+1, and allows
            # the before_save to put the model into an invalid state, which you want
            # sometimes.
            Volt::Model.no_validate do
              run_callbacks(:before_save)
            end

            # the changed method on a persistor should return a promise that will
            # be resolved when the save is complete, or fail with a hash of errors.
            if @persistor
              result = @persistor.changed(attribute_name)
            else
              result = Promise.new.resolve(nil)
            end

            # Saved, no longer new
            @new = false

            # Clear the change tracking
            clear_tracked_changes!
          end

          result
        end

      end
    end
  end
end