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?