sunstone 0.1.0 → 1.0.0
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.
- checksums.yaml +4 -4
- data/lib/active_record/connection_adapters/sunstone/column.rb +19 -0
- data/lib/active_record/connection_adapters/sunstone/database_statements.rb +40 -0
- data/lib/active_record/connection_adapters/sunstone/schema_statements.rb +95 -0
- data/lib/active_record/connection_adapters/sunstone/type/date_time.rb +22 -0
- data/lib/active_record/connection_adapters/sunstone_adapter.rb +177 -0
- data/lib/arel/collectors/sunstone.rb +75 -0
- data/lib/arel/visitors/sunstone.rb +769 -0
- data/lib/ext/active_record/associations/builder/has_and_belongs_to_many.rb +48 -0
- data/lib/ext/active_record/relation.rb +26 -0
- data/lib/ext/active_record/statement_cache.rb +24 -0
- data/lib/sunstone.rb +37 -347
- data/lib/sunstone/connection.rb +337 -0
- data/sunstone.gemspec +3 -2
- data/test/sunstone/connection_test.rb +319 -0
- data/test/sunstone/parser_test.rb +21 -21
- metadata +30 -36
- data/lib/sunstone/model.rb +0 -23
- data/lib/sunstone/model/attributes.rb +0 -99
- data/lib/sunstone/model/persistence.rb +0 -168
- data/lib/sunstone/schema.rb +0 -38
- data/lib/sunstone/type/boolean.rb +0 -19
- data/lib/sunstone/type/date_time.rb +0 -20
- data/lib/sunstone/type/decimal.rb +0 -19
- data/lib/sunstone/type/integer.rb +0 -17
- data/lib/sunstone/type/mutable.rb +0 -16
- data/lib/sunstone/type/string.rb +0 -18
- data/lib/sunstone/type/value.rb +0 -97
- data/test/sunstone/model/associations_test.rb +0 -55
- data/test/sunstone/model/attributes_test.rb +0 -60
- data/test/sunstone/model/persistence_test.rb +0 -173
- data/test/sunstone/model_test.rb +0 -11
- data/test/sunstone/schema_test.rb +0 -25
- data/test/sunstone/type/boolean_test.rb +0 -24
- data/test/sunstone/type/date_time_test.rb +0 -31
- data/test/sunstone/type/decimal_test.rb +0 -27
- data/test/sunstone/type/integer_test.rb +0 -29
- data/test/sunstone/type/string_test.rb +0 -54
- data/test/sunstone/type/value_test.rb +0 -27
@@ -0,0 +1,48 @@
|
|
1
|
+
module ActiveRecord::Associations::Builder
|
2
|
+
class HasAndBelongsToMany # :nodoc:
|
3
|
+
|
4
|
+
def through_model
|
5
|
+
habtm = JoinTableResolver.build lhs_model, association_name, options
|
6
|
+
|
7
|
+
join_model = Class.new(lhs_model.base_class.superclass) {
|
8
|
+
class << self;
|
9
|
+
attr_accessor :class_resolver
|
10
|
+
attr_accessor :name
|
11
|
+
attr_accessor :table_name_resolver
|
12
|
+
attr_accessor :left_reflection
|
13
|
+
attr_accessor :right_reflection
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.table_name
|
17
|
+
table_name_resolver.join_table
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.compute_type(class_name)
|
21
|
+
class_resolver.compute_type class_name
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.add_left_association(name, options)
|
25
|
+
belongs_to name, options
|
26
|
+
self.left_reflection = _reflect_on_association(name)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.add_right_association(name, options)
|
30
|
+
rhs_name = name.to_s.singularize.to_sym
|
31
|
+
belongs_to rhs_name, options
|
32
|
+
self.right_reflection = _reflect_on_association(rhs_name)
|
33
|
+
end
|
34
|
+
|
35
|
+
}
|
36
|
+
|
37
|
+
join_model.name = "HABTM_#{association_name.to_s.camelize}"
|
38
|
+
join_model.table_name_resolver = habtm
|
39
|
+
join_model.class_resolver = lhs_model
|
40
|
+
join_model.primary_key = nil
|
41
|
+
|
42
|
+
join_model.add_left_association :left_side, class: lhs_model
|
43
|
+
join_model.add_right_association association_name, belongs_to_options(options)
|
44
|
+
join_model
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
class Relation
|
3
|
+
def to_sql
|
4
|
+
@to_sql ||= begin
|
5
|
+
relation = self
|
6
|
+
connection = klass.connection
|
7
|
+
visitor = connection.visitor
|
8
|
+
collector = connection.collector
|
9
|
+
|
10
|
+
if eager_loading?
|
11
|
+
find_with_associations { |rel| relation = rel }
|
12
|
+
end
|
13
|
+
|
14
|
+
arel = relation.arel
|
15
|
+
binds = (arel.bind_values + relation.bind_values).dup
|
16
|
+
binds.map! { |bv| connection.quote(*bv.reverse) }
|
17
|
+
collect = visitor.accept(arel.ast, collector)
|
18
|
+
if collector.is_a?(Arel::Collectors::Sunstone)
|
19
|
+
collect.compile(binds)
|
20
|
+
else
|
21
|
+
collect.substitute_binds(binds).join
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
class StatementCache
|
3
|
+
class PartialQuery
|
4
|
+
|
5
|
+
def initialize collector
|
6
|
+
@collector = collector
|
7
|
+
@indexes = collector.value.find_all { |thing|
|
8
|
+
Arel::Nodes::BindParam === thing
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
def sql_for(binds, connection)
|
13
|
+
binds = binds.dup
|
14
|
+
@collector.compile(binds)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.partial_query(visitor, ast, collector)
|
19
|
+
collected = visitor.accept(ast, collector)
|
20
|
+
PartialQuery.new collected
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
data/lib/sunstone.rb
CHANGED
@@ -1,361 +1,51 @@
|
|
1
|
-
require 'set'
|
2
|
-
require 'uri'
|
3
|
-
require 'net/https'
|
4
|
-
|
5
1
|
require 'wankel'
|
6
2
|
require 'cookie_store'
|
7
|
-
require 'connection_pool'
|
8
3
|
|
9
4
|
require 'active_support'
|
10
5
|
require 'active_support/core_ext'
|
11
6
|
|
12
7
|
require 'active_model'
|
13
8
|
|
9
|
+
require 'active_record'
|
10
|
+
|
11
|
+
require 'sunstone/connection'
|
14
12
|
require 'sunstone/exception'
|
15
|
-
require '
|
16
|
-
require '
|
17
|
-
require '
|
13
|
+
require 'ext/active_record/statement_cache'
|
14
|
+
require 'ext/active_record/relation'
|
15
|
+
require 'ext/active_record/associations/builder/has_and_belongs_to_many'
|
16
|
+
|
17
|
+
# require 'sunstone/parser'
|
18
18
|
|
19
|
-
# _Sunstone_ is a low-level API. It provides basic HTTP #get, #post, #put, and
|
20
|
-
# #delete calls to the Sunstone Server. It can also provides basic error
|
21
|
-
# checking of responses.
|
22
19
|
module Sunstone
|
23
20
|
VERSION = 0.1
|
24
|
-
|
25
|
-
extend self
|
26
|
-
|
27
|
-
attr_reader :site
|
28
|
-
|
29
|
-
# Set the User-Agent of the client. Will be joined with other User-Agent info
|
30
|
-
attr_writer :user_agent
|
31
|
-
attr_accessor :api_key, :host, :port, :use_ssl
|
32
|
-
|
33
|
-
# Sets the Protocol, API Token, and Host and Port of the API Server
|
34
|
-
#
|
35
|
-
# #!ruby
|
36
|
-
# Sunstone.site = "https://API_KEY@host.com"
|
37
|
-
def site=(url)
|
38
|
-
@site = url
|
39
|
-
uri = URI.parse(url)
|
40
|
-
@api_key = uri.user ? CGI.unescape(uri.user) : nil
|
41
|
-
@host, @port = uri.host, uri.port
|
42
|
-
@use_ssl = (uri.scheme == 'https')
|
43
|
-
end
|
44
|
-
|
45
|
-
# Returns the User-Agent of the client. Defaults to:
|
46
|
-
# "sunstone-ruby/SUNSTONE_VERSION RUBY_VERSION-pPATCH_LEVEL PLATFORM"
|
47
|
-
def user_agent
|
48
|
-
[
|
49
|
-
@user_agent,
|
50
|
-
"Sunstone/#{Sunstone::VERSION}",
|
51
|
-
"Ruby/#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}",
|
52
|
-
RUBY_PLATFORM
|
53
|
-
].compact.join(' ')
|
54
|
-
end
|
55
|
-
|
56
|
-
# Set a cookie jar to use during request sent during the
|
57
|
-
def with_cookie_store(store, &block)
|
58
|
-
Thread.current[:sunstone_cookie_store] = store
|
59
|
-
yield
|
60
|
-
ensure
|
61
|
-
Thread.current[:sunstone_cookie_store] = nil
|
62
|
-
end
|
63
|
-
|
64
|
-
# Sends a Net::HTTPRequest to the server. The headers returned from
|
65
|
-
# Sunestone#headers are automatically added to the request. The appropriate
|
66
|
-
# error is raised if the response is not in the 200..299 range.
|
67
|
-
#
|
68
|
-
# Paramaters::
|
69
|
-
#
|
70
|
-
# * +request+ - A Net::HTTPRequest to send to the server
|
71
|
-
# * +body+ - Optional, a String, IO Object, or a Ruby object which is
|
72
|
-
# converted into JSON and sent as the body
|
73
|
-
# * +block+ - An optional block to call with the +Net::HTTPResponse+ object.
|
74
|
-
#
|
75
|
-
# Return Value::
|
76
|
-
#
|
77
|
-
# Returns the return value of the <tt>&block</tt> if given, otherwise the
|
78
|
-
# response object (a Net::HTTPResponse)
|
79
|
-
#
|
80
|
-
# Examples:
|
81
|
-
#
|
82
|
-
# #!ruby
|
83
|
-
# Sunstone.send_request(#<Net::HTTP::Get>) # => #<Net::HTTP::Response>
|
84
|
-
#
|
85
|
-
# Sunstone.send_request(#<Net::HTTP::Get @path="/404">) # => raises Sunstone::Exception::NotFound
|
86
|
-
#
|
87
|
-
# # this will still raise an exception if the response_code is not valid
|
88
|
-
# # and the block will not be called
|
89
|
-
# Sunstone.send_request(#<Net::HTTP::Get>) do |response|
|
90
|
-
# # ...
|
91
|
-
# end
|
92
|
-
#
|
93
|
-
# # The following example shows how to stream a response:
|
94
|
-
# Sunstone.send_request(#<Net::HTTP::Get>) do |response|
|
95
|
-
# response.read_body do |chunk|
|
96
|
-
# io.write(chunk)
|
97
|
-
# end
|
98
|
-
# end
|
99
|
-
def send_request(request, body=nil, &block)
|
100
|
-
request_headers.each { |k, v| request[k] = v }
|
101
|
-
|
102
|
-
if body.is_a?(IO)
|
103
|
-
request['Transfer-Encoding'] = 'chunked'
|
104
|
-
request.body_stream = body
|
105
|
-
elsif body.is_a?(String)
|
106
|
-
request.body = body
|
107
|
-
elsif body
|
108
|
-
request.body = Wankel.encode(body)
|
109
|
-
end
|
110
|
-
|
111
|
-
return_value = nil
|
112
|
-
with_connection do |connection|
|
113
|
-
connection.request(request) do |response|
|
114
|
-
|
115
|
-
if response['X-42Floors-API-Version-Deprecated']
|
116
|
-
logger.warn("DEPRECATION WARNING: API v#{API_VERSION} is being phased out")
|
117
|
-
end
|
118
|
-
|
119
|
-
validate_response_code(response)
|
120
|
-
|
121
|
-
# Get the cookies
|
122
|
-
response.each_header do |key, value|
|
123
|
-
if key.downcase == 'set-cookie' && Thread.current[:sunstone_cookie_store]
|
124
|
-
Thread.current[:sunstone_cookie_store].set_cookie("#{site}#{request.path}", value)
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
if block_given?
|
129
|
-
return_value =yield(response)
|
130
|
-
else
|
131
|
-
return_value =response
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
return_value
|
137
|
-
end
|
138
|
-
|
139
|
-
# Send a GET request to +path+ on the Sunstone Server via +Sunstone#send_request+.
|
140
|
-
# See +Sunstone#send_request+ for more details on how the response is handled.
|
141
|
-
#
|
142
|
-
# Paramaters::
|
143
|
-
#
|
144
|
-
# * +path+ - The +path+ on the server to GET to.
|
145
|
-
# * +params+ - Either a String, Hash, or Ruby Object that responds to
|
146
|
-
# #to_param. Appended on the URL as query params
|
147
|
-
# * +block+ - An optional block to call with the +Net::HTTPResponse+ object.
|
148
|
-
#
|
149
|
-
# Return Value::
|
150
|
-
#
|
151
|
-
# See +Sunstone#send_request+
|
152
|
-
#
|
153
|
-
# Examples:
|
154
|
-
#
|
155
|
-
# #!ruby
|
156
|
-
# Sunstone.get('/example') # => #<Net::HTTP::Response>
|
157
|
-
#
|
158
|
-
# Sunstone.get('/example', 'query=stuff') # => #<Net::HTTP::Response>
|
159
|
-
#
|
160
|
-
# Sunstone.get('/example', {:query => 'stuff'}) # => #<Net::HTTP::Response>
|
161
|
-
#
|
162
|
-
# Sunstone.get('/404') # => raises Sunstone::Exception::NotFound
|
163
|
-
#
|
164
|
-
# Sunstone.get('/act') do |response|
|
165
|
-
# # ...
|
166
|
-
# end
|
167
|
-
def get(path, params='', &block)
|
168
|
-
params ||= ''
|
169
|
-
request = Net::HTTP::Get.new(path + '?' + params.to_param)
|
170
|
-
|
171
|
-
send_request(request, nil, &block)
|
172
|
-
end
|
173
21
|
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
def post(path, body=nil, &block)
|
204
|
-
request = Net::HTTP::Post.new(path)
|
205
|
-
|
206
|
-
send_request(request, body, &block)
|
207
|
-
end
|
208
|
-
|
209
|
-
# Send a PUT request to +path+ on the Sunstone Server via +Sunstone#send_request+.
|
210
|
-
# See +Sunstone#send_request+ for more details on how the response is handled.
|
211
|
-
#
|
212
|
-
# Paramaters::
|
213
|
-
#
|
214
|
-
# * +path+ - The +path+ on the server to POST to.
|
215
|
-
# * +body+ - Optional, See +Sunstone#send_request+.
|
216
|
-
# * +block+ - Optional, See +Sunstone#send_request+
|
217
|
-
#
|
218
|
-
# Return Value::
|
219
|
-
#
|
220
|
-
# See +Sunstone#send_request+
|
221
|
-
#
|
222
|
-
# Examples:
|
223
|
-
#
|
224
|
-
# #!ruby
|
225
|
-
# Sunstone.put('/example') # => #<Net::HTTP::Response>
|
226
|
-
#
|
227
|
-
# Sunstone.put('/example', 'body') # => #<Net::HTTP::Response>
|
228
|
-
#
|
229
|
-
# Sunstone.put('/example', #<IO Object>) # => #<Net::HTTP::Response>
|
230
|
-
#
|
231
|
-
# Sunstone.put('/example', {:example => 'data'}) # => #<Net::HTTP::Response>
|
232
|
-
#
|
233
|
-
# Sunstone.put('/404') # => raises Sunstone::Exception::NotFound
|
234
|
-
#
|
235
|
-
# Sunstone.put('/act') do |response|
|
236
|
-
# # ...
|
237
|
-
# end
|
238
|
-
def put(path, body=nil, *valid_response_codes, &block)
|
239
|
-
request = Net::HTTP::Put.new(path)
|
240
|
-
|
241
|
-
send_request(request, body, &block)
|
242
|
-
end
|
243
|
-
|
244
|
-
# Send a DELETE request to +path+ on the Sunstone Server via +Sunstone#send_request+.
|
245
|
-
# See +Sunstone#send_request+ for more details on how the response is handled
|
246
|
-
#
|
247
|
-
# Paramaters::
|
248
|
-
#
|
249
|
-
# * +path+ - The +path+ on the server to POST to.
|
250
|
-
# * +block+ - Optional, See +Sunstone#send_request+
|
251
|
-
#
|
252
|
-
# Return Value::
|
253
|
-
#
|
254
|
-
# See +Sunstone#send_request+
|
255
|
-
#
|
256
|
-
# Examples:
|
257
|
-
#
|
258
|
-
# #!ruby
|
259
|
-
# Sunstone.delete('/example') # => #<Net::HTTP::Response>
|
260
|
-
#
|
261
|
-
# Sunstone.delete('/404') # => raises Sunstone::Exception::NotFound
|
262
|
-
#
|
263
|
-
# Sunstone.delete('/act') do |response|
|
264
|
-
# # ...
|
265
|
-
# end
|
266
|
-
def delete(path, &block)
|
267
|
-
request = Net::HTTP::Delete.new(path)
|
268
|
-
|
269
|
-
send_request(request, nil, &block)
|
270
|
-
end
|
271
|
-
|
272
|
-
# Get a connection from the connection pool and perform the block with
|
273
|
-
# the connection
|
274
|
-
def with_connection(&block)
|
275
|
-
connection_pool.with({}, &block)
|
276
|
-
end
|
277
|
-
|
278
|
-
# Ping the Sunstone. If everything is configured and operating correctly
|
279
|
-
# <tt>"pong"</tt> will be returned. Otherwise and Sunstone::Exception should be
|
280
|
-
# thrown.
|
281
|
-
#
|
282
|
-
# #!ruby
|
283
|
-
# Sunstone.ping # => "pong"
|
284
|
-
#
|
285
|
-
# Sunstone.ping # raises Sunstone::Exception::ServiceUnavailable if a 503 is returned
|
286
|
-
def ping
|
287
|
-
get('/ping').body
|
288
|
-
end
|
289
|
-
|
290
|
-
def config
|
291
|
-
@config ||= Wankel.parse(get('/config').body, :symbolize_keys => true)
|
292
|
-
end
|
293
|
-
|
294
|
-
private
|
295
|
-
|
296
|
-
def request_headers
|
297
|
-
headers = {
|
298
|
-
'Content-Type' => 'application/json',
|
299
|
-
'User-Agent' => user_agent
|
300
|
-
}
|
301
|
-
|
302
|
-
headers['Api-Key'] = api_key if api_key
|
303
|
-
|
304
|
-
headers
|
305
|
-
end
|
306
|
-
|
307
|
-
# Raise an Sunstone::Exception based on the response_code, unless the response_code
|
308
|
-
# is include in the valid_response_codes Array
|
309
|
-
#
|
310
|
-
# Paramaters::
|
311
|
-
#
|
312
|
-
# * +response+ - The Net::HTTP::Response object
|
313
|
-
#
|
314
|
-
# Return Value::
|
315
|
-
#
|
316
|
-
# If an exception is not raised the +response+ is returned
|
317
|
-
#
|
318
|
-
# Examples:
|
319
|
-
#
|
320
|
-
# #!ruby
|
321
|
-
# Sunstone.validate_response_code(<Net::HTTP::Response @code=200>) # => <Net::HTTP::Response @code=200>
|
322
|
-
#
|
323
|
-
# Sunstone.validate_response_code(<Net::HTTP::Response @code=404>) # => raises Sunstone::Exception::NotFound
|
324
|
-
#
|
325
|
-
# Sunstone.validate_response_code(<Net::HTTP::Response @code=500>) # => raises Sunstone::Exception
|
326
|
-
def validate_response_code(response)
|
327
|
-
code = response.code.to_i
|
328
|
-
|
329
|
-
if !(200..299).include?(code)
|
330
|
-
case code
|
331
|
-
when 400
|
332
|
-
raise Sunstone::Exception::BadRequest, response
|
333
|
-
when 401
|
334
|
-
raise Sunstone::Exception::Unauthorized, response
|
335
|
-
when 404
|
336
|
-
raise Sunstone::Exception::NotFound, response
|
337
|
-
when 410
|
338
|
-
raise Sunstone::Exception::Gone, response
|
339
|
-
when 422
|
340
|
-
raise Sunstone::Exception::ApiVersionUnsupported, response
|
341
|
-
when 503
|
342
|
-
raise Sunstone::Exception::ServiceUnavailable, response
|
343
|
-
when 301
|
344
|
-
raise Sunstone::Exception::MovedPermanently, response
|
345
|
-
when 300..599
|
346
|
-
raise Sunstone::Exception, response
|
347
|
-
else
|
348
|
-
raise Sunstone::Exception, response
|
349
|
-
end
|
350
|
-
end
|
351
|
-
end
|
352
|
-
|
353
|
-
def connection_pool
|
354
|
-
@connection_pool ||= ConnectionPool.new(size: 5, timeout: 5) do
|
355
|
-
connection = Net::HTTP.new(@host, @port)
|
356
|
-
connection.use_ssl = @ssl
|
357
|
-
connection
|
358
|
-
end
|
359
|
-
end
|
360
|
-
|
22
|
+
# TODO:
|
23
|
+
# # Set a cookie jar to use during request sent during the
|
24
|
+
# def with_cookie_store(store, &block)
|
25
|
+
# Thread.current[:sunstone_cookie_store] = store
|
26
|
+
# yield
|
27
|
+
# ensure
|
28
|
+
# Thread.current[:sunstone_cookie_store] = nil
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
#
|
32
|
+
# # Get a connection from the connection pool and perform the block with
|
33
|
+
# # the connection
|
34
|
+
# def with_connection(&block)
|
35
|
+
# connection_pool.with({}, &block)
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# private
|
39
|
+
#
|
40
|
+
# def request_headers
|
41
|
+
# headers = {
|
42
|
+
# 'Content-Type' => 'application/json',
|
43
|
+
# 'User-Agent' => user_agent
|
44
|
+
# }
|
45
|
+
#
|
46
|
+
# headers['Api-Key'] = api_key if api_key
|
47
|
+
#
|
48
|
+
# headers
|
49
|
+
# end
|
50
|
+
#
|
361
51
|
end
|