memento 0.3.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.
- data/.gitignore +5 -0
- data/LICENSE +20 -0
- data/README.rdoc +85 -0
- data/Rakefile +33 -0
- data/TODO +3 -0
- data/VERSION.yml +5 -0
- data/generators/memento_migration/memento_migration_generator.rb +10 -0
- data/generators/memento_migration/templates/migration.rb +23 -0
- data/lib/memento.rb +59 -0
- data/lib/memento/action.rb +40 -0
- data/lib/memento/action/create.rb +37 -0
- data/lib/memento/action/destroy.rb +30 -0
- data/lib/memento/action/update.rb +55 -0
- data/lib/memento/action_controller_methods.rb +19 -0
- data/lib/memento/active_record_methods.rb +74 -0
- data/lib/memento/result.rb +36 -0
- data/lib/memento/session.rb +29 -0
- data/lib/memento/state.rb +47 -0
- data/memento.gemspec +79 -0
- data/rails/init.rb +1 -0
- data/spec/memento/action/create_spec.rb +82 -0
- data/spec/memento/action/destroy_spec.rb +43 -0
- data/spec/memento/action/update_spec.rb +213 -0
- data/spec/memento/action_controller_methods_spec.rb +55 -0
- data/spec/memento/active_record_methods_spec.rb +65 -0
- data/spec/memento/result_spec.rb +89 -0
- data/spec/memento/session_spec.rb +117 -0
- data/spec/memento/state_spec.rb +55 -0
- data/spec/memento_spec.rb +169 -0
- data/spec/spec_helper.rb +91 -0
- metadata +95 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
module Memento::ActiveRecordMethods
|
2
|
+
|
3
|
+
IGNORE_ATTRIBUTES = [:updated_at, :created_at]
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.send :extend, ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
def memento_changes(*action_types)
|
12
|
+
include InstanceMethods
|
13
|
+
|
14
|
+
self.memento_options = action_types.last.is_a?(Hash) ? action_types.pop : {}
|
15
|
+
|
16
|
+
action_types = Memento::Action::Base.action_types if action_types.empty?
|
17
|
+
action_types.map!(&:to_s).uniq!
|
18
|
+
unless (invalid = action_types - Memento::Action::Base.action_types).empty?
|
19
|
+
raise ArgumentError.new("Invalid memento_changes: #{invalid.to_sentence}; allowed are only #{Memento::Action::Base.action_types.to_sentence}")
|
20
|
+
end
|
21
|
+
|
22
|
+
action_types.each do |action_type|
|
23
|
+
callback_exists = send(:"after_#{action_type}_callback_chain").any? do |callback|
|
24
|
+
callback.method.to_sym == :"record_#{action_type}"
|
25
|
+
end
|
26
|
+
send :"after_#{action_type}", :"record_#{action_type}" unless callback_exists
|
27
|
+
end
|
28
|
+
|
29
|
+
has_many :memento_states, :class_name => "Memento::State", :as => :record
|
30
|
+
end
|
31
|
+
|
32
|
+
def memento_options
|
33
|
+
read_inheritable_attribute(:memento_options) || write_inheritable_attribute(:memento_options,{})
|
34
|
+
end
|
35
|
+
|
36
|
+
def memento_options=(options)
|
37
|
+
options.symbolize_keys!
|
38
|
+
options[:ignore] = [options[:ignore]].flatten.map(&:to_sym) if options[:ignore]
|
39
|
+
write_inheritable_attribute(:memento_options, memento_options.merge(options))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module InstanceMethods
|
44
|
+
|
45
|
+
def attributes_for_memento
|
46
|
+
filter_attributes_for_memento(attributes)
|
47
|
+
end
|
48
|
+
|
49
|
+
def changes_for_memento
|
50
|
+
filter_attributes_for_memento(changes)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def filter_attributes_for_memento(hash)
|
56
|
+
hash.delete_if do |key, value|
|
57
|
+
ignore_attributes_for_memento.include?(key.to_sym)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def ignore_attributes_for_memento
|
62
|
+
Memento::ActiveRecordMethods::IGNORE_ATTRIBUTES + (self.class.memento_options[:ignore] || [])
|
63
|
+
end
|
64
|
+
|
65
|
+
Memento::Action::Base.action_types.each do |action_type|
|
66
|
+
define_method :"record_#{action_type}" do
|
67
|
+
Memento.instance.add_state(action_type, self)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
ActiveRecord::Base.send(:include, Memento::ActiveRecordMethods) if defined?(ActiveRecord::Base)
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class Memento::ResultArray < Array
|
2
|
+
|
3
|
+
def errors
|
4
|
+
self.find_all{ |result| result.failed? }
|
5
|
+
end
|
6
|
+
|
7
|
+
def failed?
|
8
|
+
self.any?{ |result| result.failed? }
|
9
|
+
end
|
10
|
+
|
11
|
+
def success?
|
12
|
+
!failed?
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
class Memento::Result
|
18
|
+
|
19
|
+
attr_reader :object, :state
|
20
|
+
|
21
|
+
def initialize(object, state)
|
22
|
+
@object, @state = object, state
|
23
|
+
end
|
24
|
+
|
25
|
+
def error
|
26
|
+
@object.errors[:memento_undo]
|
27
|
+
end
|
28
|
+
|
29
|
+
def failed?
|
30
|
+
!!error
|
31
|
+
end
|
32
|
+
|
33
|
+
def success?
|
34
|
+
!failed?
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Memento::Session < ActiveRecord::Base
|
2
|
+
set_table_name "memento_sessions"
|
3
|
+
|
4
|
+
has_many :states, :class_name => "Memento::State", :dependent => :delete_all
|
5
|
+
belongs_to :user
|
6
|
+
validates_presence_of :user
|
7
|
+
|
8
|
+
def add_state(action_type, record)
|
9
|
+
states.store(action_type, record)
|
10
|
+
end
|
11
|
+
|
12
|
+
def undo
|
13
|
+
states.map(&:undo).inject(Memento::ResultArray.new) do |results, result|
|
14
|
+
result.state.destroy if result.success?
|
15
|
+
results << result
|
16
|
+
end
|
17
|
+
ensure
|
18
|
+
destroy if states.count.zero?
|
19
|
+
end
|
20
|
+
|
21
|
+
def undo!
|
22
|
+
transaction do
|
23
|
+
returning(undo) do |results|
|
24
|
+
raise Memento::ErrorOnRewind if results.failed?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Memento::State < ActiveRecord::Base
|
2
|
+
set_table_name "memento_states"
|
3
|
+
|
4
|
+
belongs_to :session, :class_name => "Memento::Session"
|
5
|
+
belongs_to :record, :polymorphic => true
|
6
|
+
|
7
|
+
validates_presence_of :session
|
8
|
+
validates_presence_of :record
|
9
|
+
validates_presence_of :action_type
|
10
|
+
validates_inclusion_of :action_type, :in => Memento::Action::Base.action_types, :allow_blank => true
|
11
|
+
|
12
|
+
before_create :set_record_data
|
13
|
+
|
14
|
+
def self.store(action_type, record)
|
15
|
+
self.new(:action_type => action_type.to_s, :record => record) do |state|
|
16
|
+
state.save if state.fetch?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def undo
|
21
|
+
Memento::Result.new(action.undo, self)
|
22
|
+
end
|
23
|
+
|
24
|
+
def record_data
|
25
|
+
@record_data ||= Memento.serializer.load(read_attribute(:record_data))
|
26
|
+
end
|
27
|
+
|
28
|
+
def record_data=(data)
|
29
|
+
@record_data = nil
|
30
|
+
write_attribute(:record_data, Memento.serializer.dump(data))
|
31
|
+
end
|
32
|
+
|
33
|
+
def fetch?
|
34
|
+
action.fetch?
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def set_record_data
|
40
|
+
self.record_data = action.fetch
|
41
|
+
end
|
42
|
+
|
43
|
+
def action
|
44
|
+
"memento/action/#{action_type}".classify.constantize.new(self)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
data/memento.gemspec
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{memento}
|
8
|
+
s.version = "0.3.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Yolk Sebastian Munz & Julia Soergel GbR"]
|
12
|
+
s.date = %q{2010-01-26}
|
13
|
+
s.email = %q{sebastian@yo.lk}
|
14
|
+
s.extra_rdoc_files = [
|
15
|
+
"LICENSE",
|
16
|
+
"README.rdoc",
|
17
|
+
"TODO"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".gitignore",
|
21
|
+
"LICENSE",
|
22
|
+
"README.rdoc",
|
23
|
+
"Rakefile",
|
24
|
+
"TODO",
|
25
|
+
"VERSION.yml",
|
26
|
+
"generators/memento_migration/memento_migration_generator.rb",
|
27
|
+
"generators/memento_migration/templates/migration.rb",
|
28
|
+
"lib/memento.rb",
|
29
|
+
"lib/memento/action.rb",
|
30
|
+
"lib/memento/action/create.rb",
|
31
|
+
"lib/memento/action/destroy.rb",
|
32
|
+
"lib/memento/action/update.rb",
|
33
|
+
"lib/memento/action_controller_methods.rb",
|
34
|
+
"lib/memento/active_record_methods.rb",
|
35
|
+
"lib/memento/result.rb",
|
36
|
+
"lib/memento/session.rb",
|
37
|
+
"lib/memento/state.rb",
|
38
|
+
"memento.gemspec",
|
39
|
+
"rails/init.rb",
|
40
|
+
"spec/memento/action/create_spec.rb",
|
41
|
+
"spec/memento/action/destroy_spec.rb",
|
42
|
+
"spec/memento/action/update_spec.rb",
|
43
|
+
"spec/memento/action_controller_methods_spec.rb",
|
44
|
+
"spec/memento/active_record_methods_spec.rb",
|
45
|
+
"spec/memento/result_spec.rb",
|
46
|
+
"spec/memento/session_spec.rb",
|
47
|
+
"spec/memento/state_spec.rb",
|
48
|
+
"spec/memento_spec.rb",
|
49
|
+
"spec/spec_helper.rb"
|
50
|
+
]
|
51
|
+
s.homepage = %q{http://github.com/yolk/memento}
|
52
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
53
|
+
s.require_paths = ["lib"]
|
54
|
+
s.rubygems_version = %q{1.3.5}
|
55
|
+
s.summary = %q{Undo for Rails/ActiveRecord - covers destroy, update and create}
|
56
|
+
s.test_files = [
|
57
|
+
"spec/memento/action/create_spec.rb",
|
58
|
+
"spec/memento/action/destroy_spec.rb",
|
59
|
+
"spec/memento/action/update_spec.rb",
|
60
|
+
"spec/memento/action_controller_methods_spec.rb",
|
61
|
+
"spec/memento/active_record_methods_spec.rb",
|
62
|
+
"spec/memento/result_spec.rb",
|
63
|
+
"spec/memento/session_spec.rb",
|
64
|
+
"spec/memento/state_spec.rb",
|
65
|
+
"spec/memento_spec.rb",
|
66
|
+
"spec/spec_helper.rb"
|
67
|
+
]
|
68
|
+
|
69
|
+
if s.respond_to? :specification_version then
|
70
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
71
|
+
s.specification_version = 3
|
72
|
+
|
73
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
74
|
+
else
|
75
|
+
end
|
76
|
+
else
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'memento'
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
|
2
|
+
|
3
|
+
describe Memento::Action::Create, "when object is created" do
|
4
|
+
before do
|
5
|
+
setup_db
|
6
|
+
setup_data
|
7
|
+
Memento.instance.start(@user)
|
8
|
+
@project = Project.create!(:name => "P1", :closed_at => 3.days.ago).reload
|
9
|
+
Memento.instance.stop
|
10
|
+
end
|
11
|
+
|
12
|
+
after do
|
13
|
+
shutdown_db
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should create memento_state for ar-object with no data" do
|
17
|
+
Memento::State.count.should eql(1)
|
18
|
+
Memento::State.first.action_type.should eql("create")
|
19
|
+
Memento::State.first.record.should eql(@project) # it was destroyed, remember?
|
20
|
+
Memento::State.first.reload.record_data.should eql(nil)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should create object" do
|
24
|
+
Project.find_by_id(@project.id).should_not be_nil
|
25
|
+
Project.count.should eql(1)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should allow undoing the creation" do
|
29
|
+
Memento::Session.last.undo
|
30
|
+
Project.count.should eql(0)
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "when undoing the creation" do
|
34
|
+
it "should give back undone_object" do
|
35
|
+
Memento::Session.last.undo.map{|e| e.object.class }.should eql([Project])
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should not undo the creatio if object was modified" do
|
39
|
+
Project.last.update_attribute(:created_at, 1.minute.ago)
|
40
|
+
undone = Memento::Session.last.undo
|
41
|
+
Project.count.should eql(1)
|
42
|
+
undone.first.should_not be_success
|
43
|
+
undone.first.error.should be_was_changed
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "when record was already destroyed" do
|
47
|
+
it "should give back fake unsaved record with id set" do
|
48
|
+
Project.last.destroy
|
49
|
+
@undone = Memento::Session.last.undo
|
50
|
+
@undone.size.should eql(1)
|
51
|
+
@undone.first.object.should be_kind_of(Project)
|
52
|
+
@undone.first.object.id.should eql(@project.id)
|
53
|
+
@undone.first.object.name.should be_nil
|
54
|
+
@undone.first.object.should be_new_record
|
55
|
+
Project.count.should eql(0)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
describe Memento::Action::Create, "when object without timestamp is created" do
|
65
|
+
before do
|
66
|
+
setup_db
|
67
|
+
setup_data
|
68
|
+
Memento.instance.memento(@user) do
|
69
|
+
@obj = TimestamplessObject.create!(:name => "O1").reload
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
after do
|
74
|
+
shutdown_db
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "when undoing the creation" do
|
78
|
+
it "should give back undone_object" do
|
79
|
+
Memento::Session.last.undo.map{|e| e.object.class }.should eql([TimestamplessObject])
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
|
2
|
+
|
3
|
+
describe Memento::Action::Destroy, "when object gets destroyed" do
|
4
|
+
before do
|
5
|
+
setup_db
|
6
|
+
setup_data
|
7
|
+
@project = Project.create!(:name => "P1", :closed_at => 3.days.ago).reload
|
8
|
+
Memento.instance.start(@user)
|
9
|
+
@project.destroy
|
10
|
+
end
|
11
|
+
|
12
|
+
after do
|
13
|
+
Memento.instance.stop
|
14
|
+
shutdown_db
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should create memento_state for ar-object with full attributes_for_memento" do
|
18
|
+
Memento::State.count.should eql(1)
|
19
|
+
Memento::State.first.action_type.should eql("destroy")
|
20
|
+
Memento::State.first.record.should be_nil # it was destroyed, remember?
|
21
|
+
Memento::State.first.reload.record_data.should == @project.attributes_for_memento
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should destroy object" do
|
25
|
+
Project.find_by_id(@project.id).should be_nil
|
26
|
+
Project.count.should be_zero
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should allow undoing the destruction" do
|
30
|
+
Project.count.should be_zero
|
31
|
+
Memento::Session.last.undo
|
32
|
+
Project.count.should eql(1)
|
33
|
+
Project.first.attributes_for_memento.reject{|k, v| k.to_sym == :id }.should == (
|
34
|
+
@project.attributes_for_memento.reject{|k, v| k.to_sym == :id }
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should give back undone_object on undoing the destruction" do
|
39
|
+
Memento::Session.last.undo.map{|e| e.object.class }.should eql([Project])
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
|
2
|
+
|
3
|
+
describe Memento::Action::Update do
|
4
|
+
before do
|
5
|
+
setup_db
|
6
|
+
setup_data
|
7
|
+
@time1 = 3.days.ago
|
8
|
+
@time2 = 2.days.ago
|
9
|
+
@project = Project.create!(:name => "P1", :closed_at => @time1, :notes => "Bla bla").reload
|
10
|
+
@customer = Customer.create!(:name => "C1")
|
11
|
+
end
|
12
|
+
|
13
|
+
after do
|
14
|
+
shutdown_db
|
15
|
+
end
|
16
|
+
|
17
|
+
describe "when object gets updated" do
|
18
|
+
|
19
|
+
before do
|
20
|
+
Memento.instance.memento(@user) do
|
21
|
+
@project.update_attributes(:name => "P2", :closed_at => @time2, :customer => @customer, :notes => "Bla bla")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should create memento_state for ar-object from changes_for_memento" do
|
26
|
+
Memento::State.count.should eql(1)
|
27
|
+
Memento::State.first.action_type.should eql("update")
|
28
|
+
Memento::State.first.record.should eql(@project)
|
29
|
+
Memento::State.first.record_data.keys.sort.should eql(%w(name closed_at customer_id).sort)
|
30
|
+
Memento::State.first.record_data["name"].should eql(["P1", "P2"])
|
31
|
+
Memento::State.first.record_data["customer_id"].should eql([nil, @customer.id])
|
32
|
+
Memento::State.first.record_data["closed_at"][0].utc.to_s.should eql(@time1.utc.to_s)
|
33
|
+
Memento::State.first.record_data["closed_at"][1].utc.to_s.should eql(@time2.utc.to_s)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should update object" do
|
37
|
+
@project.reload.name.should eql("P2")
|
38
|
+
@project.customer.should eql(@customer)
|
39
|
+
@project.closed_at.to_s.should eql(@time2.to_s)
|
40
|
+
@project.should_not be_changed
|
41
|
+
Project.count.should eql(1)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should allow undoing the update" do
|
45
|
+
undone = Memento::Session.last.undo.first
|
46
|
+
undone.should be_success
|
47
|
+
undone.object.should_not be_changed
|
48
|
+
undone.object.name.should eql("P1")
|
49
|
+
undone.object.customer.should be_nil
|
50
|
+
undone.object.closed_at.to_s.should eql(@time1.to_s)
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "when record was destroyed before undo" do
|
54
|
+
before do
|
55
|
+
@project.destroy
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should return fake object with error" do
|
59
|
+
undone = Memento::Session.last.undo.first
|
60
|
+
undone.should_not be_success
|
61
|
+
undone.error.should be_was_destroyed
|
62
|
+
undone.object.class.should eql(Project)
|
63
|
+
undone.object.id.should eql(1)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "when record was changed before undo" do
|
68
|
+
|
69
|
+
describe "with mergeable unrecorded changes" do
|
70
|
+
before do
|
71
|
+
@project.update_attributes({:notes => "Bla!"})
|
72
|
+
@result = Memento::Session.first.undo.first
|
73
|
+
@object = @result.object
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should be success" do
|
77
|
+
@result.should be_success
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should return correctly updated object" do
|
81
|
+
@object.class.should eql(Project)
|
82
|
+
@object.name.should eql("P1")
|
83
|
+
@object.customer.should be_nil
|
84
|
+
@object.closed_at.to_s.should eql(@time1.to_s)
|
85
|
+
@object.notes.should eql("Bla!")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "with mergeable recorded changes" do
|
90
|
+
before do
|
91
|
+
Memento.instance.memento(@user) do
|
92
|
+
@project.update_attributes({:notes => "Bla!"})
|
93
|
+
end
|
94
|
+
Memento::State.last.update_attribute(:created_at, 1.minute.from_now)
|
95
|
+
@result = Memento::Session.first.undo.first
|
96
|
+
@object = @result.object
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should be success" do
|
100
|
+
@result.should be_success
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should return correctly updated object" do
|
104
|
+
@object.class.should eql(Project)
|
105
|
+
@object.name.should eql("P1")
|
106
|
+
@object.customer.should be_nil
|
107
|
+
@object.closed_at.to_s.should eql(@time1.to_s)
|
108
|
+
@object.notes.should eql("Bla!")
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "when second state is undone" do
|
112
|
+
before do
|
113
|
+
@result = Memento::Session.first.undo.first
|
114
|
+
@object = @result.object
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should be success" do
|
118
|
+
@result.should be_success
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should return correctly updated object" do
|
122
|
+
@object.class.should eql(Project)
|
123
|
+
@object.name.should eql("P1")
|
124
|
+
@object.customer.should be_nil
|
125
|
+
@object.closed_at.to_s.should eql(@time1.to_s)
|
126
|
+
@object.notes.should eql("Bla bla")
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "with unmergeable unrecorded changes" do
|
132
|
+
before do
|
133
|
+
@project.update_attributes({:name => "P3"})
|
134
|
+
@result = Memento::Session.last.undo.first
|
135
|
+
@object = @result.object
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should fail" do
|
139
|
+
@result.should be_failed
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should set error" do
|
143
|
+
@result.error.should be_was_changed
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should return not undone object" do
|
147
|
+
@object.name.should eql("P3")
|
148
|
+
@object.customer.should eql(@customer)
|
149
|
+
@object.closed_at.to_s.should eql(@time2.to_s)
|
150
|
+
@object.should_not be_changed
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
describe "with unmergeable recorded changes" do
|
155
|
+
before do
|
156
|
+
Memento.instance.memento(@user) do
|
157
|
+
@project.update_attributes!({:name => "P3"})
|
158
|
+
end
|
159
|
+
Memento::State.last.update_attribute(:created_at, 1.minute.from_now)
|
160
|
+
@result = Memento::Session.first.undo.first
|
161
|
+
@object = @result.object
|
162
|
+
end
|
163
|
+
|
164
|
+
it "should fail" do
|
165
|
+
@result.should be_failed
|
166
|
+
end
|
167
|
+
|
168
|
+
it "should return not undone object" do
|
169
|
+
@object.name.should eql("P3")
|
170
|
+
@object.customer.should eql(@customer)
|
171
|
+
@object.closed_at.to_s.should eql(@time2.to_s)
|
172
|
+
@object.should_not be_changed
|
173
|
+
end
|
174
|
+
|
175
|
+
describe "when second state is undone" do
|
176
|
+
before do
|
177
|
+
@result = Memento::Session.last.undo.first
|
178
|
+
@object = @result.object
|
179
|
+
end
|
180
|
+
|
181
|
+
it "should be success" do
|
182
|
+
@result.should be_success
|
183
|
+
end
|
184
|
+
|
185
|
+
it "should return correctly updated object" do
|
186
|
+
@object.class.should eql(Project)
|
187
|
+
@object.name.should eql("P2")
|
188
|
+
@object.customer.should eql(@customer)
|
189
|
+
@object.closed_at.to_s.should eql(@time2.to_s)
|
190
|
+
@object.notes.should eql("Bla bla")
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
|
198
|
+
describe "when object gets updated with no changes" do
|
199
|
+
|
200
|
+
before do
|
201
|
+
Memento.instance.memento(@user) do
|
202
|
+
@project.update_attributes(:name => "P1", :customer => nil, :notes => "Bla bla")
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
it "should not create session/state" do
|
207
|
+
Memento::Session.count.should eql(0)
|
208
|
+
Memento::State.count.should eql(0)
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|