activehistory 0.1 → 0.2

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.
@@ -8,19 +8,16 @@ class ActiveHistory::Connection
8
8
  def initialize(config)
9
9
  if config[:url]
10
10
  uri = URI.parse(config.delete(:url))
11
- config[:api_key] ||= (uri.user ? CGI.unescape(uri.user) : nil)
12
- config[:host] ||= uri.host
13
- config[:port] ||= uri.port
14
- config[:ssl] ||= (uri.scheme == 'https')
11
+ config[:api_key] ||= (uri.user ? CGI.unescape(uri.user) : nil)
12
+ config[:host] ||= uri.host
13
+ config[:port] ||= uri.port
14
+ config[:ssl] ||= (uri.scheme == 'https')
15
15
  end
16
16
 
17
17
  [:api_key, :host, :port, :ssl, :user_agent].each do |key|
18
18
  self.instance_variable_set(:"@#{key}", config[key])
19
19
  end
20
20
 
21
- @connection = Net::HTTP.new(host, port)
22
- @connection.use_ssl = ssl
23
-
24
21
  true
25
22
  end
26
23
 
@@ -61,27 +58,18 @@ class ActiveHistory::Connection
61
58
  return_value = nil
62
59
  retry_count = 0
63
60
  begin
64
- @connection.request(request) do |response|
65
- if response['API-Version-Deprecated']
66
- logger.warn("DEPRECATION WARNING: API v#{API_VERSION} is being phased out")
67
- end
68
-
61
+ connection = Net::HTTP.new(host, port)
62
+ connection.use_ssl = ssl
63
+ connection.request(request) do |response|
69
64
  validate_response_code(response)
70
-
71
- # Get the cookies
72
- response.each_header do |key, value|
73
- if key.downcase == 'set-cookie' && Thread.current[:sunstone_cookie_store]
74
- Thread.current[:sunstone_cookie_store].set_cookie(request_uri, value)
75
- end
76
- end
77
-
65
+
78
66
  if block_given?
79
- return_value =yield(response)
67
+ return_value = yield(response)
80
68
  else
81
- return_value =response
69
+ return_value = response
82
70
  end
83
71
  end
84
- rescue ActiveRecord::ConnectionNotEstablished
72
+ rescue ActiveHistory::Exception::ServiceUnavailable
85
73
  retry_count += 1
86
74
  retry_count == 1 ? retry : raise
87
75
  end
@@ -1,50 +1,104 @@
1
+ require 'globalid'
2
+ require 'securerandom'
3
+
4
+ GlobalID::Locator.use :activehistory do |gid|
5
+ ActiveHistory::Event.new({ id: gid.model_id })
6
+ end
7
+
1
8
  class ActiveHistory::Event
2
-
3
- attr_accessor :actions, :regards, :timestamp
9
+ include GlobalID::Identification
10
+
11
+ attr_accessor :id, :ip, :user_agent, :session_id, :metadata, :timestamp, :performed_by_id, :performed_by_type, :actions
4
12
 
5
13
  def initialize(attrs={})
6
- @attrs = attrs
7
- @attrs[:timestamp] ||= Time.now
8
- @actions = []
9
- @regards = []
14
+ attrs.each do |k,v|
15
+ self.send("#{k}=", v)
16
+ end
17
+
18
+ if id
19
+ @persisted = true
20
+ else
21
+ @persisted = false
22
+ @id ||= SecureRandom.uuid
23
+ end
24
+
25
+ @actions ||= []
26
+ @timestamp ||= Time.now
10
27
  end
11
28
 
29
+ def persisted?
30
+ @persisted
31
+ end
32
+
12
33
  def action!(action)
13
34
  action = ActiveHistory::Action.new(action)
14
35
  @actions << action
15
36
  action
16
37
  end
17
38
 
18
- def action_for(id)
19
- @actions.find { |a| a.subject == id }
39
+ def action_for(type, id, new_options=nil)
40
+ type = type.base_class.model_name.name if !type.is_a?(String)
41
+ action = @actions.find { |a| a.subject_type.to_s == type.to_s && a.subject_id.to_s == id.to_s }
42
+
43
+ if new_options
44
+ action || action!({ subject_type: type, subject_id: id, type: :update }.merge(new_options))
45
+ else
46
+ action
47
+ end
48
+ end
49
+
50
+ def self.create!(attrs={})
51
+ event = self.new(attrs)
52
+ event.save!
53
+ event
54
+ end
55
+
56
+ def save!
57
+ persisted? ? _update : _create
20
58
  end
21
59
 
22
- def regard!(regard)
23
- @regards << regard
60
+ def _update
61
+ return if actions.empty?
62
+ ActiveHistory.connection.post('/actions', {
63
+ actions: actions.as_json.map{|json| json[:event_id] = id; json}
64
+ })
65
+ @actions = []
24
66
  end
25
67
 
26
- def save!
27
- return if @actions.empty?
28
-
68
+ def _create
29
69
  ActiveHistory.connection.post('/events', self.as_json)
70
+ @actions = []
71
+ @persisted = true
30
72
  end
31
-
73
+
32
74
  def as_json
33
75
  {
34
- ip: @attrs[:ip],
35
- user_agent: @attrs[:user_agent],
36
- session_id: @attrs[:session_id],
37
- account: @attrs[:account],
38
- api_key: @attrs[:api_key],
39
- metadata: @attrs[:metadata],
40
- timestamp: @attrs[:timestamp].utc.iso8601(3),
41
- actions: @actions.map(&:as_json),
42
- regards: @regards.map(&:as_json)
76
+ id: id,
77
+ ip: ip,
78
+ user_agent: user_agent,
79
+ session_id: session_id,
80
+ metadata: metadata,
81
+ performed_by_type: performed_by_type,
82
+ performed_by_id: performed_by_id,
83
+ timestamp: timestamp.utc.iso8601(3),
84
+ actions: actions.as_json
43
85
  }
44
86
  end
87
+
88
+ def to_gid_param(options={})
89
+ to_global_id(options).to_param
90
+ end
91
+
92
+ def to_global_id(options={})
93
+ @global_id ||= GlobalID.create(self, { app: :activehistory }.merge(options))
94
+ end
95
+
96
+ def to_sgid_param(options={})
97
+ to_signed_global_id(options).to_param
98
+ end
45
99
 
46
- def encapsulate
47
- ensure
100
+ def to_signed_global_id(options={})
101
+ SignedGlobalID.create(self, { app: :activehistory }.merge(options))
48
102
  end
49
103
 
50
104
  end
@@ -1,3 +1,3 @@
1
1
  module ActiveHistory
2
- VERSION = '0.1'
3
- end
2
+ VERSION = '0.2'
3
+ end
@@ -13,70 +13,78 @@ class BelongsToAssociationTest < ActiveSupport::TestCase
13
13
 
14
14
  @target = travel_to(@time) { create(:photo, account: @model) }
15
15
 
16
- assert_posted("/events") do |req|
17
- req_data = JSON.parse(req.body)
18
- assert_equal 2, req_data['actions'].size
19
-
20
- assert_equal 'update', req_data['actions'][1]['type']
21
- assert_equal "Account/#{@model.id}", req_data['actions'][1]['subject']
22
- assert_equal [[], [@target.id]], req_data['actions'][1]['diff']['photo_ids']
16
+ assert_posted("/events") do
17
+ assert_action_for @model, {
18
+ diff: { photo_ids: [[], [@target.id]] },
19
+ timestamp: @time.iso8601(3),
20
+ type: 'update',
21
+ subject_type: "Account",
22
+ subject_id: @model.id,
23
+ }
23
24
  end
24
25
  end
25
-
26
+
26
27
  test 'TargetModel.update removes from association' do
27
28
  @model = create(:account)
28
29
  @target = create(:photo, account: @model)
29
30
  WebMock::RequestRegistry.instance.reset!
30
-
31
+
31
32
  travel_to(@time) { @target.update(account: nil) }
32
-
33
- assert_posted("/events") do |req|
34
- req_data = JSON.parse(req.body)
35
- assert_equal 2, req_data['actions'].size
36
33
 
37
- assert_equal 'update', req_data['actions'][1]['type']
38
- assert_equal "Account/#{@model.id}", req_data['actions'][1]['subject']
39
- assert_equal [[@target.id], []], req_data['actions'][1]['diff']['photo_ids']
34
+ assert_posted("/events") do
35
+ assert_action_for @model, {
36
+ diff: { photo_ids: [[@target.id], []] },
37
+ timestamp: @time.iso8601(3),
38
+ type: 'update',
39
+ subject_type: "Account",
40
+ subject_id: @model.id,
41
+ }
40
42
  end
41
43
  end
42
-
44
+
43
45
  test 'TargetModel.update changes association' do
44
46
  @model1 = create(:account)
45
47
  @model2 = create(:account)
46
48
  @target = create(:photo, account: @model1)
47
49
  WebMock::RequestRegistry.instance.reset!
48
-
50
+
49
51
  travel_to(@time) { @target.update(account: @model2) }
50
-
51
- assert_posted("/events") do |req|
52
- req_data = JSON.parse(req.body)
53
- assert_equal 3, req_data['actions'].size
54
52
 
55
- assert_equal 'update', req_data['actions'][1]['type']
56
- assert_equal "Account/#{@model1.id}", req_data['actions'][1]['subject']
57
- assert_equal [[@target.id], []], req_data['actions'][1]['diff']['photo_ids']
53
+ assert_posted("/events") do
54
+ assert_action_for @model1, {
55
+ diff: { photo_ids: [[@target.id], []] },
56
+ timestamp: @time.iso8601(3),
57
+ type: 'update',
58
+ subject_type: "Account",
59
+ subject_id: @model1.id,
60
+ }
58
61
 
59
- assert_equal 'update', req_data['actions'][2]['type']
60
- assert_equal "Account/#{@model2.id}", req_data['actions'][2]['subject']
61
- assert_equal [[], [@target.id]], req_data['actions'][2]['diff']['photo_ids']
62
+ assert_action_for @model2, {
63
+ diff: { photo_ids: [[], [@target.id]] },
64
+ timestamp: @time.iso8601(3),
65
+ type: 'update',
66
+ subject_type: "Account",
67
+ subject_id: @model2.id,
68
+ }
62
69
  end
63
70
  end
64
-
71
+
65
72
  test 'TargetModel.destroy removes from association' do
66
73
  @model = create(:account)
67
74
  @target = create(:photo, account: @model)
68
75
  WebMock::RequestRegistry.instance.reset!
69
-
76
+
70
77
  travel_to(@time) { @target.destroy }
71
-
72
- assert_posted("/events") do |req|
73
- req_data = JSON.parse(req.body)
74
- assert_equal 2, req_data['actions'].size
75
78
 
76
- assert_equal 'update', req_data['actions'][1]['type']
77
- assert_equal "Account/#{@model.id}", req_data['actions'][1]['subject']
78
- assert_equal [[@target.id], []], req_data['actions'][1]['diff']['photo_ids']
79
+ assert_posted("/events") do
80
+ assert_action_for @model, {
81
+ diff: { photo_ids: [[@target.id], []] },
82
+ timestamp: @time.iso8601(3),
83
+ type: 'update',
84
+ subject_type: "Account",
85
+ subject_id: @model.id,
86
+ }
79
87
  end
80
88
  end
81
-
89
+
82
90
  end
@@ -12,57 +12,54 @@ class HasAndBelongsToManyAssociationTest < ActiveSupport::TestCase
12
12
  WebMock::RequestRegistry.instance.reset!
13
13
 
14
14
  @region = travel_to(@time) { create(:region, properties: [@property]) }
15
- assert_posted("/events") do |req|
16
- req_data = JSON.parse(req.body)
17
- assert_equal 2, req_data['actions'].size
18
-
19
- assert_equal req_data['actions'][0], {
15
+
16
+ assert_posted("/events") do
17
+ assert_action_for @region, {
20
18
  diff: {
21
19
  id: [nil, @region.id],
22
20
  name: [nil, @region.name],
23
21
  property_ids: [[], [@property.id]]
24
22
  },
25
- subject: "Region/#{@region.id}",
23
+ subject_type: "Region",
24
+ subject_id: @region.id,
26
25
  timestamp: @time.iso8601(3),
27
26
  type: 'create'
28
- }.as_json
29
-
30
- assert_equal req_data['actions'][1], {
27
+ }
28
+
29
+ assert_action_for @property, {
31
30
  timestamp: @time.iso8601(3),
32
31
  type: 'update',
33
- subject: "Property/#{@property.id}",
32
+ subject_type: "Property",
33
+ subject_id: @property.id,
34
34
  diff: {
35
35
  region_ids: [[], [@region.id]]
36
36
  }
37
- }.as_json
37
+ }
38
38
  end
39
39
  end
40
40
 
41
41
  test '::create with new has_and_belongs_to_many association' do
42
- WebMock::RequestRegistry.instance.reset!
43
-
44
42
  @region = travel_to(@time) { create(:region, properties: [build(:property)]) }
45
43
  @property = @region.properties.first
46
44
 
47
- assert_posted("/events") do |req|
48
- req_data = JSON.parse(req.body)
49
- assert_equal 2, req_data['actions'].size
50
-
51
- assert_equal req_data['actions'][0], {
45
+ assert_posted("/events") do
46
+ assert_action_for @region, {
52
47
  diff: {
53
48
  id: [nil, @region.id],
54
49
  name: [nil, @region.name],
55
50
  property_ids: [[], [@property.id]]
56
51
  },
57
- subject: "Region/#{@region.id}",
52
+ subject_type: "Region",
53
+ subject_id: @region.id,
58
54
  timestamp: @time.iso8601(3),
59
55
  type: 'create'
60
- }.as_json
56
+ }
61
57
 
62
- assert_equal req_data['actions'][1], {
58
+ assert_action_for @property, {
63
59
  timestamp: @time.iso8601(3),
64
60
  type: 'create',
65
- subject: "Property/#{@property.id}",
61
+ subject_type: "Property",
62
+ subject_id: @property.id,
66
63
  diff: {
67
64
  id: [nil, @property.id],
68
65
  name: [nil, @property.name],
@@ -74,7 +71,7 @@ class HasAndBelongsToManyAssociationTest < ActiveSupport::TestCase
74
71
  active: [nil, @property.active],
75
72
  region_ids: [[], [@region.id]]
76
73
  }
77
- }.as_json
74
+ }
78
75
  end
79
76
  end
80
77
 
@@ -85,27 +82,26 @@ class HasAndBelongsToManyAssociationTest < ActiveSupport::TestCase
85
82
 
86
83
  travel_to(@time) { @region.update(properties: [@property]) }
87
84
 
88
- assert_posted("/events") do |req|
89
- req_data = JSON.parse(req.body)
90
- assert_equal 2, req_data['actions'].size
91
-
92
- assert_equal req_data['actions'][0], {
85
+ assert_posted("/events") do
86
+ assert_action_for @region, {
93
87
  diff: {
94
88
  property_ids: [[], [@property.id]]
95
89
  },
96
- subject: "Region/#{@region.id}",
90
+ subject_type: "Region",
91
+ subject_id: @region.id,
97
92
  timestamp: @time.iso8601(3),
98
93
  type: 'update'
99
- }.as_json
94
+ }
100
95
 
101
- assert_equal req_data['actions'][1], {
96
+ assert_action_for @property, {
102
97
  timestamp: @time.iso8601(3),
103
98
  type: 'update',
104
- subject: "Property/#{@property.id}",
99
+ subject_type: "Property",
100
+ subject_id: @property.id,
105
101
  diff: {
106
102
  region_ids: [[], [@region.id]]
107
103
  }
108
- }.as_json
104
+ }
109
105
  end
110
106
  end
111
107
 
@@ -116,14 +112,12 @@ class HasAndBelongsToManyAssociationTest < ActiveSupport::TestCase
116
112
  travel_to(@time) { @region.update(properties: [build(:property)]) }
117
113
  @property = @region.properties.first
118
114
 
119
- assert_posted("/events") do |req|
120
- req_data = JSON.parse(req.body)
121
- assert_equal 2, req_data['actions'].size
122
-
123
- assert_equal req_data['actions'][0], {
115
+ assert_posted("/events") do
116
+ assert_action_for @property, {
124
117
  timestamp: @time.iso8601(3),
125
118
  type: 'create',
126
- subject: "Property/#{@property.id}",
119
+ subject_type: "Property",
120
+ subject_id: @property.id,
127
121
  diff: {
128
122
  id: [nil, @property.id],
129
123
  name: [nil, @property.name],
@@ -135,16 +129,17 @@ class HasAndBelongsToManyAssociationTest < ActiveSupport::TestCase
135
129
  active: [nil, @property.active],
136
130
  region_ids: [[], [@region.id]]
137
131
  }
138
- }.as_json
132
+ }
139
133
 
140
- assert_equal req_data['actions'][1], {
134
+ assert_action_for @region, {
141
135
  diff: {
142
136
  property_ids: [[], [@property.id]]
143
137
  },
144
- subject: "Region/#{@region.id}",
138
+ subject_type: "Region",
139
+ subject_id: @region.id,
145
140
  timestamp: @time.iso8601(3),
146
141
  type: 'update'
147
- }.as_json
142
+ }
148
143
  end
149
144
  end
150
145
 
@@ -152,30 +147,70 @@ class HasAndBelongsToManyAssociationTest < ActiveSupport::TestCase
152
147
  @property = create(:property)
153
148
  @region = create(:region, properties: [@property])
154
149
  WebMock::RequestRegistry.instance.reset!
155
-
150
+
156
151
  travel_to(@time) { @region.update(properties: []) }
157
152
 
158
- assert_posted("/events") do |req|
159
- req_data = JSON.parse(req.body)
160
- assert_equal 2, req_data['actions'].size
161
-
162
- assert_equal req_data['actions'][0], {
153
+ assert_posted("/events") do
154
+ assert_action_for @region, {
163
155
  diff: {
164
156
  property_ids: [[@property.id], []]
165
157
  },
166
- subject: "Region/#{@region.id}",
158
+ subject_type: "Region",
159
+ subject_id: @region.id,
167
160
  timestamp: @time.iso8601(3),
168
161
  type: 'update'
169
- }.as_json
162
+ }
170
163
 
171
- assert_equal req_data['actions'][1], {
164
+ assert_action_for @property, {
172
165
  timestamp: @time.iso8601(3),
173
166
  type: 'update',
174
- subject: "Property/#{@property.id}",
167
+ subject_type: "Property",
168
+ subject_id: @property.id,
175
169
  diff: {
176
170
  region_ids: [[@region.id], []]
177
171
  }
178
- }.as_json
172
+ }
173
+ end
174
+ end
175
+
176
+ test '::update with replacing has_and_belongs_to_many association' do
177
+ @property1 = create(:property)
178
+ @property2 = create(:property)
179
+ @region = create(:region, properties: [@property1])
180
+ WebMock::RequestRegistry.instance.reset!
181
+
182
+ travel_to(@time) { @region.update(properties: [@property2]) }
183
+
184
+ assert_posted("/events") do
185
+ assert_action_for @region, {
186
+ diff: {
187
+ property_ids: [[@property1.id], [@property2.id]]
188
+ },
189
+ subject_type: "Region",
190
+ subject_id: @region.id,
191
+ timestamp: @time.iso8601(3),
192
+ type: 'update'
193
+ }
194
+
195
+ assert_action_for @property1, {
196
+ timestamp: @time.iso8601(3),
197
+ type: 'update',
198
+ subject_type: "Property",
199
+ subject_id: @property1.id,
200
+ diff: {
201
+ region_ids: [[@region.id], []]
202
+ }
203
+ }
204
+
205
+ assert_action_for @property2, {
206
+ timestamp: @time.iso8601(3),
207
+ type: 'update',
208
+ subject_type: "Property",
209
+ subject_id: @property2.id,
210
+ diff: {
211
+ region_ids: [[], [@region.id]]
212
+ }
213
+ }
179
214
  end
180
215
  end
181
216
 
@@ -186,25 +221,24 @@ class HasAndBelongsToManyAssociationTest < ActiveSupport::TestCase
186
221
 
187
222
  travel_to(@time) { @region.destroy }
188
223
 
189
- assert_posted("/events") do |req|
190
- req_data = JSON.parse(req.body)
191
- assert_equal 2, req_data['actions'].size
192
-
193
- assert_equal req_data['actions'][0], {
224
+ assert_posted("/events") do
225
+ assert_action_for @region, {
194
226
  diff: {
195
227
  id: [@region.id, nil],
196
228
  name: [@region.name, nil],
197
229
  property_ids: [[@property.id], []]
198
230
  },
199
- subject: "Region/#{@region.id}",
231
+ subject_type: "Region",
232
+ subject_id: @region.id,
200
233
  timestamp: @time.iso8601(3),
201
234
  type: 'destroy'
202
235
  }.as_json
203
236
 
204
- assert_equal req_data['actions'][1], {
237
+ assert_action_for @property, {
205
238
  timestamp: @time.iso8601(3),
206
239
  type: 'update',
207
- subject: "Property/#{@property.id}",
240
+ subject_type: "Property",
241
+ subject_id: @property.id,
208
242
  diff: {
209
243
  region_ids: [[@region.id], []]
210
244
  }
@@ -212,7 +246,6 @@ class HasAndBelongsToManyAssociationTest < ActiveSupport::TestCase
212
246
  end
213
247
  end
214
248
 
215
-
216
249
  test 'has_and_belongs_to_many <<'
217
250
  test 'has_and_belongs_to_many.delete'
218
251
  test 'has_and_belongs_to_many.destroy'