activehistory 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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'