class DraftApprove::Persistor

Logic for writing a Draft to the database, and for applying changes contained within a single Draft and saving them to the database.

@api private

Constants

CREATE_METHOD

IMPORTANT NOTE: These constants are written to the database, so cannot be updated without requiring a migration of existing draft data. Such a migration may be very slow, since these constants are embedded in the JSON generated by this serializer!

DEFAULT_CREATE_METHOD
DEFAULT_DELETE_METHOD
DEFAULT_UPDATE_METHOD
DELETE_METHOD
UPDATE_METHOD

Public Class Methods

write_draft_from_model(action_type, model, options = nil) click to toggle source

Write a Draft object to the database to persist any changes to the given model.

@param action_type [String] the type of action this draft will represent -

+CREATE+, +UPDATE+, or +DELETE+

@param model [Object] the acts_as_draftable ActiveRecord model whose

changes will be saved to the database

@param options [Hash] the options to use when saving this draft, see

+DraftApprove::Draftable::InstanceMethods#draft_save!+ and
+DraftApprove::Draftable::InstanceMethods#draft_destroy!+ for details of
valid options

@return [Draft, false] the Draft record which was created, or false if

there were no changes (ie. the result would have been a 'no-op' change)

@see DraftApprove::Draftable::InstanceMethods#draft_save! @see DraftApprove::Draftable::InstanceMethods#draft_destroy!

# File lib/draft_approve/persistor.rb, line 43
def self.write_draft_from_model(action_type, model, options = nil)
  raise(ArgumentError, 'model argument must be present') unless model.present?

  if validate_model?(options) && model.invalid?
    raise(ActiveRecord::RecordInvalid, model)
  end

  DraftApprove::Transaction.ensure_in_draft_transaction do
    # Now we're in a Transaction, ensure we don't get multiple drafts for the same object
    if model.persisted? && Draft.pending_approval.where(draftable: model).count > 0
      raise(DraftApprove::Errors::ExistingDraftError, "#{model} has existing draft")
    end

    case action_type
    when Draft::CREATE
      raise(DraftApprove::Errors::AlreadyPersistedModelError, "#{model} is already persisted") if model.persisted?
      draftable_type = model.class.name
      draftable_id = nil
    when Draft::UPDATE
      raise(DraftApprove::Errors::UnpersistedModelError, "#{model} isn't persisted") unless model.persisted?
      draftable_type = model.class.name
      draftable_id = model.id
    when Draft::DELETE
      raise(DraftApprove::Errors::UnpersistedModelError, "#{model} isn't persisted") unless model.persisted?
      draftable_type = model.class.name
      draftable_id = model.id
    else
      raise(ArgumentError, "Unknown draft_action_type #{action_type}")
    end

    draft_transaction = DraftApprove::Transaction.current_draft_transaction!
    draft_options = sanitize_options_for_db(options)
    serializer = serializer_class(draft_transaction)
    changes = serializer.changes_for_model(model)

    # Don't write no-op updates!
    return false if changes.empty? && action_type == Draft::UPDATE

    return model.draft_pending_approval = Draft.create!(
      draft_transaction: draft_transaction,
      draftable_type: draftable_type,
      draftable_id: draftable_id,
      draft_action_type: action_type,
      draft_changes: changes,
      draft_options: draft_options
    )
  end
end
write_model_from_draft(draft) click to toggle source

Write the changes represented by the given Draft object to the database.

Depending upon the type of Draft, this method may create a new record in the database, update an existing record, or delete a record.

@param draft [Draft] the Draft object whose changes should be applied

and persisted to the database

@return [Object] the acts_as_draftable ActiveRecord model which has been

created, updated, or deleted
# File lib/draft_approve/persistor.rb, line 102
def self.write_model_from_draft(draft)
  serializer = serializer_class(draft.draft_transaction)
  new_values_hash = serializer.new_values_for_draft(draft)
  options = draft.draft_options || {}

  case draft.draft_action_type
  when Draft::CREATE
    raise(DraftApprove::Errors::NoDraftableError, "No draftable_type for #{draft}") if draft.draftable_type.blank?

    create_method = (options.include?(CREATE_METHOD) ? options[CREATE_METHOD] : DEFAULT_CREATE_METHOD)

    model_class = Object.const_get(draft.draftable_type)
    model = model_class.send(create_method, new_values_hash)

    # We've only just persisted the model, the draft can't have referenced it before!
    draft.update!(draftable: model)

    return model
  when Draft::UPDATE
    raise(DraftApprove::Errors::NoDraftableError, "No draftable for #{draft}") if draft.draftable.blank?

    update_method = (options.include?(UPDATE_METHOD) ? options[UPDATE_METHOD] : DEFAULT_UPDATE_METHOD)

    model = draft.draftable
    model.send(update_method, new_values_hash)
    return model
  when Draft::DELETE
    raise(DraftApprove::Errors::NoDraftableError, "No draftable for #{draft}") if draft.draftable.blank?

    delete_method = (options.include?(DELETE_METHOD) ? options[DELETE_METHOD] : DEFAULT_DELETE_METHOD)

    model = draft.draftable
    model.send(delete_method)
    return model
  else
    raise(ArgumentError, "Unknown draft_action_type #{draft.draft_action_type}")
  end
end

Private Class Methods

sanitize_options_for_db(options) click to toggle source

Helper to remove invalid options before they get persisted to the database

# File lib/draft_approve/persistor.rb, line 155
def self.sanitize_options_for_db(options)
  return nil if !options || options.empty?

  draft_options_keys = [CREATE_METHOD, UPDATE_METHOD, DELETE_METHOD]

  accepted_options = options.each_with_object({}) do |(key, value), accepted_opts|
    accepted_opts[key.to_s] = value if draft_options_keys.include?(key.to_s)
  end

  return (accepted_options.empty? ? nil : accepted_options)
end
serializer_class(draft_transaction) click to toggle source

Helper to get the serialization class to use

# File lib/draft_approve/persistor.rb, line 150
def self.serializer_class(draft_transaction)
  draft_transaction.serialization_module.get_serializer
end
validate_model?(options) click to toggle source

Helper to determine whether to validate a model before writing a draft

# File lib/draft_approve/persistor.rb, line 144
def self.validate_model?(options)
  options ||= {}
  options.fetch(:validate, true)
end