lib/draftsman/model.rb in draftsman-0.3.7 vs lib/draftsman/model.rb in draftsman-0.4.0
- old
+ new
@@ -43,10 +43,14 @@
def has_drafts(options = {})
# Lazily include the instance methods so we don't clutter up
# any more ActiveRecord models than we need to.
send :include, InstanceMethods
+ # Define before/around/after callbacks on each drafted model
+ send :extend, ActiveModel::Callbacks
+ define_model_callbacks :draft_creation, :draft_update, :draft_destruction, :draft_destroy
+
class_attribute :draftsman_options
self.draftsman_options = options.dup
class_attribute :draft_association_name
self.draft_association_name = options[:draft] || :draft
@@ -192,37 +196,159 @@
end
# Creates object and records a draft for the object's creation. Returns `true` or `false` depending on whether or not
# the objects passed validation and the save was successful.
def draft_creation
- transaction do
- # We want to save the draft after create
- return false unless self.save
+ run_callbacks :draft_creation do
+ transaction do
+ # We want to save the draft after create
+ return false unless self.save
- data = {
- :item => self,
- :event => 'create',
- :whodunnit => Draftsman.whodunnit,
- :object => object_attrs_for_draft_record
- }
- data[:object_changes] = changes_for_draftsman(previous_changes: true) if track_object_changes_for_draft?
- data = merge_metadata_for_draft(data)
+ data = {
+ :item => self,
+ :event => 'create',
+ :whodunnit => Draftsman.whodunnit,
+ :object => object_attrs_for_draft_record
+ }
+ data[:object_changes] = changes_for_draftsman(previous_changes: true) if track_object_changes_for_draft?
+ data = merge_metadata_for_draft(data)
- send "build_#{self.class.draft_association_name}", data
+ send "build_#{self.class.draft_association_name}", data
- if send(self.class.draft_association_name).save
- write_attribute "#{self.class.draft_association_name}_id", send(self.class.draft_association_name).id
- self.update_column "#{self.class.draft_association_name}_id", send(self.class.draft_association_name).id
- return true
- else
- raise ActiveRecord::Rollback and return false
+ if send(self.class.draft_association_name).save
+ write_attribute "#{self.class.draft_association_name}_id", send(self.class.draft_association_name).id
+ self.update_column "#{self.class.draft_association_name}_id", send(self.class.draft_association_name).id
+ else
+ raise ActiveRecord::Rollback and return false
+ end
end
end
+ return true
end
- # Trashes object and records a draft for a `destroy` event.
+ # DEPRECATED: Use `draft_destruction` instead.
def draft_destroy
+ ActiveSupport::Deprecation.warn('`draft_destroy` is deprecated and will be removed from Draftsman 1.0. Use `draft_destruction` instead.')
+
+ run_callbacks :draft_destroy do
+ _draft_destruction
+ end
+ end
+
+ # Trashes object and records a draft for a `destroy` event.
+ def draft_destruction
+ run_callbacks :draft_destruction do
+ _draft_destruction
+ end
+ end
+
+ # Updates object and records a draft for an `update` event. If the draft is being updated to the object's original
+ # state, the draft is destroyed. Returns `true` or `false` depending on if the object passed validation and the save
+ # was successful.
+ def draft_update
+ run_callbacks :draft_update do
+ transaction do
+ save_only_columns_for_draft
+
+ # We want to save the draft before update
+ return false unless self.valid?
+
+ # If updating a creation draft, also update this item
+ if self.draft? && send(self.class.draft_association_name).create?
+ data = {
+ :item => self,
+ :whodunnit => Draftsman.whodunnit,
+ :object => object_attrs_for_draft_record
+ }
+
+ if track_object_changes_for_draft?
+ data[:object_changes] = changes_for_draftsman(changed_from: self.send(self.class.draft_association_name).changeset)
+ end
+ data = merge_metadata_for_draft(data)
+ send(self.class.draft_association_name).update_attributes data
+ self.save
+ # Destroy the draft if this record has changed back to the original record
+ elsif changed_to_original_for_draft?
+ nilified_draft = send(self.class.draft_association_name)
+ send "#{self.class.draft_association_name}_id=", nil
+ self.save
+ nilified_draft.destroy
+ # Save a draft if record is changed notably
+ elsif changed_notably_for_draft?
+ data = {
+ :item => self,
+ :whodunnit => Draftsman.whodunnit,
+ :object => object_attrs_for_draft_record
+ }
+ data = merge_metadata_for_draft(data)
+
+ # If there's already a draft, update it.
+ if send(self.class.draft_association_name).present?
+ data[:object_changes] = changes_for_draftsman if track_object_changes_for_draft?
+ send(self.class.draft_association_name).update_attributes data
+ update_skipped_attributes
+ # If there's not draft, create an update draft.
+ else
+ data[:event] = 'update'
+ data[:object_changes] = changes_for_draftsman if track_object_changes_for_draft?
+ send "build_#{self.class.draft_association_name}", data
+
+ if send(self.class.draft_association_name).save
+ update_column "#{self.class.draft_association_name}_id", send(self.class.draft_association_name).id
+ update_skipped_attributes
+ else
+ raise ActiveRecord::Rollback and return false
+ end
+ end
+ # If record is a draft and not changed notably, then update the draft.
+ elsif self.draft?
+ data = {
+ :item => self,
+ :whodunnit => Draftsman.whodunnit,
+ :object => object_attrs_for_draft_record
+ }
+ data[:object_changes] = changes_for_draftsman(changed_from: @object.draft.changeset) if track_object_changes_for_draft?
+ data = merge_metadata_for_draft(data)
+ send(self.class.draft_association_name).update_attributes data
+ update_skipped_attributes
+ # Otherwise, just save the record
+ else
+ self.save
+ end
+ end
+ end
+ rescue Exception => e
+ false
+ end
+
+ # Returns serialized object representing this drafted item.
+ def object_attrs_for_draft_record(object = nil)
+ object ||= self
+
+ _attrs = object.attributes.except(*self.class.draftsman_options[:skip]).tap do |attributes|
+ self.class.serialize_attributes_for_draftsman attributes
+ end
+
+ self.class.draft_class.object_col_is_json? ? _attrs : Draftsman.serializer.dump(_attrs)
+ end
+
+ # Returns whether or not this item has been published at any point in its lifecycle.
+ def published?
+ self.published_at.present?
+ end
+
+ # Returns whether or not this item has been trashed
+ def trashed?
+ send(self.class.trashed_at_attribute_name).present?
+ end
+
+ private
+
+ # This is only abstracted away at this moment because of the
+ # `draft_destroy` deprecation. Move all of this logic back into
+ # `draft_destruction` after `draft_destroy is removed.`
+ def _draft_destruction
transaction do
data = {
:item => self,
:event => 'destroy',
:whodunnit => Draftsman.whodunnit,
@@ -261,118 +387,17 @@
if association.klass.draftable? && association.options.has_key?(:dependent) && association.options[:dependent] == :destroy
dependents = self.send(association.name)
dependents = [dependents] if (dependents && association.macro == :has_one)
dependents.each do |dependent|
- dependent.draft_destroy unless dependent.draft? && dependent.send(dependent.class.draft_association_name).destroy?
+ dependent.draft_destruction unless dependent.draft? && dependent.send(dependent.class.draft_association_name).destroy?
end if dependents
end
end
end
end
- # Updates object and records a draft for an `update` event. If the draft is being updated to the object's original
- # state, the draft is destroyed. Returns `true` or `false` depending on if the object passed validation and the save
- # was successful.
- def draft_update
- transaction do
- save_only_columns_for_draft
-
- # We want to save the draft before update
- return false unless self.valid?
-
- # If updating a creation draft, also update this item
- if self.draft? && send(self.class.draft_association_name).create?
- data = {
- :item => self,
- :whodunnit => Draftsman.whodunnit,
- :object => object_attrs_for_draft_record
- }
-
- if track_object_changes_for_draft?
- data[:object_changes] = changes_for_draftsman(changed_from: self.send(self.class.draft_association_name).changeset)
- end
-
- data = merge_metadata_for_draft(data)
- send(self.class.draft_association_name).update_attributes data
- self.save
- # Destroy the draft if this record has changed back to the original record
- elsif changed_to_original_for_draft?
- nilified_draft = send(self.class.draft_association_name)
- send "#{self.class.draft_association_name}_id=", nil
- self.save
- nilified_draft.destroy
- # Save a draft if record is changed notably
- elsif changed_notably_for_draft?
- data = {
- :item => self,
- :whodunnit => Draftsman.whodunnit,
- :object => object_attrs_for_draft_record
- }
- data = merge_metadata_for_draft(data)
-
- # If there's already a draft, update it.
- if send(self.class.draft_association_name).present?
- data[:object_changes] = changes_for_draftsman if track_object_changes_for_draft?
- send(self.class.draft_association_name).update_attributes data
- update_skipped_attributes
- # If there's not draft, create an update draft.
- else
- data[:event] = 'update'
- data[:object_changes] = changes_for_draftsman if track_object_changes_for_draft?
- send "build_#{self.class.draft_association_name}", data
-
- if send(self.class.draft_association_name).save
- update_column "#{self.class.draft_association_name}_id", send(self.class.draft_association_name).id
- update_skipped_attributes
- else
- raise ActiveRecord::Rollback and return false
- end
- end
- # If record is a draft and not changed notably, then update the draft.
- elsif self.draft?
- data = {
- :item => self,
- :whodunnit => Draftsman.whodunnit,
- :object => object_attrs_for_draft_record
- }
- data[:object_changes] = changes_for_draftsman(changed_from: @object.draft.changeset) if track_object_changes_for_draft?
- data = merge_metadata_for_draft(data)
- send(self.class.draft_association_name).update_attributes data
- update_skipped_attributes
- # Otherwise, just save the record
- else
- self.save
- end
- end
- rescue Exception => e
- false
- end
-
- # Returns serialized object representing this drafted item.
- def object_attrs_for_draft_record(object = nil)
- object ||= self
-
- _attrs = object.attributes.except(*self.class.draftsman_options[:skip]).tap do |attributes|
- self.class.serialize_attributes_for_draftsman attributes
- end
-
- self.class.draft_class.object_col_is_json? ? _attrs : Draftsman.serializer.dump(_attrs)
- end
-
- # Returns whether or not this item has been published at any point in its lifecycle.
- def published?
- self.published_at.present?
- end
-
- # Returns whether or not this item has been trashed
- def trashed?
- send(self.class.trashed_at_attribute_name).present?
- end
-
- private
-
# Returns changes on this object, excluding attributes defined in the options for `:ignore` and `:skip`.
def changed_and_not_ignored_for_draft(options = {})
options[:previous_changes] ||= false
my_changed = options[:previous_changes] ? previous_changes.keys : self.changed
@@ -450,10 +475,10 @@
# Save columns outside of the `only` option directly to master table
def save_only_columns_for_draft
if self.class.draftsman_options[:only].any?
only_changes = {}
only_changed_attributes = self.changed - self.class.draftsman_options[:only]
-
+
only_changed_attributes.each do |attribute|
only_changes[attribute] = self.changes[attribute].last
end
self.update_columns only_changes if only_changes.any?