activehistory 0.1

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.
@@ -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