active_metadata 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
data/db/test.sqlite3 CHANGED
Binary file
@@ -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
- metadata_id_from = self.class.class_variable_get("@@metadata_id_from")
47
- return self.id if metadata_id_from.nil?
48
- receiver = self
49
- metadata_id_from.each do |item|
50
- receiver = receiver.send item
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
- # Resolve concurrency using the passed timestamps and the active_metadata histories
64
- # To be called from controller before updating the model. Params that contains a conflict are removed from the params list.
65
- # Returns 2 values containing the the deleted params with the related history:
66
- # [{:key => [passed_value,history]}]
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
- # first result is the WARNINGS array: conflict appears on a field not touched by the user that submit the value
69
- # first result is the FATALS Array : if the conflict appears on a field modified by the user that submit the value
70
- # an empty array is returned by default
71
- def manage_concurrency(params, timestamp)
72
- warnings = []
73
- fatals = []
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
- params.each do |key, val|
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
- #remove the key from the update process
88
- params.delete key
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
@@ -1,3 +1,3 @@
1
1
  module ActiveMetadata
2
- VERSION = "0.2.3"
2
+ VERSION = "0.2.4"
3
3
  end
@@ -1,4 +1,4 @@
1
1
  class Watcher < ActiveRecord::Base
2
2
  attr_reader :notifier
3
-
3
+
4
4
  end
@@ -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
- warn.should be_empty
20
- fatal.should be_empty
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
- warn,fatal = @document.manage_concurrency params,Time.now
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
- warn,fatal = @document.manage_concurrency params,form_timestamp
43
- @document.update_attributes(params)
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
- warn.size.should eq 1
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
- fatal.size.should eq 1
72
- fatal[0][:name][0].should eq "altro nome"
73
- warn.should be_empty
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
- warn,fatal = @document.manage_concurrency params,Time.now
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
- fatal.size.should eq 1
98
- fatal[0][:name][0].should eq "altro nome"
99
- warn.size.should eq 1
100
- warn[0][:keep_alive][0].should eq false
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.3
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: &2168675080 !ruby/object:Gem::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: *2168675080
25
+ version_requirements: *2169354520
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: sqlite3
28
- requirement: &2168674400 !ruby/object:Gem::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: *2168674400
36
+ version_requirements: *2169353840
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: sqlite3-ruby
39
- requirement: &2168673660 !ruby/object:Gem::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: *2168673660
47
+ version_requirements: *2169353240
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: cucumber
50
- requirement: &2168672720 !ruby/object:Gem::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: *2168672720
58
+ version_requirements: *2169352820
59
59
  - !ruby/object:Gem::Dependency
60
60
  name: ci_reporter
61
- requirement: &2168672140 !ruby/object:Gem::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: *2168672140
69
+ version_requirements: *2169352320
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: rails
72
- requirement: &2168671320 !ruby/object:Gem::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: *2168671320
80
+ version_requirements: *2169351620
81
81
  - !ruby/object:Gem::Dependency
82
82
  name: activerecord
83
- requirement: &2168670740 !ruby/object:Gem::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: *2168670740
91
+ version_requirements: *2169350680
92
92
  - !ruby/object:Gem::Dependency
93
93
  name: paperclip
94
- requirement: &2168670280 !ruby/object:Gem::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: *2168670280
102
+ version_requirements: *2169350180
103
103
  - !ruby/object:Gem::Dependency
104
104
  name: to_xls
105
- requirement: &2168669560 !ruby/object:Gem::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: *2168669560
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: