memento 0.3.7 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rbenv-version +1 -1
- data/CHANGES.md +10 -1
- data/README.rdoc +9 -9
- data/generators/memento_migration/templates/migration.rb +4 -4
- data/lib/memento.rb +72 -54
- data/lib/memento/action.rb +7 -7
- data/lib/memento/action/create.rb +35 -33
- data/lib/memento/action/destroy.rb +26 -25
- data/lib/memento/action/update.rb +49 -48
- data/lib/memento/action_controller_methods.rb +3 -3
- data/lib/memento/active_record_methods.rb +21 -21
- data/lib/memento/result.rb +35 -34
- data/lib/memento/session.rb +25 -24
- data/lib/memento/state.rb +45 -44
- data/lib/memento/version.rb +2 -2
- data/memento.gemspec +2 -2
- data/spec/memento/action/create_spec.rb +14 -14
- data/spec/memento/action/destroy_spec.rb +9 -9
- data/spec/memento/action/update_spec.rb +34 -34
- data/spec/memento/action_controller_methods_spec.rb +9 -9
- data/spec/memento/active_record_methods_spec.rb +17 -17
- data/spec/memento/result_spec.rb +12 -12
- data/spec/memento/session_spec.rb +21 -21
- data/spec/memento/state_spec.rb +12 -12
- data/spec/memento_spec.rb +109 -68
- data/spec/spec_helper.rb +9 -9
- metadata +2 -2
data/.rbenv-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.9.3-
|
1
|
+
1.9.3-p194
|
data/CHANGES.md
CHANGED
@@ -1,6 +1,15 @@
|
|
1
1
|
### dev
|
2
2
|
|
3
|
-
[full changelog](http://github.com/yolk/valvat/compare/v0.
|
3
|
+
[full changelog](http://github.com/yolk/valvat/compare/v0.4.0...master)
|
4
|
+
|
5
|
+
### 0.4.0 / 2012-10-29
|
6
|
+
|
7
|
+
[full changelog](http://github.com/yolk/valvat/compare/v0.3.7...v0.4.0)
|
8
|
+
|
9
|
+
* Memento is a Module now, not a Singleton: Use Memento directly and not Memento.instance
|
10
|
+
* Memento module is threadsafe now
|
11
|
+
* Changed main api: instead of Memento.memento() use Memento.watch() or Memento()
|
12
|
+
* Some code cleanup
|
4
13
|
|
5
14
|
### 0.3.7 / 2012-08-13
|
6
15
|
|
data/README.rdoc
CHANGED
@@ -26,9 +26,9 @@ memento needs two tables in your database, one to store "sessions" (sets of stat
|
|
26
26
|
|
27
27
|
script/generate memento_migration
|
28
28
|
rake db:migrate
|
29
|
-
|
29
|
+
|
30
30
|
memento assumes you have a user-model. Every session is owned by a user.
|
31
|
-
|
31
|
+
|
32
32
|
== Configure your models
|
33
33
|
|
34
34
|
Then you have to tell every model you want to undo actions on that it should be watched by memento:
|
@@ -42,13 +42,13 @@ This will tell memento to create snapshots of the model when an new instance is
|
|
42
42
|
If you want memento to only take snapshots on specific actions:
|
43
43
|
|
44
44
|
memento_changes :update, :destroy
|
45
|
-
|
45
|
+
|
46
46
|
This will take a snapshot only when an instance is updated or destroyed.
|
47
47
|
|
48
48
|
By default memento will ignore changes to the :updated_at and :created_at attributes. You can add further attributes to ignore with the :ignore option:
|
49
49
|
|
50
50
|
memento_changes :ignore => [:calculated_birthday, :friends_count]
|
51
|
-
|
51
|
+
|
52
52
|
This will ignore changes on the calculated_birthday and the firends_count-attributes. When memento saves a whole instance of your model before it is destroyed, those attributes will not be stored for later recovery. Only ignore attributes you can re-calculate from other data!
|
53
53
|
|
54
54
|
== Action!
|
@@ -57,22 +57,22 @@ When you perform any of the configured actions on your model in isolation in you
|
|
57
57
|
|
58
58
|
Person.create!(:name => "Blah")
|
59
59
|
Memento::Session.count # => 0
|
60
|
-
|
60
|
+
|
61
61
|
You have to wrap every action block you want memento to track in your controller with the memento-method:
|
62
62
|
|
63
63
|
memento do
|
64
64
|
Person.create!(:name => "Blah")
|
65
65
|
end
|
66
66
|
Memento::Session.count # => 1
|
67
|
-
|
68
|
-
This assumes there is an method called "current_user" in your controllers. It will set the HTTP-Header 'X-Memento-Session-Id' on your response.
|
67
|
+
|
68
|
+
This assumes there is an method called "current_user" in your controllers. It will also set the HTTP-Header 'X-Memento-Session-Id' on your response.
|
69
69
|
|
70
70
|
If you want memento to watch changes outside of your controllers (for example inside the console) you can use:
|
71
71
|
|
72
|
-
Memento
|
72
|
+
Memento(user) do
|
73
73
|
Person.create!(:name => "Blah")
|
74
74
|
end
|
75
|
-
|
75
|
+
|
76
76
|
Where the variable user is assumed to hold an instance of User.
|
77
77
|
|
78
78
|
== Undo!
|
@@ -1,11 +1,11 @@
|
|
1
1
|
class MementoMigration < ActiveRecord::Migration
|
2
|
-
|
2
|
+
|
3
3
|
def self.up
|
4
4
|
create_table :memento_sessions do |t|
|
5
5
|
t.references :user
|
6
6
|
t.timestamps
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
9
|
create_table :memento_states do |t|
|
10
10
|
t.string :action_type
|
11
11
|
t.binary :record_data, :limit => 16777215
|
@@ -14,10 +14,10 @@ class MementoMigration < ActiveRecord::Migration
|
|
14
14
|
t.timestamps
|
15
15
|
end
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
def self.down
|
19
19
|
drop_table :memento_states
|
20
20
|
drop_table :memento_sessions
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
end
|
data/lib/memento.rb
CHANGED
@@ -1,63 +1,81 @@
|
|
1
1
|
require 'active_record'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
module Memento
|
4
|
+
|
6
5
|
class ErrorOnRewind < StandardError;end
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
# For backwards compatibility (was a Singleton)
|
10
|
+
def instance
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
def watch(user_or_id)
|
15
|
+
start(user_or_id)
|
16
|
+
yield
|
17
|
+
session && !session.new_record? && session.states.any? ? session : false
|
18
|
+
ensure
|
19
|
+
stop
|
20
|
+
end
|
21
|
+
|
22
|
+
def start(user_or_id)
|
23
|
+
user = user_or_id.is_a?(User) ? user_or_id : User.find_by_id(user_or_id)
|
24
|
+
self.session = user ? Memento::Session.new(:user => user) : nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def stop
|
28
|
+
session.destroy if session && session.states.count.zero?
|
29
|
+
self.session = nil
|
30
|
+
end
|
31
|
+
|
32
|
+
def add_state(action_type, record)
|
33
|
+
return unless save_session
|
34
|
+
session.add_state(action_type, record)
|
35
|
+
end
|
36
|
+
|
37
|
+
def active?
|
38
|
+
!!session && !ignore?
|
39
|
+
end
|
40
|
+
|
41
|
+
def ignore
|
42
|
+
Thread.current[:memento_ignore] = true
|
43
|
+
yield
|
44
|
+
ensure
|
45
|
+
Thread.current[:memento_ignore] = false
|
46
|
+
end
|
47
|
+
|
48
|
+
def serializer=(serializer)
|
49
|
+
@serializer = serializer
|
50
|
+
end
|
51
|
+
|
52
|
+
def serializer
|
53
|
+
@serializer ||= YAML
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def session
|
59
|
+
Thread.current[:memento_session]
|
60
|
+
end
|
61
|
+
|
62
|
+
def session=(session)
|
63
|
+
Thread.current[:memento_session] = session
|
64
|
+
end
|
65
|
+
|
66
|
+
def ignore?
|
67
|
+
!!Thread.current[:memento_ignore]
|
68
|
+
end
|
69
|
+
|
70
|
+
def save_session
|
71
|
+
active? && (!session.changed? || session.save)
|
72
|
+
end
|
58
73
|
end
|
59
74
|
end
|
60
75
|
|
76
|
+
def Memento(user_or_id, &block)
|
77
|
+
Memento.watch(user_or_id, &block)
|
78
|
+
end
|
61
79
|
require 'memento/result'
|
62
80
|
require 'memento/action'
|
63
81
|
require 'memento/active_record_methods'
|
data/lib/memento/action.rb
CHANGED
@@ -5,29 +5,29 @@ module Memento::Action
|
|
5
5
|
def initialize(state)
|
6
6
|
@state = state
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
9
|
attr_reader :state
|
10
10
|
class_attribute :action_types, :instance_reader => false, :instance_writer => false
|
11
11
|
self.action_types = []
|
12
|
-
|
12
|
+
|
13
13
|
def record
|
14
14
|
@state.record
|
15
15
|
end
|
16
|
-
|
16
|
+
|
17
17
|
def record_data
|
18
18
|
@state.record_data
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
def fetch?
|
22
22
|
true
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
def self.inherited(child)
|
26
26
|
self.action_types << child.name.demodulize.underscore
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
private
|
30
|
-
|
30
|
+
|
31
31
|
def new_object
|
32
32
|
object = @state.record_type.constantize.new
|
33
33
|
yield(object) if block_given?
|
@@ -1,37 +1,39 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
1
|
+
module Memento
|
2
|
+
class Action::Create < Memento::Action::Base
|
3
|
+
|
4
|
+
def fetch;end
|
5
|
+
|
6
|
+
def undo
|
7
|
+
if record.nil?
|
8
|
+
build_fake_object
|
9
|
+
elsif record_was_changed?
|
10
|
+
was_changed
|
11
|
+
else
|
12
|
+
destroy_record
|
13
|
+
end
|
12
14
|
end
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
record.updated_at > record.created_at rescue false
|
19
|
-
end
|
20
|
-
|
21
|
-
def build_fake_object
|
22
|
-
new_object do |object|
|
23
|
-
object.id = state.record_id
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def record_was_changed?
|
19
|
+
record.updated_at > record.created_at rescue false
|
24
20
|
end
|
21
|
+
|
22
|
+
def build_fake_object
|
23
|
+
new_object do |object|
|
24
|
+
object.id = state.record_id
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def was_changed
|
29
|
+
record.errors.add(:memento_undo, ActiveSupport::StringInquirer.new("was_changed"))
|
30
|
+
record
|
31
|
+
end
|
32
|
+
|
33
|
+
def destroy_record
|
34
|
+
record.destroy
|
35
|
+
record
|
36
|
+
end
|
37
|
+
|
25
38
|
end
|
26
|
-
|
27
|
-
def was_changed
|
28
|
-
record.errors.add(:memento_undo, ActiveSupport::StringInquirer.new("was_changed"))
|
29
|
-
record
|
30
|
-
end
|
31
|
-
|
32
|
-
def destroy_record
|
33
|
-
record.destroy
|
34
|
-
record
|
35
|
-
end
|
36
|
-
|
37
39
|
end
|
@@ -1,31 +1,32 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
1
|
+
module Memento
|
2
|
+
class Action::Destroy < Memento::Action::Base
|
3
|
+
|
4
|
+
def fetch
|
5
|
+
record.attributes_for_memento
|
6
|
+
end
|
7
|
+
|
8
|
+
def undo
|
9
|
+
rebuild_object do |object|
|
10
|
+
begin
|
11
|
+
object.save!
|
12
|
+
rescue
|
13
|
+
object.id = nil
|
14
|
+
object.save!
|
15
|
+
end
|
16
|
+
state.record = object
|
17
|
+
state.save
|
14
18
|
end
|
15
|
-
state.record = object
|
16
|
-
state.save
|
17
19
|
end
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def rebuild_object
|
24
|
+
new_object do |object|
|
25
|
+
state.record_data.each do |attribute, value|
|
26
|
+
object.send(:"#{attribute}=", value)
|
27
|
+
end
|
28
|
+
yield(object) if block_given?
|
26
29
|
end
|
27
|
-
yield(object) if block_given?
|
28
30
|
end
|
29
31
|
end
|
30
|
-
|
31
32
|
end
|
@@ -1,54 +1,55 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
def fetch?
|
8
|
-
record.changes_for_memento.any?
|
9
|
-
end
|
10
|
-
|
11
|
-
def undo
|
12
|
-
if !record
|
13
|
-
was_destroyed
|
14
|
-
elsif mergable?
|
15
|
-
update_record
|
16
|
-
else
|
17
|
-
was_changed
|
1
|
+
module Memento
|
2
|
+
class Action::Update < Memento::Action::Base
|
3
|
+
|
4
|
+
def fetch
|
5
|
+
record.changes_for_memento
|
18
6
|
end
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
7
|
+
|
8
|
+
def fetch?
|
9
|
+
record.changes_for_memento.any?
|
10
|
+
end
|
11
|
+
|
12
|
+
def undo
|
13
|
+
if !record
|
14
|
+
was_destroyed
|
15
|
+
elsif mergable?
|
16
|
+
update_record
|
17
|
+
else
|
18
|
+
was_changed
|
27
19
|
end
|
28
|
-
object.save!
|
29
20
|
end
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def update_record
|
25
|
+
record.tap do |object|
|
26
|
+
record_data.each do |attribute, values|
|
27
|
+
object.send(:"#{attribute}=", values.first)
|
28
|
+
end
|
29
|
+
object.save!
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def mergable?
|
34
|
+
record_data.all? do |attribute, values|
|
35
|
+
# ugly fix to compare times
|
36
|
+
values = values.map{|v| v.is_a?(Time) ? v.to_s(:db) : v }
|
37
|
+
current_value = record.send(:"#{attribute}")
|
38
|
+
current_value = current_value.utc.to_s(:db) if current_value.is_a?(Time)
|
39
|
+
values.include?(current_value) || (current_value.is_a?(String) && values.include?(current_value.gsub(/\r\n/, "\n")))
|
40
|
+
end || record_data.size.zero?
|
41
|
+
end
|
42
|
+
|
43
|
+
def was_destroyed
|
44
|
+
new_object do |object|
|
45
|
+
object.errors[:memento_undo] << ActiveSupport::StringInquirer.new("was_destroyed")
|
46
|
+
object.id = state.record_id
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def was_changed
|
51
|
+
record.errors[:memento_undo] << ActiveSupport::StringInquirer.new("was_changed")
|
52
|
+
record
|
46
53
|
end
|
47
54
|
end
|
48
|
-
|
49
|
-
def was_changed
|
50
|
-
record.errors[:memento_undo] << ActiveSupport::StringInquirer.new("was_changed")
|
51
|
-
record
|
52
|
-
end
|
53
|
-
|
54
55
|
end
|