draft_approve 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/.yardopts +6 -0
- data/Appraisals +3 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +91 -0
- data/LICENSE.md +21 -0
- data/README.md +329 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/draft_approve.gemspec +56 -0
- data/lib/draft_approve.rb +5 -0
- data/lib/draft_approve/draft_changes_proxy.rb +242 -0
- data/lib/draft_approve/draftable/base_class_methods.rb +33 -0
- data/lib/draft_approve/draftable/class_methods.rb +119 -0
- data/lib/draft_approve/draftable/instance_methods.rb +80 -0
- data/lib/draft_approve/errors.rb +19 -0
- data/lib/draft_approve/models/draft.rb +75 -0
- data/lib/draft_approve/models/draft_transaction.rb +109 -0
- data/lib/draft_approve/persistor.rb +167 -0
- data/lib/draft_approve/serialization/json.rb +16 -0
- data/lib/draft_approve/serialization/json/constants.rb +21 -0
- data/lib/draft_approve/serialization/json/draft_changes_proxy.rb +317 -0
- data/lib/draft_approve/serialization/json/serializer.rb +181 -0
- data/lib/draft_approve/transaction.rb +125 -0
- data/lib/draft_approve/version.rb +3 -0
- data/lib/generators/draft_approve/migration/migration_generator.rb +41 -0
- data/lib/generators/draft_approve/migration/templates/create_draft_approve_tables.rb +25 -0
- metadata +253 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'draft_approve/persistor'
|
2
|
+
|
3
|
+
module DraftApprove
|
4
|
+
module Draftable
|
5
|
+
|
6
|
+
# Instance methods automatically added to an ActiveRecord model when
|
7
|
+
# +acts_as_draftable+ is called
|
8
|
+
module InstanceMethods
|
9
|
+
##### Basic DraftApprove instance methods #####
|
10
|
+
|
11
|
+
# Whether this object is draftable. Helper method to identify draftable
|
12
|
+
# objects.
|
13
|
+
#
|
14
|
+
# @return [Boolean] true
|
15
|
+
def draftable?
|
16
|
+
true
|
17
|
+
end
|
18
|
+
|
19
|
+
# Saves any changes to the object as a draft.
|
20
|
+
#
|
21
|
+
# This method may be called both on a new object which has not been
|
22
|
+
# persisted yet, and on objects which have already been persisted.
|
23
|
+
#
|
24
|
+
# @param options [Hash] the options to save the draft with
|
25
|
+
# @option options [Symbol] :validate whether to validate the model before
|
26
|
+
# draft changes are saved, defaults to +true+
|
27
|
+
# @option options [Symbol] :create_method the method to use when creating
|
28
|
+
# a new object from this draft, eg. +:find_or_create_by!+. This must be
|
29
|
+
# a method available on the object, and must accept a hash of attribute
|
30
|
+
# names to attribute values. The default is +create!+. Ignored if this
|
31
|
+
# draft is for an object which has already been persisted.
|
32
|
+
# @option options [Symbol] :update_method the method to use when updating
|
33
|
+
# an existing object from this draft, eg. +:update_columns+. This must
|
34
|
+
# be a method available on the object, and must accept a hash of
|
35
|
+
# attribute names to attribute values. The default is +update!+. Ignored
|
36
|
+
# if this draft is for an object which has not yet been persisted.
|
37
|
+
#
|
38
|
+
# @return [Draft, nil] the +Draft+ object which was created, or +nil+ if
|
39
|
+
# there were no changes to the object
|
40
|
+
def draft_save!(options = nil)
|
41
|
+
if self.new_record?
|
42
|
+
DraftApprove::Persistor.write_draft_from_model(Draft::CREATE, self, options)
|
43
|
+
else
|
44
|
+
DraftApprove::Persistor.write_draft_from_model(Draft::UPDATE, self, options)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Marks this object to be destroyed when this draft change is approved.
|
49
|
+
#
|
50
|
+
# This method should only be called on objects which have already been
|
51
|
+
# persisted.
|
52
|
+
#
|
53
|
+
# @param options [Hash] the options to save the draft with
|
54
|
+
# @option options [Symbol] :delete_method the method to use to delete
|
55
|
+
# the object when this draft is approved, eg. +:delete+. This must be
|
56
|
+
# a method available on the object. The default is +destroy!+.
|
57
|
+
#
|
58
|
+
# @return [Draft] the +Draft+ object which was created
|
59
|
+
def draft_destroy!(options = nil)
|
60
|
+
DraftApprove::Persistor.write_draft_from_model(Draft::DELETE, self, options)
|
61
|
+
end
|
62
|
+
|
63
|
+
##### Additional convenience DraftApprove instance methods #####
|
64
|
+
|
65
|
+
# Updates an existing object with the given attributes, and saves the
|
66
|
+
# updates as a draft.
|
67
|
+
#
|
68
|
+
# @param attributes [Hash] a hash of attribute names to attribute values,
|
69
|
+
# like the hash expected by the ActiveRecord +update+ / +update!+
|
70
|
+
# methods
|
71
|
+
#
|
72
|
+
# @return [Draft, nil] the +Draft+ object which was created, or +nil+ if
|
73
|
+
# there were no changes to the object
|
74
|
+
def draft_update!(attributes)
|
75
|
+
self.assign_attributes(attributes)
|
76
|
+
self.draft_save!
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module DraftApprove
|
2
|
+
module Errors
|
3
|
+
class DraftTransactionError < StandardError; end
|
4
|
+
class NestedDraftTransactionError < DraftTransactionError; end
|
5
|
+
class NoDraftTransactionError < DraftTransactionError; end
|
6
|
+
|
7
|
+
class DraftSaveError < StandardError; end
|
8
|
+
class ExistingDraftError < DraftSaveError; end
|
9
|
+
class AlreadyPersistedModelError < DraftSaveError; end
|
10
|
+
class UnpersistedModelError < DraftSaveError; end
|
11
|
+
|
12
|
+
class ChangeSerializationError < StandardError; end
|
13
|
+
class AssociationUnsavedError < ChangeSerializationError; end
|
14
|
+
|
15
|
+
class ApplyDraftChangesError < StandardError; end
|
16
|
+
class NoDraftableError < ApplyDraftChangesError; end
|
17
|
+
class PriorDraftNotAppliedError < ApplyDraftChangesError; end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# @note It is strongly recommended that you do not directly create +Draft+
|
2
|
+
# objects, and instead use the supported public interface for doing so. See
|
3
|
+
# +DraftApprove::Draftable::ClassMethods+,
|
4
|
+
# +DraftApprove::Draftable::InstanceMethods+, and the README docs for this.
|
5
|
+
#
|
6
|
+
# ActiveRecord model for persisting data about draft changes.
|
7
|
+
#
|
8
|
+
# Each +Draft+ must be linked to a +DraftTransaction+, and must have a
|
9
|
+
# +draft_action_type+ which specifies whether this draft is to create a new
|
10
|
+
# record, update a record, or delete a record.
|
11
|
+
#
|
12
|
+
# If the draft is to update or delete an existing record in the database, the
|
13
|
+
# +Draft+ will also have a link to the +acts_as_draftable+ instance to which it
|
14
|
+
# relates, via the polymorphic +draftable+ association.
|
15
|
+
#
|
16
|
+
# Linking to the +acts_as_draftable+ instance is not possible for drafts which
|
17
|
+
# create new records, since the new record does not yet exist in the database!
|
18
|
+
# In these cases, the +draftable_type+ column is still set to the name of the
|
19
|
+
# class which is to be created, but the +draftable_id+ is +nil+.
|
20
|
+
#
|
21
|
+
# The +draft_changes+ attribute is a serialized representation of the draft
|
22
|
+
# changes. The representation is delegated to a +DraftApprove::Serialization+
|
23
|
+
# module. At present, there is only a JSON implementation, suitable for use with
|
24
|
+
# PostgreSQL databases.
|
25
|
+
#
|
26
|
+
# Note that saving 'no-op' +Draft+s is generally avoided by this library
|
27
|
+
# (specifically by the +DraftApprove::Persistor+ class).
|
28
|
+
#
|
29
|
+
# @see DraftTransaction
|
30
|
+
# @see DraftApprove::Draftable::ClassMethods
|
31
|
+
# @see DraftApprove::Draftable::InstanceMethods
|
32
|
+
class Draft < ActiveRecord::Base
|
33
|
+
# IMPORTANT NOTE: These constants are written to the database, so cannot be
|
34
|
+
# updated without requiring a migration of existing draft data
|
35
|
+
CREATE = 'create'.freeze
|
36
|
+
UPDATE = 'update'.freeze
|
37
|
+
DELETE = 'delete'.freeze
|
38
|
+
|
39
|
+
belongs_to :draft_transaction
|
40
|
+
belongs_to :draftable, polymorphic: true, optional: true
|
41
|
+
|
42
|
+
validates :draft_action_type, inclusion: {
|
43
|
+
in: [CREATE, UPDATE, DELETE],
|
44
|
+
message: "%{value} is not a valid Draft.draft_action_type"
|
45
|
+
}
|
46
|
+
|
47
|
+
scope :pending_approval, -> { joins(:draft_transaction).merge(DraftTransaction.pending_approval) }
|
48
|
+
scope :approved, -> { joins(:draft_transaction).merge(DraftTransaction.approved) }
|
49
|
+
scope :rejected, -> { joins(:draft_transaction).merge(DraftTransaction.rejected) }
|
50
|
+
scope :approval_error, -> { joins(:draft_transaction).merge(DraftTransaction.approval_error) }
|
51
|
+
|
52
|
+
# @return [Boolean] +true+ if this +Draft+ is to create a new record, +false+
|
53
|
+
# otherwise
|
54
|
+
def create?
|
55
|
+
draft_action_type == CREATE
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [Boolean] +true+ if this +Draft+ is to update an existing record,
|
59
|
+
# +false+ otherwise
|
60
|
+
def update?
|
61
|
+
draft_action_type == UPDATE
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [Boolean] +true+ if this +Draft+ is to delete an existing record,
|
65
|
+
# +false+ otherwise
|
66
|
+
def delete?
|
67
|
+
draft_action_type == DELETE
|
68
|
+
end
|
69
|
+
|
70
|
+
# Apply the changes in this draft, writing them to the database
|
71
|
+
# @api private
|
72
|
+
def apply_changes!
|
73
|
+
DraftApprove::Persistor.write_model_from_draft(self)
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# @note It is strongly recommended that you do not directly create
|
2
|
+
# +DraftTransaction+ objects, and instead use the supported public interface
|
3
|
+
# for doing so. See +DraftApprove::Draftable::ClassMethods+ and the README
|
4
|
+
# docs for this.
|
5
|
+
#
|
6
|
+
# ActiveRecord model for persisting data about a group of draft changes which
|
7
|
+
# should be approved or rejected as a single, transactional group.
|
8
|
+
#
|
9
|
+
# Each +DraftTransaction+ has many linked +Draft+ objects.
|
10
|
+
#
|
11
|
+
# When a +DraftTransaction+ is first created, it has +status+ of
|
12
|
+
# +pending_approval+. The changes should then be reviewed and either approved or
|
13
|
+
# rejected.
|
14
|
+
#
|
15
|
+
# +DraftTransaction+ objects also have optional attribute for storing who or
|
16
|
+
# what created the transaction and the group of draft changes (+created_by+
|
17
|
+
# attribute), who or what reviewed the changes (+reviewed_by+ attribute), and
|
18
|
+
# the reason given for approving or rejecting the changes (+review_reason+
|
19
|
+
# attribute), and finally the stack trace of any error which occurred during
|
20
|
+
# the process of applying the changes (+error+ attribute).
|
21
|
+
#
|
22
|
+
# Arbitrary extra data can also be stored in the +extra_data+ attribute.
|
23
|
+
#
|
24
|
+
# Note that saving 'no-op' +DraftTransaction+s is generally avoided by this
|
25
|
+
# library (specifically by the +DraftApprove::Transaction+ class).
|
26
|
+
#
|
27
|
+
# @see Draft
|
28
|
+
# @see DraftApprove::Draftable::ClassMethods
|
29
|
+
class DraftTransaction < ActiveRecord::Base
|
30
|
+
# IMPORTANT NOTE: These constants are written to the database, so cannot be
|
31
|
+
# updated without requiring a migration of existing draft data
|
32
|
+
PENDING_APPROVAL = 'pending_approval'.freeze
|
33
|
+
APPROVED = 'approved'.freeze
|
34
|
+
REJECTED = 'rejected'.freeze
|
35
|
+
APPROVAL_ERROR = 'approval_error'.freeze
|
36
|
+
|
37
|
+
has_many :drafts
|
38
|
+
|
39
|
+
validates :status, inclusion: {
|
40
|
+
in: [PENDING_APPROVAL, APPROVED, REJECTED, APPROVAL_ERROR],
|
41
|
+
message: "%{value} is not a valid DraftTransaction.status"
|
42
|
+
}
|
43
|
+
|
44
|
+
scope :pending_approval, -> { where(status: PENDING_APPROVAL) }
|
45
|
+
scope :approved, -> { where(status: APPROVED) }
|
46
|
+
scope :rejected, -> { where(status: REJECTED) }
|
47
|
+
scope :approval_error, -> { where(status: APPROVAL_ERROR) }
|
48
|
+
|
49
|
+
# Approve all changes in this +DraftTransaction+ and immediately apply them
|
50
|
+
# to the database.
|
51
|
+
#
|
52
|
+
# Note that applying the changes occurs within a database transaction.
|
53
|
+
#
|
54
|
+
# @param reviewed_by [String] the user or process which approved these changes
|
55
|
+
# @param review_reason [String] the reason for approving these changes
|
56
|
+
#
|
57
|
+
# @return [Boolean] +true+ if the changes were successfully applied
|
58
|
+
def approve_changes!(reviewed_by: nil, review_reason: nil)
|
59
|
+
begin
|
60
|
+
ActiveRecord::Base.transaction do
|
61
|
+
self.lock! # Avoid multiple threads applying changes concurrently
|
62
|
+
return false unless self.status == PENDING_APPROVAL
|
63
|
+
|
64
|
+
drafts.order(:created_at, :id).each do |draft|
|
65
|
+
draft.apply_changes!
|
66
|
+
end
|
67
|
+
|
68
|
+
self.update!(status: APPROVED, reviewed_by: reviewed_by, review_reason: review_reason)
|
69
|
+
return true
|
70
|
+
end
|
71
|
+
rescue StandardError => e
|
72
|
+
# Log the error in the database table and re-raise
|
73
|
+
self.update!(status: APPROVAL_ERROR, error: "#{e.inspect}\n#{e.backtrace.join("\n")}")
|
74
|
+
raise
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Reject all changes in this +DraftTransaction+.
|
79
|
+
#
|
80
|
+
# @param reviewed_by [String] the user or process which rejected these changes
|
81
|
+
# @param review_reason [String] the reason for rejecting these changes
|
82
|
+
#
|
83
|
+
# @return [Boolean] +true+ if the changes were successfully rejected
|
84
|
+
def reject_changes!(reviewed_by: nil, review_reason: nil)
|
85
|
+
self.update!(status: REJECTED, reviewed_by: reviewed_by, review_reason: review_reason)
|
86
|
+
return true
|
87
|
+
end
|
88
|
+
|
89
|
+
# Get a +DraftChangesProxy+ for the given object in the scope of this
|
90
|
+
# +DraftTransaction+.
|
91
|
+
#
|
92
|
+
# @param object [Object] the +Draft+ or +acts_as_draftable+ object to
|
93
|
+
# create a +DraftChangesProxy+ for
|
94
|
+
#
|
95
|
+
# @return [DraftChangesProxy] a proxy to get changes drafted to the given
|
96
|
+
# object and related objects, within the scope of this +DraftTransaction+
|
97
|
+
#
|
98
|
+
# @see DraftApprove::DraftChangesProxy
|
99
|
+
def draft_proxy_for(object)
|
100
|
+
serialization_module.get_draft_changes_proxy.new(object, self)
|
101
|
+
end
|
102
|
+
|
103
|
+
# @return the module used for serialization by this +DraftTransaction+.
|
104
|
+
#
|
105
|
+
# @api private
|
106
|
+
def serialization_module
|
107
|
+
Object.const_get(self.serialization)
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'draft_approve/errors'
|
2
|
+
require 'draft_approve/models/draft'
|
3
|
+
require 'draft_approve/serialization/json'
|
4
|
+
|
5
|
+
module DraftApprove
|
6
|
+
|
7
|
+
# Logic for writing a +Draft+ to the database, and for applying changes
|
8
|
+
# contained within a single +Draft+ and saving them to the database.
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class Persistor
|
12
|
+
DEFAULT_CREATE_METHOD = 'create!'.freeze
|
13
|
+
DEFAULT_UPDATE_METHOD = 'update!'.freeze
|
14
|
+
DEFAULT_DELETE_METHOD = 'destroy!'.freeze
|
15
|
+
private_constant :DEFAULT_CREATE_METHOD, :DEFAULT_UPDATE_METHOD, :DEFAULT_DELETE_METHOD
|
16
|
+
|
17
|
+
# IMPORTANT NOTE: These constants are written to the database, so cannot be
|
18
|
+
# updated without requiring a migration of existing draft data. Such a
|
19
|
+
# migration may be very slow, since these constants are embedded in the
|
20
|
+
# JSON generated by this serializer!
|
21
|
+
CREATE_METHOD = 'create_method'.freeze
|
22
|
+
UPDATE_METHOD = 'update_method'.freeze
|
23
|
+
DELETE_METHOD = 'delete_method'.freeze
|
24
|
+
private_constant :CREATE_METHOD, :UPDATE_METHOD, :DELETE_METHOD
|
25
|
+
|
26
|
+
# Write a +Draft+ object to the database to persist any changes to the
|
27
|
+
# given model.
|
28
|
+
#
|
29
|
+
# @param action_type [String] the type of action this draft will represent -
|
30
|
+
# +CREATE+, +UPDATE+, or +DELETE+
|
31
|
+
# @param model [Object] the +acts_as_draftable+ ActiveRecord model whose
|
32
|
+
# changes will be saved to the database
|
33
|
+
# @param options [Hash] the options to use when saving this draft, see
|
34
|
+
# +DraftApprove::Draftable::InstanceMethods#draft_save!+ and
|
35
|
+
# +DraftApprove::Draftable::InstanceMethods#draft_destroy!+ for details of
|
36
|
+
# valid options
|
37
|
+
#
|
38
|
+
# @return [Draft, false] the +Draft+ record which was created, or +false+ if
|
39
|
+
# there were no changes (ie. the result would have been a 'no-op' change)
|
40
|
+
#
|
41
|
+
# @see DraftApprove::Draftable::InstanceMethods#draft_save!
|
42
|
+
# @see DraftApprove::Draftable::InstanceMethods#draft_destroy!
|
43
|
+
def self.write_draft_from_model(action_type, model, options = nil)
|
44
|
+
raise(ArgumentError, 'model argument must be present') unless model.present?
|
45
|
+
|
46
|
+
if validate_model?(options) && model.invalid?
|
47
|
+
raise(ActiveRecord::RecordInvalid, model)
|
48
|
+
end
|
49
|
+
|
50
|
+
DraftApprove::Transaction.ensure_in_draft_transaction do
|
51
|
+
# Now we're in a Transaction, ensure we don't get multiple drafts for the same object
|
52
|
+
if model.persisted? && Draft.pending_approval.where(draftable: model).count > 0
|
53
|
+
raise(DraftApprove::Errors::ExistingDraftError, "#{model} has existing draft")
|
54
|
+
end
|
55
|
+
|
56
|
+
case action_type
|
57
|
+
when Draft::CREATE
|
58
|
+
raise(DraftApprove::Errors::AlreadyPersistedModelError, "#{model} is already persisted") if model.persisted?
|
59
|
+
draftable_type = model.class.name
|
60
|
+
draftable_id = nil
|
61
|
+
when Draft::UPDATE
|
62
|
+
raise(DraftApprove::Errors::UnpersistedModelError, "#{model} isn't persisted") unless model.persisted?
|
63
|
+
draftable_type = model.class.name
|
64
|
+
draftable_id = model.id
|
65
|
+
when Draft::DELETE
|
66
|
+
raise(DraftApprove::Errors::UnpersistedModelError, "#{model} isn't persisted") unless model.persisted?
|
67
|
+
draftable_type = model.class.name
|
68
|
+
draftable_id = model.id
|
69
|
+
else
|
70
|
+
raise(ArgumentError, "Unknown draft_action_type #{action_type}")
|
71
|
+
end
|
72
|
+
|
73
|
+
draft_transaction = DraftApprove::Transaction.current_draft_transaction!
|
74
|
+
draft_options = sanitize_options_for_db(options)
|
75
|
+
serializer = serializer_class(draft_transaction)
|
76
|
+
changes = serializer.changes_for_model(model)
|
77
|
+
|
78
|
+
# Don't write no-op updates!
|
79
|
+
return false if changes.empty? && action_type == Draft::UPDATE
|
80
|
+
|
81
|
+
return model.draft_pending_approval = Draft.create!(
|
82
|
+
draft_transaction: draft_transaction,
|
83
|
+
draftable_type: draftable_type,
|
84
|
+
draftable_id: draftable_id,
|
85
|
+
draft_action_type: action_type,
|
86
|
+
draft_changes: changes,
|
87
|
+
draft_options: draft_options
|
88
|
+
)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Write the changes represented by the given +Draft+ object to the database.
|
93
|
+
#
|
94
|
+
# Depending upon the type of +Draft+, this method may create a new record in
|
95
|
+
# the database, update an existing record, or delete a record.
|
96
|
+
#
|
97
|
+
# @param draft [Draft] the +Draft+ object whose changes should be applied
|
98
|
+
# and persisted to the database
|
99
|
+
#
|
100
|
+
# @return [Object] the +acts_as_draftable+ ActiveRecord model which has been
|
101
|
+
# created, updated, or deleted
|
102
|
+
def self.write_model_from_draft(draft)
|
103
|
+
serializer = serializer_class(draft.draft_transaction)
|
104
|
+
new_values_hash = serializer.new_values_for_draft(draft)
|
105
|
+
options = draft.draft_options || {}
|
106
|
+
|
107
|
+
case draft.draft_action_type
|
108
|
+
when Draft::CREATE
|
109
|
+
raise(DraftApprove::Errors::NoDraftableError, "No draftable_type for #{draft}") if draft.draftable_type.blank?
|
110
|
+
|
111
|
+
create_method = (options.include?(CREATE_METHOD) ? options[CREATE_METHOD] : DEFAULT_CREATE_METHOD)
|
112
|
+
|
113
|
+
model_class = Object.const_get(draft.draftable_type)
|
114
|
+
model = model_class.send(create_method, new_values_hash)
|
115
|
+
|
116
|
+
# We've only just persisted the model, the draft can't have referenced it before!
|
117
|
+
draft.update!(draftable: model)
|
118
|
+
|
119
|
+
return model
|
120
|
+
when Draft::UPDATE
|
121
|
+
raise(DraftApprove::Errors::NoDraftableError, "No draftable for #{draft}") if draft.draftable.blank?
|
122
|
+
|
123
|
+
update_method = (options.include?(UPDATE_METHOD) ? options[UPDATE_METHOD] : DEFAULT_UPDATE_METHOD)
|
124
|
+
|
125
|
+
model = draft.draftable
|
126
|
+
model.send(update_method, new_values_hash)
|
127
|
+
return model
|
128
|
+
when Draft::DELETE
|
129
|
+
raise(DraftApprove::Errors::NoDraftableError, "No draftable for #{draft}") if draft.draftable.blank?
|
130
|
+
|
131
|
+
delete_method = (options.include?(DELETE_METHOD) ? options[DELETE_METHOD] : DEFAULT_DELETE_METHOD)
|
132
|
+
|
133
|
+
model = draft.draftable
|
134
|
+
model.send(delete_method)
|
135
|
+
return model
|
136
|
+
else
|
137
|
+
raise(ArgumentError, "Unknown draft_action_type #{draft.draft_action_type}")
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
|
143
|
+
# Helper to determine whether to validate a model before writing a draft
|
144
|
+
def self.validate_model?(options)
|
145
|
+
options ||= {}
|
146
|
+
options.fetch(:validate, true)
|
147
|
+
end
|
148
|
+
|
149
|
+
# Helper to get the serialization class to use
|
150
|
+
def self.serializer_class(draft_transaction)
|
151
|
+
draft_transaction.serialization_module.get_serializer
|
152
|
+
end
|
153
|
+
|
154
|
+
# Helper to remove invalid options before they get persisted to the database
|
155
|
+
def self.sanitize_options_for_db(options)
|
156
|
+
return nil if !options || options.empty?
|
157
|
+
|
158
|
+
draft_options_keys = [CREATE_METHOD, UPDATE_METHOD, DELETE_METHOD]
|
159
|
+
|
160
|
+
accepted_options = options.each_with_object({}) do |(key, value), accepted_opts|
|
161
|
+
accepted_opts[key.to_s] = value if draft_options_keys.include?(key.to_s)
|
162
|
+
end
|
163
|
+
|
164
|
+
return (accepted_options.empty? ? nil : accepted_options)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|