memento 0.3.7 → 0.4.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/.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
|