active_metadata 0.2.3 → 0.2.4
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/db/test.sqlite3 +0 -0
- data/lib/active_metadata/base.rb +53 -23
- data/lib/active_metadata/version.rb +1 -1
- data/lib/model/active_record/watcher.rb +1 -1
- data/spec/concurrency_spec.rb +38 -27
- metadata +20 -19
data/db/test.sqlite3
CHANGED
Binary file
|
data/lib/active_metadata/base.rb
CHANGED
@@ -19,7 +19,10 @@ module ActiveMetadata
|
|
19
19
|
module Config
|
20
20
|
|
21
21
|
def acts_as_metadata *args
|
22
|
+
before_update :manage_concurrency
|
22
23
|
after_save :save_history
|
24
|
+
attr_accessor :conflicts
|
25
|
+
attr_accessor :active_metadata_timestamp
|
23
26
|
|
24
27
|
class_variable_set("@@metadata_id_from", args.empty? ? nil : args[0][:metadata_id_from])
|
25
28
|
|
@@ -34,6 +37,11 @@ module ActiveMetadata
|
|
34
37
|
|
35
38
|
include ActiveMetadata::Helpers
|
36
39
|
|
40
|
+
def attributes=(attributes)
|
41
|
+
attributes.delete(:active_metadata_timestamp) unless attributes[:active_metadata_timestamp].nil?
|
42
|
+
super
|
43
|
+
end
|
44
|
+
|
37
45
|
def self.included(klass)
|
38
46
|
[:notes, :attachments, :history].each do |item|
|
39
47
|
klass.send(:define_method, "#{item.to_s}_cache_key".to_sym) do |field|
|
@@ -43,13 +51,11 @@ module ActiveMetadata
|
|
43
51
|
end
|
44
52
|
|
45
53
|
def metadata_id
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
end
|
52
|
-
receiver.id
|
54
|
+
metadata_root.id
|
55
|
+
end
|
56
|
+
|
57
|
+
def active_metadata_timestamp
|
58
|
+
metadata_root.active_metadata_timestamp || nil
|
53
59
|
end
|
54
60
|
|
55
61
|
def current_user_id
|
@@ -60,23 +66,45 @@ module ActiveMetadata
|
|
60
66
|
end
|
61
67
|
end
|
62
68
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
69
|
+
def metadata_root
|
70
|
+
metadata_id_from = self.class.class_variable_get("@@metadata_id_from")
|
71
|
+
return self if metadata_id_from.nil?
|
72
|
+
receiver = self
|
73
|
+
metadata_id_from.each do |item|
|
74
|
+
receiver = receiver.send item
|
75
|
+
end
|
76
|
+
receiver
|
77
|
+
end
|
78
|
+
|
79
|
+
# Resolve concurrency using the provided timestamps and the active_metadata histories.
|
80
|
+
# Conflicts are stored into @conflicts instance variable
|
81
|
+
#
|
82
|
+
# Timestamp used for versioning can be passed both as :
|
83
|
+
# ====
|
84
|
+
# * @object.active_metadata_timestamp = ....
|
85
|
+
# * @object.update_attributes :active_metadata_timestamp => ...
|
67
86
|
#
|
68
|
-
#
|
69
|
-
#
|
70
|
-
#
|
71
|
-
|
72
|
-
|
73
|
-
|
87
|
+
# Check is skipped if no timestamp exists. A check is made also for parents defined via acts_as_metadata method
|
88
|
+
#
|
89
|
+
# Conflicts
|
90
|
+
# ====
|
91
|
+
# * @conflicts[:warnings] contains any conflict that "apply cleanly" or where the submit value has not been modified by the user that is saving
|
92
|
+
# the data
|
93
|
+
#
|
94
|
+
# * @conflicts[:fatals] contains any conflict that "do not apply cleanly" or where the passed value does not match the last history value and was modified
|
95
|
+
# by the user that is submittig the data
|
96
|
+
#
|
97
|
+
def manage_concurrency
|
98
|
+
return if active_metadata_timestamp.nil? #if no timestamp no way to check concurrency so just skip
|
99
|
+
|
100
|
+
self.conflicts = { :warnings => [], :fatals => [] }
|
74
101
|
|
75
102
|
# scan params
|
76
|
-
|
103
|
+
self.attributes.each do |key, val|
|
77
104
|
# ensure the query order
|
78
105
|
histories = history_for key.to_sym, "created_at DESC"
|
79
106
|
latest_history = histories.first
|
107
|
+
timestamp = self.active_metadata_timestamp
|
80
108
|
|
81
109
|
# if form timestamp is subsequent the history last change go on
|
82
110
|
# if history does not exists yet go on
|
@@ -84,8 +112,11 @@ module ActiveMetadata
|
|
84
112
|
|
85
113
|
#if the timestamp is previous of the last history change
|
86
114
|
if timestamp < latest_history.created_at
|
87
|
-
|
88
|
-
|
115
|
+
|
116
|
+
begin
|
117
|
+
self[key.to_sym] = self.changes[key][0]
|
118
|
+
rescue
|
119
|
+
end # there is a conflict so ensure the actual value if any change exists
|
89
120
|
|
90
121
|
# We have a conflict.
|
91
122
|
# Check if the actual submission has been modified
|
@@ -100,9 +131,9 @@ module ActiveMetadata
|
|
100
131
|
end
|
101
132
|
|
102
133
|
if [h.value, b_val].include? val
|
103
|
-
warnings << {key => [val, h]}
|
134
|
+
self.conflicts[:warnings] << {key.to_sym => [val, h]}
|
104
135
|
else
|
105
|
-
fatals << {key => [val, h]}
|
136
|
+
self.conflicts[:fatals] << {key.to_sym => [val, h]}
|
106
137
|
end
|
107
138
|
end
|
108
139
|
|
@@ -110,7 +141,6 @@ module ActiveMetadata
|
|
110
141
|
|
111
142
|
end
|
112
143
|
|
113
|
-
return warnings, fatals
|
114
144
|
end
|
115
145
|
|
116
146
|
end # InstanceMethods
|
data/spec/concurrency_spec.rb
CHANGED
@@ -7,17 +7,29 @@ describe "Manage Concurrency" do
|
|
7
7
|
@document.reload
|
8
8
|
end
|
9
9
|
|
10
|
+
it "should assign the active_metadata_timestamp via mass assignment" do
|
11
|
+
ts = Time.now
|
12
|
+
params = {:name => "nuovo nome", :active_metadata_timestamp => ts}
|
13
|
+
@document.update_attributes params
|
14
|
+
@document.active_metadata_timestamp.should eq ts
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should skip if no timestamp is provided" do
|
18
|
+
@document.update_attributes :name => "nuovo nome"
|
19
|
+
@document.conflicts.should be_nil
|
20
|
+
end
|
21
|
+
|
10
22
|
describe "when a field is modified and the form ts is subsequent of the history ts" do
|
11
23
|
|
12
24
|
it "should save the new value and history must be updated with the latest change" do
|
13
25
|
sleep 0.2.seconds
|
14
26
|
|
15
|
-
params = {:name => "nuovo nome"}
|
16
|
-
warn,fatal = @document.manage_concurrency params,Time.now
|
27
|
+
params = {:name => "nuovo nome", :active_metadata_timestamp => Time.now}
|
17
28
|
@document.update_attributes(params)
|
18
29
|
|
19
|
-
|
20
|
-
|
30
|
+
@document.conflicts[:warnings].should be_empty
|
31
|
+
@document.conflicts[:fatals].should be_empty
|
32
|
+
|
21
33
|
hs = @document.history_for(:name)
|
22
34
|
hs.count.should eq 2
|
23
35
|
hs.first.value.should eq "nuovo nome"
|
@@ -30,21 +42,20 @@ describe "Manage Concurrency" do
|
|
30
42
|
|
31
43
|
it "should not change the model value and the history if the newest history value is equal to the submitted once, param should be retuned as warning" do
|
32
44
|
form_timestamp = Time.now
|
33
|
-
params = {:name => "nuovo nome"}
|
34
45
|
|
35
46
|
#someone change the field value
|
36
47
|
sleep 0.2.seconds
|
37
|
-
|
38
|
-
@document.update_attributes(params)
|
48
|
+
@document.update_attributes({:name => "nuovo nome", :active_metadata_timestamp => Time.now})
|
39
49
|
|
40
50
|
# user submit pushing the same value that was already saved by another user
|
41
51
|
sleep 0.2.seconds
|
42
|
-
|
43
|
-
|
52
|
+
@document.update_attributes({:name => "nuovo nome", :active_metadata_timestamp => form_timestamp})
|
53
|
+
|
54
|
+
warnings = @document.conflicts[:warnings]
|
55
|
+
warnings.size.should eq 1
|
56
|
+
warnings[0][:name][0].should eq "nuovo nome"
|
44
57
|
|
45
|
-
|
46
|
-
warn[0][:name][0].should eq "nuovo nome"
|
47
|
-
fatal.should be_empty
|
58
|
+
@document.conflicts[:fatals].should be_empty
|
48
59
|
hs = @document.history_for(:name)
|
49
60
|
#last change shoudl not be recorded in history
|
50
61
|
hs.count.should eq 2
|
@@ -54,25 +65,25 @@ describe "Manage Concurrency" do
|
|
54
65
|
end
|
55
66
|
|
56
67
|
it "should reject the change if the history newest value is different from the submitted once, param should be returned as fatal" do
|
68
|
+
#fixtures
|
57
69
|
form_timestamp = Time.now
|
58
70
|
params = {:name => "nuovo nome"}
|
59
71
|
|
60
72
|
#someone change the field value
|
61
73
|
sleep 0.2.seconds
|
62
|
-
warn,fatal = @document.manage_concurrency params,Time.now
|
63
74
|
@document.update_attributes(params)
|
64
75
|
|
65
76
|
# user submit a new value
|
66
77
|
sleep 0.2.seconds
|
67
|
-
different_params = {:name => "altro nome"}
|
68
|
-
warn,fatal = @document.manage_concurrency different_params,form_timestamp
|
78
|
+
different_params = {:name => "altro nome", :active_metadata_timestamp => form_timestamp}
|
69
79
|
@document.update_attributes(different_params)
|
70
80
|
|
71
|
-
|
72
|
-
|
73
|
-
|
81
|
+
#assetions
|
82
|
+
fatals = @document.conflicts[:fatals]
|
83
|
+
fatals.size.should eq 1
|
84
|
+
fatals[0][:name][0].should eq "altro nome"
|
85
|
+
@document.conflicts[:warnings].should be_empty
|
74
86
|
hs = @document.history_for(:name)
|
75
|
-
#last change shoudl not be recorded in history
|
76
87
|
hs.count.should eq 2
|
77
88
|
hs.first.value.should eq "nuovo nome"
|
78
89
|
Document.last.name.should eq "nuovo nome"
|
@@ -85,19 +96,19 @@ describe "Manage Concurrency" do
|
|
85
96
|
|
86
97
|
#someone change the field value
|
87
98
|
sleep 0.2.seconds
|
88
|
-
|
89
|
-
@document.update_attributes(params)
|
99
|
+
@document.update_attributes params
|
90
100
|
|
91
101
|
# user submit a new value
|
92
102
|
sleep 0.2.seconds
|
93
|
-
different_params = {:name => "altro nome", :keep_alive => false}
|
94
|
-
warn,fatal = @document.manage_concurrency different_params,form_timestamp
|
103
|
+
different_params = {:name => "altro nome", :keep_alive => false, :active_metadata_timestamp => form_timestamp}
|
95
104
|
@document.update_attributes(different_params)
|
96
105
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
106
|
+
fatals = @document.conflicts[:fatals]
|
107
|
+
fatals.size.should eq 1
|
108
|
+
fatals[0][:name][0].should eq "altro nome"
|
109
|
+
warnings = @document.conflicts[:warnings]
|
110
|
+
warnings.size.should eq 1
|
111
|
+
warnings[0][:keep_alive][0].should eq false
|
101
112
|
end
|
102
113
|
|
103
114
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_metadata
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -14,7 +14,7 @@ date: 2011-10-19 00:00:00.000000000Z
|
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rspec-rails
|
17
|
-
requirement: &
|
17
|
+
requirement: &2169354520 !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ! '>='
|
@@ -22,10 +22,10 @@ dependencies:
|
|
22
22
|
version: '0'
|
23
23
|
type: :development
|
24
24
|
prerelease: false
|
25
|
-
version_requirements: *
|
25
|
+
version_requirements: *2169354520
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: sqlite3
|
28
|
-
requirement: &
|
28
|
+
requirement: &2169353840 !ruby/object:Gem::Requirement
|
29
29
|
none: false
|
30
30
|
requirements:
|
31
31
|
- - ! '>='
|
@@ -33,10 +33,10 @@ dependencies:
|
|
33
33
|
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
|
-
version_requirements: *
|
36
|
+
version_requirements: *2169353840
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
38
|
name: sqlite3-ruby
|
39
|
-
requirement: &
|
39
|
+
requirement: &2169353240 !ruby/object:Gem::Requirement
|
40
40
|
none: false
|
41
41
|
requirements:
|
42
42
|
- - ! '>='
|
@@ -44,10 +44,10 @@ dependencies:
|
|
44
44
|
version: '0'
|
45
45
|
type: :development
|
46
46
|
prerelease: false
|
47
|
-
version_requirements: *
|
47
|
+
version_requirements: *2169353240
|
48
48
|
- !ruby/object:Gem::Dependency
|
49
49
|
name: cucumber
|
50
|
-
requirement: &
|
50
|
+
requirement: &2169352820 !ruby/object:Gem::Requirement
|
51
51
|
none: false
|
52
52
|
requirements:
|
53
53
|
- - ! '>='
|
@@ -55,10 +55,10 @@ dependencies:
|
|
55
55
|
version: '0'
|
56
56
|
type: :development
|
57
57
|
prerelease: false
|
58
|
-
version_requirements: *
|
58
|
+
version_requirements: *2169352820
|
59
59
|
- !ruby/object:Gem::Dependency
|
60
60
|
name: ci_reporter
|
61
|
-
requirement: &
|
61
|
+
requirement: &2169352320 !ruby/object:Gem::Requirement
|
62
62
|
none: false
|
63
63
|
requirements:
|
64
64
|
- - ! '>='
|
@@ -66,10 +66,10 @@ dependencies:
|
|
66
66
|
version: '0'
|
67
67
|
type: :development
|
68
68
|
prerelease: false
|
69
|
-
version_requirements: *
|
69
|
+
version_requirements: *2169352320
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
71
|
name: rails
|
72
|
-
requirement: &
|
72
|
+
requirement: &2169351620 !ruby/object:Gem::Requirement
|
73
73
|
none: false
|
74
74
|
requirements:
|
75
75
|
- - =
|
@@ -77,10 +77,10 @@ dependencies:
|
|
77
77
|
version: 3.0.1
|
78
78
|
type: :runtime
|
79
79
|
prerelease: false
|
80
|
-
version_requirements: *
|
80
|
+
version_requirements: *2169351620
|
81
81
|
- !ruby/object:Gem::Dependency
|
82
82
|
name: activerecord
|
83
|
-
requirement: &
|
83
|
+
requirement: &2169350680 !ruby/object:Gem::Requirement
|
84
84
|
none: false
|
85
85
|
requirements:
|
86
86
|
- - =
|
@@ -88,10 +88,10 @@ dependencies:
|
|
88
88
|
version: 3.0.1
|
89
89
|
type: :runtime
|
90
90
|
prerelease: false
|
91
|
-
version_requirements: *
|
91
|
+
version_requirements: *2169350680
|
92
92
|
- !ruby/object:Gem::Dependency
|
93
93
|
name: paperclip
|
94
|
-
requirement: &
|
94
|
+
requirement: &2169350180 !ruby/object:Gem::Requirement
|
95
95
|
none: false
|
96
96
|
requirements:
|
97
97
|
- - ! '>='
|
@@ -99,10 +99,10 @@ dependencies:
|
|
99
99
|
version: '0'
|
100
100
|
type: :runtime
|
101
101
|
prerelease: false
|
102
|
-
version_requirements: *
|
102
|
+
version_requirements: *2169350180
|
103
103
|
- !ruby/object:Gem::Dependency
|
104
104
|
name: to_xls
|
105
|
-
requirement: &
|
105
|
+
requirement: &2169349300 !ruby/object:Gem::Requirement
|
106
106
|
none: false
|
107
107
|
requirements:
|
108
108
|
- - ! '>='
|
@@ -110,7 +110,7 @@ dependencies:
|
|
110
110
|
version: '0'
|
111
111
|
type: :runtime
|
112
112
|
prerelease: false
|
113
|
-
version_requirements: *
|
113
|
+
version_requirements: *2169349300
|
114
114
|
description: First implementation will write metadata on mongodb
|
115
115
|
email:
|
116
116
|
- acampolonghi@gmail.com
|
@@ -228,3 +228,4 @@ test_files:
|
|
228
228
|
- spec/support/section.rb
|
229
229
|
- spec/support/user.rb
|
230
230
|
- spec/support/watcher_notifier.rb
|
231
|
+
has_rdoc:
|