yolk-memento 0.2.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 +32 -0
- data/TODO +3 -0
- data/VERSION.yml +4 -0
- data/generators/memento_migration/memento_migration_generator.rb +10 -0
- data/generators/memento_migration/templates/migration.rb +23 -0
- data/lib/memento.rb +56 -0
- data/lib/memento/action.rb +32 -0
- data/lib/memento/action/create.rb +41 -0
- data/lib/memento/action/destroy.rb +19 -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 +71 -0
- data/memento.gemspec +75 -0
- data/rails/init.rb +1 -0
- data/spec/memento/action/create_spec.rb +96 -0
- data/spec/memento/action/destroy_spec.rb +43 -0
- data/spec/memento/action/update_spec.rb +209 -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 +181 -0
- data/spec/memento/state_spec.rb +105 -0
- data/spec/memento_spec.rb +156 -0
- data/spec/spec_helper.rb +91 -0
- metadata +92 -0
@@ -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,71 @@
|
|
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 ||= Marshal.load(super)
|
26
|
+
end
|
27
|
+
|
28
|
+
def record_data=(data)
|
29
|
+
@record_data = nil
|
30
|
+
super(Marshal.dump(data))
|
31
|
+
end
|
32
|
+
|
33
|
+
def fetch?
|
34
|
+
action.fetch?
|
35
|
+
end
|
36
|
+
|
37
|
+
def new_object
|
38
|
+
object = record_type.constantize.new
|
39
|
+
yield(object) if block_given?
|
40
|
+
object
|
41
|
+
end
|
42
|
+
|
43
|
+
def rebuild_object(*skip)
|
44
|
+
skip = skip ? skip.map(&:to_sym) : []
|
45
|
+
new_object do |object|
|
46
|
+
record_data.each do |attribute, value|
|
47
|
+
object.send(:"#{attribute}=", value) unless skip.include?(attribute.to_sym)
|
48
|
+
end
|
49
|
+
yield(object) if block_given?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def later_states_on_record_for(action_type_param)
|
54
|
+
Memento::State.all(:conditions => [
|
55
|
+
"record_id = ? AND record_type = ? AND " +
|
56
|
+
"action_type = ? AND created_at >= ? AND id != ? ",
|
57
|
+
record_id, record_type, action_type_param.to_s, created_at, id
|
58
|
+
])
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def set_record_data
|
64
|
+
self.record_data = action.fetch
|
65
|
+
end
|
66
|
+
|
67
|
+
def action
|
68
|
+
"memento/action/#{action_type}".classify.constantize.new(self)
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
data/memento.gemspec
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{memento}
|
5
|
+
s.version = "0.2.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Yolk Sebastian Munz & Julia Soergel GbR"]
|
9
|
+
s.date = %q{2009-07-09}
|
10
|
+
s.email = %q{sebastian@yo.lk}
|
11
|
+
s.extra_rdoc_files = [
|
12
|
+
"LICENSE",
|
13
|
+
"README.rdoc"
|
14
|
+
]
|
15
|
+
s.files = [
|
16
|
+
".gitignore",
|
17
|
+
"LICENSE",
|
18
|
+
"README.rdoc",
|
19
|
+
"Rakefile",
|
20
|
+
"TODO",
|
21
|
+
"VERSION.yml",
|
22
|
+
"generators/memento_migration/memento_migration_generator.rb",
|
23
|
+
"generators/memento_migration/templates/migration.rb",
|
24
|
+
"lib/memento.rb",
|
25
|
+
"lib/memento/action.rb",
|
26
|
+
"lib/memento/action/create.rb",
|
27
|
+
"lib/memento/action/destroy.rb",
|
28
|
+
"lib/memento/action/update.rb",
|
29
|
+
"lib/memento/action_controller_methods.rb",
|
30
|
+
"lib/memento/active_record_methods.rb",
|
31
|
+
"lib/memento/result.rb",
|
32
|
+
"lib/memento/session.rb",
|
33
|
+
"lib/memento/state.rb",
|
34
|
+
"memento.gemspec",
|
35
|
+
"rails/init.rb",
|
36
|
+
"spec/memento/action/create_spec.rb",
|
37
|
+
"spec/memento/action/destroy_spec.rb",
|
38
|
+
"spec/memento/action/update_spec.rb",
|
39
|
+
"spec/memento/action_controller_methods_spec.rb",
|
40
|
+
"spec/memento/active_record_methods_spec.rb",
|
41
|
+
"spec/memento/result_spec.rb",
|
42
|
+
"spec/memento/session_spec.rb",
|
43
|
+
"spec/memento/state_spec.rb",
|
44
|
+
"spec/memento_spec.rb",
|
45
|
+
"spec/spec_helper.rb"
|
46
|
+
]
|
47
|
+
s.has_rdoc = true
|
48
|
+
s.homepage = %q{http://github.com/yolk/memento}
|
49
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
50
|
+
s.require_paths = ["lib"]
|
51
|
+
s.rubygems_version = %q{1.3.2}
|
52
|
+
s.summary = %q{Undo for Rails/ActiveRecord - covers destroy, update and create}
|
53
|
+
s.test_files = [
|
54
|
+
"spec/memento/action/create_spec.rb",
|
55
|
+
"spec/memento/action/destroy_spec.rb",
|
56
|
+
"spec/memento/action/update_spec.rb",
|
57
|
+
"spec/memento/action_controller_methods_spec.rb",
|
58
|
+
"spec/memento/active_record_methods_spec.rb",
|
59
|
+
"spec/memento/result_spec.rb",
|
60
|
+
"spec/memento/session_spec.rb",
|
61
|
+
"spec/memento/state_spec.rb",
|
62
|
+
"spec/memento_spec.rb",
|
63
|
+
"spec/spec_helper.rb"
|
64
|
+
]
|
65
|
+
|
66
|
+
if s.respond_to? :specification_version then
|
67
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
68
|
+
s.specification_version = 3
|
69
|
+
|
70
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
71
|
+
else
|
72
|
+
end
|
73
|
+
else
|
74
|
+
end
|
75
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'memento'
|
@@ -0,0 +1,96 @@
|
|
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
|
+
|
48
|
+
it "should give back fake unsaved record with id set" do
|
49
|
+
Project.last.destroy
|
50
|
+
@undone = Memento::Session.last.undo
|
51
|
+
@undone.size.should eql(1)
|
52
|
+
@undone.first.object.should be_kind_of(Project)
|
53
|
+
@undone.first.object.id.should eql(@project.id)
|
54
|
+
@undone.first.object.name.should be_nil
|
55
|
+
@undone.first.object.should be_new_record
|
56
|
+
Project.count.should eql(0)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should give back fake unsaved record with all data set when destruction was stateed" do
|
60
|
+
Memento.instance.memento(@user) { Project.last.destroy }
|
61
|
+
Memento::State.last.update_attribute(:created_at, 5.minutes.from_now)
|
62
|
+
@undone = Memento::Session.first.undo
|
63
|
+
@undone.size.should eql(1)
|
64
|
+
@undone.first.object.should be_kind_of(Project)
|
65
|
+
@undone.first.object.id.should eql(@project.id)
|
66
|
+
@undone.first.object.name.should eql(@project.name)
|
67
|
+
@undone.first.object.closed_at.should eql(@project.closed_at)
|
68
|
+
@undone.first.object.should be_new_record
|
69
|
+
Project.count.should eql(0)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
describe Memento::Action::Create, "when object without timestamp is created" do
|
79
|
+
before do
|
80
|
+
setup_db
|
81
|
+
setup_data
|
82
|
+
Memento.instance.memento(@user) do
|
83
|
+
@obj = TimestamplessObject.create!(:name => "O1").reload
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
after do
|
88
|
+
shutdown_db
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "when undoing the creation" do
|
92
|
+
it "should give back undone_object" do
|
93
|
+
Memento::Session.last.undo.map{|e| e.object.class }.should eql([TimestamplessObject])
|
94
|
+
end
|
95
|
+
end
|
96
|
+
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,209 @@
|
|
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 unstateed 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 stateed 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 unstateed 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 return not undone object" do
|
143
|
+
@object.name.should eql("P3")
|
144
|
+
@object.customer.should eql(@customer)
|
145
|
+
@object.closed_at.to_s.should eql(@time2.to_s)
|
146
|
+
@object.should_not be_changed
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe "with unmergeable stateed changes" do
|
151
|
+
before do
|
152
|
+
Memento.instance.memento(@user) do
|
153
|
+
@project.update_attributes!({:name => "P3"})
|
154
|
+
end
|
155
|
+
Memento::State.last.update_attribute(:created_at, 1.minute.from_now)
|
156
|
+
@result = Memento::Session.first.undo.first
|
157
|
+
@object = @result.object
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should fail" do
|
161
|
+
@result.should be_failed
|
162
|
+
end
|
163
|
+
|
164
|
+
it "should return not undone object" do
|
165
|
+
@object.name.should eql("P3")
|
166
|
+
@object.customer.should eql(@customer)
|
167
|
+
@object.closed_at.to_s.should eql(@time2.to_s)
|
168
|
+
@object.should_not be_changed
|
169
|
+
end
|
170
|
+
|
171
|
+
describe "when second state is undone" do
|
172
|
+
before do
|
173
|
+
@result = Memento::Session.last.undo.first
|
174
|
+
@object = @result.object
|
175
|
+
end
|
176
|
+
|
177
|
+
it "should be success" do
|
178
|
+
@result.should be_success
|
179
|
+
end
|
180
|
+
|
181
|
+
it "should return correctly updated object" do
|
182
|
+
@object.class.should eql(Project)
|
183
|
+
@object.name.should eql("P2")
|
184
|
+
@object.customer.should eql(@customer)
|
185
|
+
@object.closed_at.to_s.should eql(@time2.to_s)
|
186
|
+
@object.notes.should eql("Bla bla")
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
describe "when object gets updated with no changes" do
|
195
|
+
|
196
|
+
before do
|
197
|
+
Memento.instance.memento(@user) do
|
198
|
+
@project.update_attributes(:name => "P1", :customer => nil, :notes => "Bla bla")
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
it "should not create session/state" do
|
203
|
+
Memento::Session.count.should eql(0)
|
204
|
+
Memento::State.count.should eql(0)
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|