Skip to content

Instantly share code, notes, and snippets.

@stan
Created September 27, 2012 10:08
Show Gist options
  • Select an option

  • Save stan/3793263 to your computer and use it in GitHub Desktop.

Select an option

Save stan/3793263 to your computer and use it in GitHub Desktop.

Revisions

  1. s12chung created this gist Sep 26, 2011.
    86 changes: 86 additions & 0 deletions undo.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,86 @@
    module Mongoid
    module Undo
    extend ActiveSupport::Concern

    included do
    include Mongoid::Document

    field :version, type: Integer, default: 0
    before_save :prepare_versioning
    after_save :create_version
    before_destroy :version_destroy
    end

    def versions; ::Version.where(obj_id: id) end
    def versions_gt(version = self.version); versions.where(:version.gt => version) end
    def version_at(version = self.version); versions.where(version: version).first end
    def current_version; version_at end
    def previous_version; version_at version - 1 end

    def versioned?; version > 0 end
    def has?(ver)
    ver.obj_id == id && ver.obj_class == self.class.to_s
    end
    def matches?(ver)
    has?(ver) &&
    ver.obj_attributes == attributes &&
    ver.version == version
    end

    def undo!; rollback_to!(version - 1) end

    def rollback_to!(version)
    unless persisted?; return false end
    if version == 0
    versions.destroy
    without_versioning :destroy
    attributes
    else
    ver = version_at(version)
    unless ver; return false end

    ver.rollback_to(self)
    end
    end

    def without_versioning(method = nil)
    @without_versioning = true
    method ? method.to_proc.call(self) : yield
    ensure
    @without_versioning = false
    end

    private
    def match_persisted?
    current_version && attributes == current_version.obj_attributes
    end

    def prepare_versioning
    if !@without_versioning
    if version == 0 && persisted?
    self.version += 1
    old_self = self.class.find(id)
    old_self.version += 1
    old_self.send(:create_version)
    end
    if !match_persisted?; self.version += 1 end
    end
    end

    def create_version(destroy = false)
    if !@without_versioning && (destroy || !match_persisted?)
    ver = ::Version.new(obj_id: id, obj_class: self.class.to_s, version: version)
    unless destroy; ver.attributes = ver.attributes.merge({:obj_attributes => attributes}) end
    ver.save!
    end
    end

    def version_destroy
    if !@without_versioning
    self.version += 1
    without_versioning(:save)
    create_version(true)
    end
    end
    end
    end
    48 changes: 48 additions & 0 deletions version.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,48 @@
    module Mongoid::Undo
    module Version
    extend ActiveSupport::Concern

    included do
    include Mongoid::Document
    include Mongoid::Timestamps::Created

    field :obj_id
    field :obj_class
    field :obj_attributes, type: Hash
    field :version, type: Integer
    validates_presence_of(fields.keys - ['_id', '_type', 'created_at', 'obj_attributes'])
    end

    def obj; obj_class.constantize.find(obj_id) end

    def created?; version == 1 end
    def deleted?; !obj_attributes end
    def modified?; !(created? || deleted?) end
    def latest?(obj = nil)
    unless obj; obj = self.obj end
    obj.nil? || version == obj.version
    end

    def rollback_to(obj = nil)
    if !deleted?
    if obj.nil? || obj.id != obj_id; obj = self.obj end
    if obj.nil?; obj = obj_class.constantize.new end

    obj.versions_gt(version).each {|ver| ver.destroy }
    rollback(obj)
    else
    false
    end
    end

    private

    def rollback(obj = nil)
    old_attributes = obj.attributes
    obj.attributes = obj_attributes
    obj.without_versioning :save

    old_attributes
    end
    end
    end