activehistory 0.2 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +2 -2
- data/activehistory.gemspec +1 -1
- data/lib/activehistory.rb +5 -2
- data/lib/activehistory/adapters/active_record.rb +12 -6
- data/lib/activehistory/event.rb +15 -5
- data/lib/activehistory/version.rb +1 -1
- data/test/active_record_adapter/association_test/has_many_association_test.rb +65 -19
- data/test/test_helper.rb +18 -3
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 738938e4c7303437ed611ad74d5b48736f5590a6
|
4
|
+
data.tar.gz: 5e0a178f69fd5cff99025256a819bccb30e35445
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aa67c5b5797e87d86122853469a87b25af576baed17e7672cb81290ee68707038d3368afc09a9b7bcefc2be43605a3ac206fedbb9626dd21d5c46c5f66345849
|
7
|
+
data.tar.gz: f535f87bceff3329c4850d584c78201decc0ba7af419fcd9e3f288d1f9d84ea9607b39eaaf13fb7765970d1e41435d2c077918e3898d27a7f5763f3c6bcb9359
|
data/Gemfile.lock
CHANGED
data/activehistory.gemspec
CHANGED
@@ -36,6 +36,6 @@ Gem::Specification.new do |s|
|
|
36
36
|
# s.add_runtime_dependency 'msgpack'
|
37
37
|
# s.add_runtime_dependency 'cookie_store'
|
38
38
|
s.add_runtime_dependency 'arel', '~> 7.0'
|
39
|
-
s.add_runtime_dependency 'activerecord', '5.0
|
39
|
+
s.add_runtime_dependency 'activerecord', '~> 5.0'
|
40
40
|
s.add_runtime_dependency 'globalid', '~> 0.3.7'
|
41
41
|
end
|
data/lib/activehistory.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
1
3
|
module ActiveHistory
|
2
4
|
|
3
|
-
mattr_accessor :connection
|
5
|
+
mattr_accessor :connection, :logger
|
4
6
|
|
5
7
|
UUIDV4 = /^[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i
|
6
8
|
|
7
9
|
def self.configure(settings)
|
10
|
+
self.logger = settings&.delete(:logger) || Logger.new(STDOUT)
|
8
11
|
@@connection = ActiveHistory::Connection.new(settings)
|
9
12
|
end
|
10
13
|
|
@@ -27,7 +30,7 @@ module ActiveHistory
|
|
27
30
|
|
28
31
|
yield
|
29
32
|
ensure
|
30
|
-
if Thread.current[:activehistory_event] && !Thread.current[:activehistory_event].actions.empty?
|
33
|
+
if configured? && Thread.current[:activehistory_event] && !Thread.current[:activehistory_event].actions.empty?
|
31
34
|
Thread.current[:activehistory_event].save!
|
32
35
|
end
|
33
36
|
Thread.current[:activehistory_event] = nil
|
@@ -45,7 +45,6 @@ module ActiveHistory::Adapter
|
|
45
45
|
else
|
46
46
|
reflection_or_relation_name
|
47
47
|
end
|
48
|
-
puts "#{self}##{id}.#{reflection.name} +#{added.inspect} -#{removed.inspect}"
|
49
48
|
|
50
49
|
action = ActiveHistory.current_event(timestamp: timestamp).action_for(self, id, { type: type, timestamp: timestamp })
|
51
50
|
|
@@ -54,9 +53,18 @@ module ActiveHistory::Adapter
|
|
54
53
|
action.diff[diff_key] ||= [[], []]
|
55
54
|
action.diff[diff_key][0] |= removed
|
56
55
|
action.diff[diff_key][1] |= added
|
56
|
+
in_common = (action.diff[diff_key][0] & action.diff[diff_key][1])
|
57
|
+
if !in_common.empty?
|
58
|
+
action.diff[diff_key][0] = action.diff[diff_key][0] - in_common
|
59
|
+
action.diff[diff_key][1] = action.diff[diff_key][1] - in_common
|
60
|
+
end
|
57
61
|
else
|
58
62
|
diff_key = "#{reflection.name.to_s.singularize}_id"
|
59
|
-
action.diff[diff_key]
|
63
|
+
if action.diff.has_key?(diff_key) && action.diff[diff_key][0] == added.first
|
64
|
+
action.diff.delete(diff_key)
|
65
|
+
else
|
66
|
+
action.diff[diff_key] ||= [removed.first, added.first]
|
67
|
+
end
|
60
68
|
end
|
61
69
|
|
62
70
|
|
@@ -220,8 +228,6 @@ module ActiveHistory::Adapter
|
|
220
228
|
timestamp: timestamp,
|
221
229
|
type: type
|
222
230
|
})
|
223
|
-
|
224
|
-
|
225
231
|
end
|
226
232
|
|
227
233
|
def activehistory_association_udpated(reflection, id, added: [], removed: [], timestamp: nil, type: :update)
|
@@ -394,10 +400,11 @@ module ActiveRecord
|
|
394
400
|
|
395
401
|
def activehistory_encapsulate
|
396
402
|
@activehistory_timestamp = Time.now.utc
|
403
|
+
|
397
404
|
if !Thread.current[:activehistory_save_lock]
|
398
405
|
run_save = true
|
399
406
|
Thread.current[:activehistory_save_lock] = true
|
400
|
-
if
|
407
|
+
if Thread.current[:activehistory_event].nil?
|
401
408
|
destroy_current_event = true
|
402
409
|
Thread.current[:activehistory_event] = ActiveHistory::Event.new(timestamp: @activehistory_timestamp)
|
403
410
|
end
|
@@ -418,7 +425,6 @@ module ActiveRecord
|
|
418
425
|
if destroy_current_event
|
419
426
|
Thread.current[:activehistory_event] = nil
|
420
427
|
end
|
421
|
-
|
422
428
|
end
|
423
429
|
|
424
430
|
end
|
data/lib/activehistory/event.rb
CHANGED
@@ -41,7 +41,13 @@ class ActiveHistory::Event
|
|
41
41
|
action = @actions.find { |a| a.subject_type.to_s == type.to_s && a.subject_id.to_s == id.to_s }
|
42
42
|
|
43
43
|
if new_options
|
44
|
-
|
44
|
+
if action
|
45
|
+
action.diff.merge!(new_options[:diff]) if new_options.has_key?(:diff)
|
46
|
+
action
|
47
|
+
else
|
48
|
+
action!({ subject_type: type, subject_id: id, type: :update }.merge(new_options))
|
49
|
+
end
|
50
|
+
|
45
51
|
else
|
46
52
|
action
|
47
53
|
end
|
@@ -59,14 +65,18 @@ class ActiveHistory::Event
|
|
59
65
|
|
60
66
|
def _update
|
61
67
|
return if actions.empty?
|
62
|
-
|
63
|
-
|
64
|
-
})
|
68
|
+
actions.delete_if { |a| a.diff.empty? }
|
69
|
+
payload = JSON.generate({actions: actions.as_json.map{ |json| json[:event_id] = id; json }})
|
70
|
+
ActiveHistory.logger.debug("[ActiveHistory] POST /actions WITH #{payload}")
|
71
|
+
ActiveHistory.connection.post('/actions', payload)
|
65
72
|
@actions = []
|
66
73
|
end
|
67
74
|
|
68
75
|
def _create
|
69
|
-
|
76
|
+
actions.delete_if { |a| a.diff.empty? }
|
77
|
+
payload = JSON.generate(self.as_json)
|
78
|
+
ActiveHistory.logger.debug("[ActiveHistory] POST /events WITH #{payload}")
|
79
|
+
ActiveHistory.connection.post('/events', payload)
|
70
80
|
@actions = []
|
71
81
|
@persisted = true
|
72
82
|
end
|
@@ -12,7 +12,7 @@ class HasManyAssociationTest < ActiveSupport::TestCase
|
|
12
12
|
WebMock::RequestRegistry.instance.reset!
|
13
13
|
|
14
14
|
@photo = travel_to(@time) { create(:photo, property: @property) }
|
15
|
-
|
15
|
+
|
16
16
|
assert_posted("/events") do
|
17
17
|
assert_action_for @photo, {
|
18
18
|
diff: {
|
@@ -71,13 +71,13 @@ class HasManyAssociationTest < ActiveSupport::TestCase
|
|
71
71
|
test 'has_many <<'
|
72
72
|
test 'has_many.delete'
|
73
73
|
test 'has_many.destroy'
|
74
|
-
|
74
|
+
|
75
75
|
test 'has_many=' do
|
76
76
|
@property = create(:property)
|
77
77
|
@photo1 = create(:photo)
|
78
78
|
@photo2 = create(:photo)
|
79
79
|
WebMock::RequestRegistry.instance.reset!
|
80
|
-
|
80
|
+
|
81
81
|
travel_to(@time) { @property.photos = [@photo1] }
|
82
82
|
assert_posted("/events") do
|
83
83
|
assert_action_for @photo1, {
|
@@ -96,10 +96,10 @@ class HasManyAssociationTest < ActiveSupport::TestCase
|
|
96
96
|
type: 'update'
|
97
97
|
}
|
98
98
|
end
|
99
|
-
|
99
|
+
|
100
100
|
WebMock::RequestRegistry.instance.reset!
|
101
101
|
travel_to(@time) { @property.photos = [@photo2] }
|
102
|
-
assert_posted("/events") do
|
102
|
+
assert_posted("/events") do
|
103
103
|
assert_action_for @photo2, {
|
104
104
|
diff: { property_id: [nil, @property.id] },
|
105
105
|
subject_type: "Photo",
|
@@ -107,7 +107,7 @@ class HasManyAssociationTest < ActiveSupport::TestCase
|
|
107
107
|
timestamp: @time.iso8601(3),
|
108
108
|
type: 'update'
|
109
109
|
}
|
110
|
-
|
110
|
+
|
111
111
|
assert_action_for @property, {
|
112
112
|
diff: { photo_ids: [[@photo1.id], [@photo2.id]] },
|
113
113
|
subject_type: "Property",
|
@@ -115,7 +115,7 @@ class HasManyAssociationTest < ActiveSupport::TestCase
|
|
115
115
|
timestamp: @time.iso8601(3),
|
116
116
|
type: 'update'
|
117
117
|
}
|
118
|
-
|
118
|
+
|
119
119
|
assert_action_for @photo1, {
|
120
120
|
diff: { property_id: [@property.id, nil] },
|
121
121
|
subject_type: "Photo",
|
@@ -125,13 +125,13 @@ class HasManyAssociationTest < ActiveSupport::TestCase
|
|
125
125
|
}
|
126
126
|
end
|
127
127
|
end
|
128
|
-
|
128
|
+
|
129
129
|
test 'has_many_ids=' do
|
130
130
|
@property = create(:property)
|
131
131
|
@photo1 = create(:photo)
|
132
132
|
@photo2 = create(:photo)
|
133
133
|
WebMock::RequestRegistry.instance.reset!
|
134
|
-
|
134
|
+
|
135
135
|
travel_to(@time) { @property.photo_ids = [@photo1].map(&:id) }
|
136
136
|
assert_posted("/events") do
|
137
137
|
assert_action_for @property, {
|
@@ -141,7 +141,7 @@ class HasManyAssociationTest < ActiveSupport::TestCase
|
|
141
141
|
timestamp: @time.iso8601(3),
|
142
142
|
type: 'update'
|
143
143
|
}
|
144
|
-
|
144
|
+
|
145
145
|
assert_action_for @photo1, {
|
146
146
|
diff: { property_id: [nil, @property.id] },
|
147
147
|
subject_type: "Photo",
|
@@ -150,10 +150,10 @@ class HasManyAssociationTest < ActiveSupport::TestCase
|
|
150
150
|
type: 'update'
|
151
151
|
}
|
152
152
|
end
|
153
|
-
|
153
|
+
|
154
154
|
WebMock::RequestRegistry.instance.reset!
|
155
155
|
travel_to(@time) { @property.photo_ids = [@photo2].map(&:id) }
|
156
|
-
assert_posted("/events") do
|
156
|
+
assert_posted("/events") do
|
157
157
|
assert_action_for @photo2, {
|
158
158
|
diff: { property_id: [nil, @property.id] },
|
159
159
|
subject_type: "Photo",
|
@@ -161,7 +161,7 @@ class HasManyAssociationTest < ActiveSupport::TestCase
|
|
161
161
|
timestamp: @time.iso8601(3),
|
162
162
|
type: 'update'
|
163
163
|
}
|
164
|
-
|
164
|
+
|
165
165
|
assert_action_for @property, {
|
166
166
|
diff: { photo_ids: [[@photo1.id], [@photo2.id]] },
|
167
167
|
subject_type: "Property",
|
@@ -169,7 +169,7 @@ class HasManyAssociationTest < ActiveSupport::TestCase
|
|
169
169
|
timestamp: @time.iso8601(3),
|
170
170
|
type: 'update'
|
171
171
|
}
|
172
|
-
|
172
|
+
|
173
173
|
assert_action_for @photo1, {
|
174
174
|
diff: { property_id: [@property.id, nil] },
|
175
175
|
subject_type: "Photo",
|
@@ -179,16 +179,16 @@ class HasManyAssociationTest < ActiveSupport::TestCase
|
|
179
179
|
}
|
180
180
|
end
|
181
181
|
end
|
182
|
-
|
182
|
+
|
183
183
|
test 'has_many.clear' do
|
184
184
|
@photo1 = create(:photo)
|
185
185
|
@photo2 = create(:photo)
|
186
186
|
@property = create(:property, photos: [@photo1, @photo2])
|
187
187
|
WebMock::RequestRegistry.instance.reset!
|
188
|
-
|
188
|
+
|
189
189
|
travel_to(@time) { @property.photos.clear }
|
190
190
|
assert_posted("/events") do |req|
|
191
|
-
|
191
|
+
|
192
192
|
assert_action_for @property, {
|
193
193
|
diff: { photo_ids: [[@photo1, @photo2].map(&:id), []] },
|
194
194
|
subject_type: "Property",
|
@@ -196,7 +196,7 @@ class HasManyAssociationTest < ActiveSupport::TestCase
|
|
196
196
|
timestamp: @time.iso8601(3),
|
197
197
|
type: 'update'
|
198
198
|
}
|
199
|
-
|
199
|
+
|
200
200
|
assert_action_for @photo1, {
|
201
201
|
diff: { property_id: [@property.id, nil] },
|
202
202
|
subject_type: "Photo",
|
@@ -204,7 +204,7 @@ class HasManyAssociationTest < ActiveSupport::TestCase
|
|
204
204
|
timestamp: @time.iso8601(3),
|
205
205
|
type: 'update'
|
206
206
|
}
|
207
|
-
|
207
|
+
|
208
208
|
assert_action_for @photo2, {
|
209
209
|
diff: { property_id: [@property.id, nil] },
|
210
210
|
subject_type: "Photo",
|
@@ -215,6 +215,52 @@ class HasManyAssociationTest < ActiveSupport::TestCase
|
|
215
215
|
end
|
216
216
|
end
|
217
217
|
|
218
|
+
test 'has_many.clear, has_many=, while updating model; a werid example but could happen in an after_save callback etc..' do
|
219
|
+
@photo1 = create(:photo)
|
220
|
+
@photo2 = create(:photo)
|
221
|
+
@property = create(:property, name: 'old name', photos: [@photo1])
|
222
|
+
WebMock::RequestRegistry.instance.reset!
|
223
|
+
|
224
|
+
travel_to(@time) {
|
225
|
+
begin
|
226
|
+
Thread.current[:activehistory_save_lock] = true
|
227
|
+
Thread.current[:activehistory_event] = ActiveHistory::Event.new()
|
228
|
+
@property.name = 'new name'
|
229
|
+
@property.photos.clear
|
230
|
+
@property.photos = [@photo2, @photo1]
|
231
|
+
@property.save
|
232
|
+
ActiveHistory.current_event.save!
|
233
|
+
ensure
|
234
|
+
Thread.current[:activehistory_save_lock] = false
|
235
|
+
Thread.current[:activehistory_event] = nil
|
236
|
+
end
|
237
|
+
}
|
238
|
+
|
239
|
+
assert_posted("/events") do |req|
|
240
|
+
|
241
|
+
assert_action_for @property, {
|
242
|
+
diff: {
|
243
|
+
photo_ids: [[], [@photo2].map(&:id)],
|
244
|
+
name: ['old name', 'new name']
|
245
|
+
},
|
246
|
+
subject_type: "Property",
|
247
|
+
subject_id: @property.id,
|
248
|
+
timestamp: @time.iso8601(3),
|
249
|
+
type: 'update'
|
250
|
+
}
|
251
|
+
|
252
|
+
assert_no_action_for @photo1
|
253
|
+
|
254
|
+
assert_action_for @photo2, {
|
255
|
+
diff: { property_id: [nil, @property.id] },
|
256
|
+
subject_type: "Photo",
|
257
|
+
subject_id: @photo2.id,
|
258
|
+
timestamp: @time.iso8601(3),
|
259
|
+
type: 'update'
|
260
|
+
}
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
218
264
|
test 'has_many.create'
|
219
265
|
|
220
266
|
end
|
data/test/test_helper.rb
CHANGED
@@ -17,7 +17,8 @@ require 'active_record'
|
|
17
17
|
require 'activehistory'
|
18
18
|
|
19
19
|
ActiveHistory.configure({
|
20
|
-
url: 'http://activehistory.com'
|
20
|
+
url: 'http://activehistory.com',
|
21
|
+
logger: Logger.new("/dev/null")
|
21
22
|
})
|
22
23
|
ActiveRecord::Base.establish_connection({
|
23
24
|
adapter: 'postgresql',
|
@@ -40,6 +41,12 @@ class ActiveSupport::TestCase
|
|
40
41
|
WebMock.stub_request(:any, /^http:\/\/activehistory.com\/.*/)
|
41
42
|
end
|
42
43
|
|
44
|
+
set_callback(:teardown, :after) do
|
45
|
+
if !Thread.current[:activehistory_event].nil?
|
46
|
+
raise 'no nil'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
43
50
|
def assert_posted(path, &block)
|
44
51
|
assert_requested(:post, "#{ActiveHistory.url}#{path}", times: 1) do |req|
|
45
52
|
@req = JSON.parse(req.body)
|
@@ -48,13 +55,21 @@ class ActiveSupport::TestCase
|
|
48
55
|
end
|
49
56
|
|
50
57
|
def assert_action_for(model, expected)
|
51
|
-
action = @req['actions'].find do |
|
52
|
-
|
58
|
+
action = @req['actions'].find do |a|
|
59
|
+
a['subject_type'] == model.class.base_class.model_name.name && a['subject_id'] == model.id
|
53
60
|
end
|
54
61
|
|
55
62
|
assert_equal(expected.as_json, action)
|
56
63
|
end
|
57
64
|
|
65
|
+
def assert_no_action_for(model)
|
66
|
+
action = @req['actions'].find do |a|
|
67
|
+
a['subject_type'] == model.class.base_class.model_name.name && a['subject_id'] == model.id
|
68
|
+
end
|
69
|
+
|
70
|
+
assert_nil action
|
71
|
+
end
|
72
|
+
|
58
73
|
def assert_not_posted(path)
|
59
74
|
assert_not_requested(:post, "#{ActiveHistory.url}#{path}")
|
60
75
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activehistory
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jon Bracy
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-12-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -210,16 +210,16 @@ dependencies:
|
|
210
210
|
name: activerecord
|
211
211
|
requirement: !ruby/object:Gem::Requirement
|
212
212
|
requirements:
|
213
|
-
- -
|
213
|
+
- - "~>"
|
214
214
|
- !ruby/object:Gem::Version
|
215
|
-
version: 5.0
|
215
|
+
version: '5.0'
|
216
216
|
type: :runtime
|
217
217
|
prerelease: false
|
218
218
|
version_requirements: !ruby/object:Gem::Requirement
|
219
219
|
requirements:
|
220
|
-
- -
|
220
|
+
- - "~>"
|
221
221
|
- !ruby/object:Gem::Version
|
222
|
-
version: 5.0
|
222
|
+
version: '5.0'
|
223
223
|
- !ruby/object:Gem::Dependency
|
224
224
|
name: globalid
|
225
225
|
requirement: !ruby/object:Gem::Requirement
|
@@ -290,7 +290,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
290
290
|
version: '0'
|
291
291
|
requirements: []
|
292
292
|
rubyforge_project:
|
293
|
-
rubygems_version: 2.
|
293
|
+
rubygems_version: 2.5.1
|
294
294
|
signing_key:
|
295
295
|
specification_version: 4
|
296
296
|
summary: Track changes to ActiveRecord models
|