activehistory 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,121 @@
1
+ require 'uri'
2
+ require 'net/https'
3
+
4
+ class ActiveHistory::Connection
5
+
6
+ attr_reader :api_key, :host, :port, :ssl
7
+
8
+ def initialize(config)
9
+ if config[:url]
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')
15
+ end
16
+
17
+ [:api_key, :host, :port, :ssl, :user_agent].each do |key|
18
+ self.instance_variable_set(:"@#{key}", config[key])
19
+ end
20
+
21
+ @connection = Net::HTTP.new(host, port)
22
+ @connection.use_ssl = ssl
23
+
24
+ true
25
+ end
26
+
27
+ def user_agent
28
+ [
29
+ @user_agent,
30
+ "Sunstone/#{ActiveHistory::VERSION}",
31
+ "Ruby/#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}",
32
+ RUBY_PLATFORM
33
+ ].compact.join(' ')
34
+ end
35
+
36
+ def post(path, body=nil, &block)
37
+ request = Net::HTTP::Post.new(path)
38
+
39
+ send_request(request, body, &block)
40
+ end
41
+
42
+ def url
43
+ "http#{ssl ? 's' : ''}://#{host}#{port != 80 ? (port == 443 && ssl ? '' : ":#{port}") : ''}"
44
+ end
45
+
46
+ def send_request(request, body=nil, &block)
47
+ request['Accept'] = 'application/json'
48
+ request['User-Agent'] = user_agent
49
+ request['Api-Key'] = api_key
50
+ request['Content-Type'] = 'application/json'
51
+
52
+ if body.is_a?(IO)
53
+ request['Transfer-Encoding'] = 'chunked'
54
+ request.body_stream = body
55
+ elsif body.is_a?(String)
56
+ request.body = body
57
+ elsif body
58
+ request.body = JSON.generate(body)
59
+ end
60
+
61
+ return_value = nil
62
+ retry_count = 0
63
+ 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
+
69
+ 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
+
78
+ if block_given?
79
+ return_value =yield(response)
80
+ else
81
+ return_value =response
82
+ end
83
+ end
84
+ rescue ActiveRecord::ConnectionNotEstablished
85
+ retry_count += 1
86
+ retry_count == 1 ? retry : raise
87
+ end
88
+
89
+ return_value
90
+ end
91
+
92
+ def validate_response_code(response)
93
+ code = response.code.to_i
94
+
95
+ if !(200..299).include?(code)
96
+ case code
97
+ when 400
98
+ raise ActiveHistory::Exception::BadRequest, response.body
99
+ when 401
100
+ raise ActiveHistory::Exception::Unauthorized, response
101
+ when 404
102
+ raise ActiveHistory::Exception::NotFound, response
103
+ when 410
104
+ raise ActiveHistory::Exception::Gone, response
105
+ when 422
106
+ raise ActiveHistory::Exception::ApiVersionUnsupported, response
107
+ when 503
108
+ raise ActiveHistory::Exception::ServiceUnavailable, response
109
+ when 301
110
+ raise ActiveHistory::Exception::MovedPermanently, response
111
+ when 502
112
+ raise ActiveHistory::Exception::BadGateway, response
113
+ when 300..599
114
+ raise ActiveHistory::Exception, response
115
+ else
116
+ raise ActiveHistory::Exception, response
117
+ end
118
+ end
119
+ end
120
+
121
+ end
@@ -0,0 +1,50 @@
1
+ class ActiveHistory::Event
2
+
3
+ attr_accessor :actions, :regards, :timestamp
4
+
5
+ def initialize(attrs={})
6
+ @attrs = attrs
7
+ @attrs[:timestamp] ||= Time.now
8
+ @actions = []
9
+ @regards = []
10
+ end
11
+
12
+ def action!(action)
13
+ action = ActiveHistory::Action.new(action)
14
+ @actions << action
15
+ action
16
+ end
17
+
18
+ def action_for(id)
19
+ @actions.find { |a| a.subject == id }
20
+ end
21
+
22
+ def regard!(regard)
23
+ @regards << regard
24
+ end
25
+
26
+ def save!
27
+ return if @actions.empty?
28
+
29
+ ActiveHistory.connection.post('/events', self.as_json)
30
+ end
31
+
32
+ def as_json
33
+ {
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)
43
+ }
44
+ end
45
+
46
+ def encapsulate
47
+ ensure
48
+ end
49
+
50
+ end
@@ -0,0 +1,32 @@
1
+ module ActiveHistory
2
+
3
+ class Exception < ::Exception
4
+
5
+ class BadGateway < ActiveHistory::Exception
6
+ end
7
+
8
+ class BadRequest < ActiveHistory::Exception
9
+ end
10
+
11
+ class Unauthorized < ActiveHistory::Exception
12
+ end
13
+
14
+ class NotFound < ActiveHistory::Exception
15
+ end
16
+
17
+ class Gone < ActiveHistory::Exception
18
+ end
19
+
20
+ class MovedPermanently < ActiveHistory::Exception
21
+ end
22
+
23
+ class ApiVersionUnsupported < ActiveHistory::Exception
24
+ end
25
+
26
+ class ServiceUnavailable < ActiveHistory::Exception
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+
@@ -0,0 +1,11 @@
1
+ class ActiveHistory::Regard
2
+
3
+ def initialize(id)
4
+ @subject = id
5
+ end
6
+
7
+ def as_json
8
+ { subject: @subject }
9
+ end
10
+
11
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveHistory
2
+ VERSION = '0.1'
3
+ end
@@ -0,0 +1,82 @@
1
+ require 'test_helper'
2
+
3
+ class BelongsToAssociationTest < ActiveSupport::TestCase
4
+
5
+ def setup
6
+ super
7
+ @time = (Time.now.utc + 2).change(usec: 0)
8
+ end
9
+
10
+ test 'TargetModel.create' do
11
+ @model = create(:account)
12
+ WebMock::RequestRegistry.instance.reset!
13
+
14
+ @target = travel_to(@time) { create(:photo, account: @model) }
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']
23
+ end
24
+ end
25
+
26
+ test 'TargetModel.update removes from association' do
27
+ @model = create(:account)
28
+ @target = create(:photo, account: @model)
29
+ WebMock::RequestRegistry.instance.reset!
30
+
31
+ 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
+
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']
40
+ end
41
+ end
42
+
43
+ test 'TargetModel.update changes association' do
44
+ @model1 = create(:account)
45
+ @model2 = create(:account)
46
+ @target = create(:photo, account: @model1)
47
+ WebMock::RequestRegistry.instance.reset!
48
+
49
+ 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
+
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']
58
+
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
+ end
63
+ end
64
+
65
+ test 'TargetModel.destroy removes from association' do
66
+ @model = create(:account)
67
+ @target = create(:photo, account: @model)
68
+ WebMock::RequestRegistry.instance.reset!
69
+
70
+ 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
+
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
+ end
80
+ end
81
+
82
+ end
@@ -0,0 +1,224 @@
1
+ require 'test_helper'
2
+
3
+ class HasAndBelongsToManyAssociationTest < ActiveSupport::TestCase
4
+
5
+ def setup
6
+ super
7
+ @time = (Time.now.utc + 2).change(usec: 0)
8
+ end
9
+
10
+ test '::create with existing has_and_belongs_to_many association' do
11
+ @property = create(:property)
12
+ WebMock::RequestRegistry.instance.reset!
13
+
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], {
20
+ diff: {
21
+ id: [nil, @region.id],
22
+ name: [nil, @region.name],
23
+ property_ids: [[], [@property.id]]
24
+ },
25
+ subject: "Region/#{@region.id}",
26
+ timestamp: @time.iso8601(3),
27
+ type: 'create'
28
+ }.as_json
29
+
30
+ assert_equal req_data['actions'][1], {
31
+ timestamp: @time.iso8601(3),
32
+ type: 'update',
33
+ subject: "Property/#{@property.id}",
34
+ diff: {
35
+ region_ids: [[], [@region.id]]
36
+ }
37
+ }.as_json
38
+ end
39
+ end
40
+
41
+ test '::create with new has_and_belongs_to_many association' do
42
+ WebMock::RequestRegistry.instance.reset!
43
+
44
+ @region = travel_to(@time) { create(:region, properties: [build(:property)]) }
45
+ @property = @region.properties.first
46
+
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], {
52
+ diff: {
53
+ id: [nil, @region.id],
54
+ name: [nil, @region.name],
55
+ property_ids: [[], [@property.id]]
56
+ },
57
+ subject: "Region/#{@region.id}",
58
+ timestamp: @time.iso8601(3),
59
+ type: 'create'
60
+ }.as_json
61
+
62
+ assert_equal req_data['actions'][1], {
63
+ timestamp: @time.iso8601(3),
64
+ type: 'create',
65
+ subject: "Property/#{@property.id}",
66
+ diff: {
67
+ id: [nil, @property.id],
68
+ name: [nil, @property.name],
69
+ description: [nil, @property.description],
70
+ constructed: [nil, @property.constructed],
71
+ size: [nil, @property.size],
72
+ created_at: [nil, @property.created_at],
73
+ aliases: [nil, []],
74
+ active: [nil, @property.active],
75
+ region_ids: [[], [@region.id]]
76
+ }
77
+ }.as_json
78
+ end
79
+ end
80
+
81
+ test '::update with adding existing has_and_belongs_to_many association' do
82
+ @property = create(:property)
83
+ @region = create(:region)
84
+ WebMock::RequestRegistry.instance.reset!
85
+
86
+ travel_to(@time) { @region.update(properties: [@property]) }
87
+
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], {
93
+ diff: {
94
+ property_ids: [[], [@property.id]]
95
+ },
96
+ subject: "Region/#{@region.id}",
97
+ timestamp: @time.iso8601(3),
98
+ type: 'update'
99
+ }.as_json
100
+
101
+ assert_equal req_data['actions'][1], {
102
+ timestamp: @time.iso8601(3),
103
+ type: 'update',
104
+ subject: "Property/#{@property.id}",
105
+ diff: {
106
+ region_ids: [[], [@region.id]]
107
+ }
108
+ }.as_json
109
+ end
110
+ end
111
+
112
+ test '::update with adding new has_and_belongs_to_many association' do
113
+ @region = create(:region)
114
+ WebMock::RequestRegistry.instance.reset!
115
+
116
+ travel_to(@time) { @region.update(properties: [build(:property)]) }
117
+ @property = @region.properties.first
118
+
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], {
124
+ timestamp: @time.iso8601(3),
125
+ type: 'create',
126
+ subject: "Property/#{@property.id}",
127
+ diff: {
128
+ id: [nil, @property.id],
129
+ name: [nil, @property.name],
130
+ description: [nil, @property.description],
131
+ constructed: [nil, @property.constructed],
132
+ size: [nil, @property.size],
133
+ created_at: [nil, @property.created_at],
134
+ aliases: [nil, []],
135
+ active: [nil, @property.active],
136
+ region_ids: [[], [@region.id]]
137
+ }
138
+ }.as_json
139
+
140
+ assert_equal req_data['actions'][1], {
141
+ diff: {
142
+ property_ids: [[], [@property.id]]
143
+ },
144
+ subject: "Region/#{@region.id}",
145
+ timestamp: @time.iso8601(3),
146
+ type: 'update'
147
+ }.as_json
148
+ end
149
+ end
150
+
151
+ test '::update with removing has_and_belongs_to_many association' do
152
+ @property = create(:property)
153
+ @region = create(:region, properties: [@property])
154
+ WebMock::RequestRegistry.instance.reset!
155
+
156
+ travel_to(@time) { @region.update(properties: []) }
157
+
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], {
163
+ diff: {
164
+ property_ids: [[@property.id], []]
165
+ },
166
+ subject: "Region/#{@region.id}",
167
+ timestamp: @time.iso8601(3),
168
+ type: 'update'
169
+ }.as_json
170
+
171
+ assert_equal req_data['actions'][1], {
172
+ timestamp: @time.iso8601(3),
173
+ type: 'update',
174
+ subject: "Property/#{@property.id}",
175
+ diff: {
176
+ region_ids: [[@region.id], []]
177
+ }
178
+ }.as_json
179
+ end
180
+ end
181
+
182
+ test '::destroying updates has_and_belongs_to_many associations' do
183
+ @property = create(:property)
184
+ @region = create(:region, properties: [@property])
185
+ WebMock::RequestRegistry.instance.reset!
186
+
187
+ travel_to(@time) { @region.destroy }
188
+
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], {
194
+ diff: {
195
+ id: [@region.id, nil],
196
+ name: [@region.name, nil],
197
+ property_ids: [[@property.id], []]
198
+ },
199
+ subject: "Region/#{@region.id}",
200
+ timestamp: @time.iso8601(3),
201
+ type: 'destroy'
202
+ }.as_json
203
+
204
+ assert_equal req_data['actions'][1], {
205
+ timestamp: @time.iso8601(3),
206
+ type: 'update',
207
+ subject: "Property/#{@property.id}",
208
+ diff: {
209
+ region_ids: [[@region.id], []]
210
+ }
211
+ }.as_json
212
+ end
213
+ end
214
+
215
+
216
+ test 'has_and_belongs_to_many <<'
217
+ test 'has_and_belongs_to_many.delete'
218
+ test 'has_and_belongs_to_many.destroy'
219
+ test 'has_and_belongs_to_many='
220
+ test 'has_and_belongs_to_many_ids='
221
+ test 'has_and_belongs_to_many.clear'
222
+ test 'has_and_belongs_to_many.create'
223
+
224
+ end