sunstone 5.0.0.1 → 5.0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ext/active_record/finder_methods.rb +1 -1
- data/lib/active_record/connection_adapters/sunstone/database_statements.rb +48 -15
- data/lib/active_record/connection_adapters/sunstone/schema_statements.rb +22 -4
- data/lib/active_record/connection_adapters/sunstone/type/json.rb +20 -0
- data/lib/active_record/connection_adapters/sunstone_adapter.rb +19 -48
- data/lib/arel/collectors/sunstone.rb +55 -21
- data/lib/arel/visitors/sunstone.rb +13 -15
- data/lib/sunstone/connection.rb +33 -7
- data/lib/sunstone/version.rb +1 -1
- data/sunstone.gemspec +1 -0
- data/test/active_record/persistance_test.rb +14 -6
- data/test/active_record/query_test.rb +49 -2
- data/test/models.rb +48 -24
- data/test/test_helper.rb +2 -8
- metadata +18 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 251b8b254f40eac45d625f004fe405db180eefe0
|
4
|
+
data.tar.gz: e06aa5cf5ce843ccfe6c495fd5e0e7d7ec3d5685
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1ad88a3734db407224aa3addb478b5eaabfd9b5ad1ae0e3effe18c67e783a6131c652b58cbb55508f300a630f0c3aea259904743418cc711af0f8ab356bfc546
|
7
|
+
data.tar.gz: 628039ec1b81d9f4630a85dca9decd0e455e47d45d2386721cb96765628c86f19a5372275e52e1d6bdd0ac42082da8ee3ba2e4104969731a524448e10caabdbb
|
@@ -101,7 +101,7 @@ module ActiveRecord
|
|
101
101
|
else
|
102
102
|
if parent.association_cached?(reflection.name)
|
103
103
|
model = parent.association(reflection.name).target
|
104
|
-
construct(model, attributes.select{|k,v| !
|
104
|
+
construct(model, attributes.select{|k,v| !model.class.column_names.include?(k.to_s) }, seen, model_cache)
|
105
105
|
end
|
106
106
|
end
|
107
107
|
|
@@ -13,20 +13,57 @@ module ActiveRecord
|
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
|
+
# Returns an ActiveRecord::Result instance.
|
17
|
+
def select_all(arel, name = nil, binds = [], preparable: nil)
|
18
|
+
arel, binds = binds_from_relation arel, binds
|
19
|
+
select(arel, name, binds)
|
20
|
+
end
|
21
|
+
|
16
22
|
def exec_query(arel, name = 'SAR', binds = [], prepare: false)
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
|
24
|
+
sars = []
|
25
|
+
multiple_requests = arel.is_a?(Arel::SelectManager) && arel&.limit
|
26
|
+
|
27
|
+
if multiple_requests
|
28
|
+
allowed_limit = limit_definition(arel.source.left.name)
|
29
|
+
requested_limit = binds.find { |x| x.name == 'LIMIT' }.value
|
30
|
+
multiple_requests = requested_limit > allowed_limit
|
31
|
+
end
|
32
|
+
|
33
|
+
send_request = lambda { |req_arel|
|
34
|
+
sar = to_sar(req_arel, binds)
|
35
|
+
sars.push(sar)
|
36
|
+
log_mess = sar.path.split('?', 2)
|
37
|
+
log("#{sar.method} #{log_mess[0]} #{(log_mess[1] && !log_mess[1].empty?) ? MessagePack.unpack(CGI.unescape(log_mess[1])) : '' }", name) do
|
38
|
+
response = @connection.send_request(sar)
|
39
|
+
if response.is_a?(Net::HTTPNoContent)
|
40
|
+
nil
|
41
|
+
else
|
42
|
+
JSON.parse(response.body)
|
43
|
+
end
|
26
44
|
end
|
27
45
|
}
|
28
46
|
|
29
|
-
if
|
47
|
+
result = if multiple_requests
|
48
|
+
bind = binds.find { |x| x.name == 'LIMIT' }
|
49
|
+
binds.delete(bind)
|
50
|
+
|
51
|
+
limit, offset, results = allowed_limit, 0, []
|
52
|
+
while offset < requested_limit
|
53
|
+
split_arel = arel.clone
|
54
|
+
split_arel.limit = limit
|
55
|
+
split_arel.offset = offset
|
56
|
+
request_results = send_request.call(split_arel)
|
57
|
+
results = results + request_results
|
58
|
+
break if request_results.size < limit
|
59
|
+
offset = offset + limit
|
60
|
+
end
|
61
|
+
results
|
62
|
+
else
|
63
|
+
send_request.call(arel)
|
64
|
+
end
|
65
|
+
|
66
|
+
if !multiple_requests && sars[0].instance_variable_get(:@sunstone_calculation)
|
30
67
|
# this is a count, min, max.... yea i know..
|
31
68
|
ActiveRecord::Result.new(['all'], [result], {:all => type_map.lookup('integer')})
|
32
69
|
elsif result.is_a?(Array)
|
@@ -35,7 +72,7 @@ module ActiveRecord
|
|
35
72
|
ActiveRecord::Result.new(result.keys, [result])
|
36
73
|
end
|
37
74
|
end
|
38
|
-
|
75
|
+
|
39
76
|
def last_inserted_id(result)
|
40
77
|
row = result.rows.first
|
41
78
|
row && row['id']
|
@@ -46,7 +83,3 @@ module ActiveRecord
|
|
46
83
|
end
|
47
84
|
end
|
48
85
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
@@ -19,17 +19,35 @@ module ActiveRecord
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
+
def definition(table_name)
|
23
|
+
response = @connection.get("/#{table_name}/schema")
|
24
|
+
|
25
|
+
version = Gem::Version.create(response['StandardAPI-Version'] || '5.0.0.4')
|
26
|
+
|
27
|
+
if (version >= Gem::Version.create('5.0.0.5'))
|
28
|
+
JSON.parse(response.body)
|
29
|
+
else
|
30
|
+
{ 'columns' => JSON.parse(response.body), 'limit' => nil }
|
31
|
+
end
|
32
|
+
rescue ::Sunstone::Exception::NotFound
|
33
|
+
raise ActiveRecord::StatementInvalid, "Table \"#{table_name}\" does not exist"
|
34
|
+
end
|
35
|
+
|
22
36
|
# Returns the list of a table's column names, data types, and default values.
|
23
37
|
#
|
24
38
|
# Query implementation notes:
|
25
39
|
# - format_type includes the column size constraint, e.g. varchar(50)
|
26
40
|
# - ::regclass is a function that gives the id for a table name
|
27
41
|
def column_definitions(table_name) # :nodoc:
|
28
|
-
|
29
|
-
rescue ::Sunstone::Exception::NotFound
|
30
|
-
raise ActiveRecord::StatementInvalid, "Table \"#{table_name}\" does not exist"
|
42
|
+
definition(table_name)['columns']
|
31
43
|
end
|
32
|
-
|
44
|
+
|
45
|
+
# Returns the limit definition of the table (the maximum limit that can
|
46
|
+
# be used).
|
47
|
+
def limit_definition(table_name)
|
48
|
+
definition(table_name)['limit'] || Float::INFINITY
|
49
|
+
end
|
50
|
+
|
33
51
|
def tables
|
34
52
|
JSON.parse(@connection.get('/tables').body)
|
35
53
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module ConnectionAdapters
|
3
|
+
module Sunstone
|
4
|
+
module Type
|
5
|
+
class Json < ActiveRecord::Type::Internal::AbstractJson
|
6
|
+
|
7
|
+
|
8
|
+
def deserialize(value)
|
9
|
+
value.nil? ? nil : value.dup
|
10
|
+
end
|
11
|
+
|
12
|
+
def serialize(value)
|
13
|
+
value
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -10,6 +10,7 @@ require 'active_record/connection_adapters/sunstone/column'
|
|
10
10
|
require 'active_record/connection_adapters/sunstone/type/date_time'
|
11
11
|
require 'active_record/connection_adapters/sunstone/type/array'
|
12
12
|
require 'active_record/connection_adapters/sunstone/type/uuid'
|
13
|
+
require 'active_record/connection_adapters/sunstone/type/json'
|
13
14
|
|
14
15
|
module ActiveRecord
|
15
16
|
module ConnectionHandling # :nodoc:
|
@@ -20,12 +21,8 @@ module ActiveRecord
|
|
20
21
|
# objects
|
21
22
|
def sunstone_connection(config)
|
22
23
|
conn_params = config.symbolize_keys
|
23
|
-
|
24
24
|
conn_params.delete_if { |_, v| v.nil? }
|
25
25
|
|
26
|
-
# Map ActiveRecords param names to PGs.
|
27
|
-
conn_params[:user] = conn_params.delete(:username) if conn_params[:username]
|
28
|
-
conn_params[:dbname] = conn_params.delete(:database) if conn_params[:database]
|
29
26
|
if conn_params[:url]
|
30
27
|
uri = URI.parse(conn_params.delete(:url))
|
31
28
|
conn_params[:api_key] ||= (uri.user ? CGI.unescape(uri.user) : nil)
|
@@ -34,12 +31,11 @@ module ActiveRecord
|
|
34
31
|
conn_params[:use_ssl] ||= (uri.scheme == 'https')
|
35
32
|
end
|
36
33
|
|
37
|
-
# Forward only valid config params to
|
34
|
+
# Forward only valid config params to Sunstone::Connection
|
38
35
|
conn_params.slice!(*VALID_SUNSTONE_CONN_PARAMS)
|
39
36
|
|
40
|
-
|
41
|
-
|
42
|
-
ConnectionAdapters::SunstoneAPIAdapter.new(nil, logger, conn_params, config)
|
37
|
+
client = ::Sunstone::Connection.new(conn_params)
|
38
|
+
ConnectionAdapters::SunstoneAPIAdapter.new(client, logger, conn_params, config)
|
43
39
|
end
|
44
40
|
end
|
45
41
|
|
@@ -71,44 +67,29 @@ module ActiveRecord
|
|
71
67
|
include Sunstone::ColumnDumper
|
72
68
|
# include Savepoints
|
73
69
|
|
74
|
-
# Returns 'SunstoneAPI' as adapter name for identification purposes.
|
75
|
-
def adapter_name
|
76
|
-
ADAPTER_NAME
|
77
|
-
end
|
78
|
-
|
79
70
|
# Initializes and connects a SunstoneAPI adapter.
|
80
71
|
def initialize(connection, logger, connection_parameters, config)
|
81
72
|
super(connection, logger, config)
|
82
73
|
|
83
74
|
@prepared_statements = false
|
84
|
-
@
|
85
|
-
@connection_parameters, @config = connection_parameters, config
|
75
|
+
@connection_parameters = connection_parameters
|
86
76
|
|
87
|
-
|
88
|
-
|
89
|
-
@type_map = Type::HashLookupTypeMap.new
|
90
|
-
initialize_type_map(type_map)
|
77
|
+
# @type_map = Type::HashLookupTypeMap.new
|
78
|
+
# initialize_type_map(type_map)
|
91
79
|
end
|
92
80
|
|
93
|
-
# Is this connection alive and ready for queries?
|
94
81
|
def active?
|
95
|
-
@connection.
|
96
|
-
true
|
97
|
-
rescue Net::HTTPExceptions
|
98
|
-
false
|
82
|
+
@connection.active?
|
99
83
|
end
|
100
84
|
|
101
|
-
# TODO: this doesn't work yet
|
102
|
-
# Close then reopen the connection.
|
103
85
|
def reconnect!
|
104
86
|
super
|
105
|
-
@connection.
|
106
|
-
# configure_connection
|
87
|
+
@connection.reconnect!
|
107
88
|
end
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
89
|
+
|
90
|
+
def disconnect!
|
91
|
+
super
|
92
|
+
@connection.disconnect!
|
112
93
|
end
|
113
94
|
|
114
95
|
# Executes the delete statement and returns the number of rows affected.
|
@@ -117,14 +98,6 @@ module ActiveRecord
|
|
117
98
|
r.rows.first.to_i
|
118
99
|
end
|
119
100
|
|
120
|
-
# TODO: deal with connection.close
|
121
|
-
# Disconnects from the database if already connected. Otherwise, this
|
122
|
-
# method does nothing.
|
123
|
-
def disconnect!
|
124
|
-
super
|
125
|
-
@connection.close rescue nil
|
126
|
-
end
|
127
|
-
|
128
101
|
def native_database_types #:nodoc:
|
129
102
|
NATIVE_DATABASE_TYPES
|
130
103
|
end
|
@@ -140,7 +113,11 @@ module ActiveRecord
|
|
140
113
|
def update_table_definition(table_name, base) #:nodoc:
|
141
114
|
SunstoneAPI::Table.new(table_name, base)
|
142
115
|
end
|
143
|
-
|
116
|
+
|
117
|
+
def arel_visitor
|
118
|
+
Arel::Visitors::Sunstone.new
|
119
|
+
end
|
120
|
+
|
144
121
|
def collector
|
145
122
|
Arel::Collectors::Sunstone.new
|
146
123
|
end
|
@@ -206,7 +183,7 @@ module ActiveRecord
|
|
206
183
|
m.register_type 'integer', Type::Integer.new
|
207
184
|
m.register_type 'decimal', Type::Decimal.new
|
208
185
|
m.register_type 'datetime', Sunstone::Type::DateTime.new
|
209
|
-
m.register_type 'json', Type::
|
186
|
+
m.register_type 'json', Sunstone::Type::Json.new
|
210
187
|
m.register_type 'uuid', Sunstone::Type::Uuid.new
|
211
188
|
|
212
189
|
if defined?(Sunstone::Type::EWKB)
|
@@ -214,12 +191,6 @@ module ActiveRecord
|
|
214
191
|
end
|
215
192
|
end
|
216
193
|
|
217
|
-
# Connects to a Sunstone API server and sets up the adapter depending on
|
218
|
-
# the connected server's characteristics.
|
219
|
-
def connect
|
220
|
-
@connection = ::Sunstone::Connection.new(@connection_parameters)
|
221
|
-
end
|
222
|
-
|
223
194
|
def create_table_definition(name, temporary, options, as = nil) # :nodoc:
|
224
195
|
SunstoneAPI::TableDefinition.new native_database_types, name, temporary, options, as
|
225
196
|
end
|
@@ -2,7 +2,9 @@ module Arel
|
|
2
2
|
module Collectors
|
3
3
|
class Sunstone < Arel::Collectors::Bind
|
4
4
|
|
5
|
-
|
5
|
+
MAX_URI_LENGTH = 2083
|
6
|
+
|
7
|
+
attr_accessor :request_type, :table, :where, :limit, :offset, :order, :operation, :columns, :updates, :eager_loads, :id, :distinct, :distinct_on
|
6
8
|
|
7
9
|
def cast_attribute(v)
|
8
10
|
if (v.is_a?(ActiveRecord::Attribute))
|
@@ -58,49 +60,81 @@ module Arel
|
|
58
60
|
|
59
61
|
def compile bvs, conn = nil
|
60
62
|
path = "/#{table}"
|
63
|
+
headers = {}
|
64
|
+
request_type_override = nil
|
61
65
|
|
66
|
+
body = nil
|
62
67
|
if updates
|
63
|
-
body = {
|
68
|
+
body = {
|
69
|
+
table.singularize => substitute_binds(updates.clone, bvs)
|
70
|
+
}
|
64
71
|
end
|
65
72
|
|
66
|
-
|
73
|
+
params = {}
|
67
74
|
if where
|
68
|
-
|
69
|
-
if
|
70
|
-
|
75
|
+
params[:where] = substitute_binds(where.clone, bvs)
|
76
|
+
if params[:where].size == 1
|
77
|
+
params[:where] = params[:where].pop
|
71
78
|
end
|
72
79
|
end
|
73
80
|
|
74
81
|
if eager_loads
|
75
|
-
|
82
|
+
params[:include] = eager_loads.clone
|
83
|
+
end
|
84
|
+
|
85
|
+
if distinct_on
|
86
|
+
params[:distinct_on] = distinct_on
|
87
|
+
elsif distinct
|
88
|
+
params[:distinct] = true
|
89
|
+
end
|
90
|
+
|
91
|
+
if limit.is_a?(Arel::Nodes::BindParam)
|
92
|
+
params[:limit] = substitute_binds(limit, bvs)
|
93
|
+
elsif limit
|
94
|
+
params[:limit] = limit
|
95
|
+
end
|
96
|
+
|
97
|
+
if offset.is_a?(Arel::Nodes::BindParam)
|
98
|
+
params[:offset] = substitute_binds(offset, bvs)
|
99
|
+
elsif offset
|
100
|
+
params[:offset] = offset
|
76
101
|
end
|
77
102
|
|
78
|
-
|
79
|
-
get_params[:offset] = substitute_binds(offset, bvs) if offset
|
80
|
-
get_params[:order] = substitute_binds(order, bvs) if order
|
103
|
+
params[:order] = substitute_binds(order, bvs) if order
|
81
104
|
|
82
105
|
case operation
|
83
106
|
when :count
|
84
107
|
path += "/#{operation}"
|
85
108
|
when :calculate
|
86
109
|
path += "/calculate"
|
87
|
-
|
110
|
+
params[:select] = columns
|
88
111
|
when :update, :delete
|
89
|
-
path += "/#{
|
90
|
-
|
91
|
-
end
|
92
|
-
if get_params.size > 0
|
93
|
-
path += "?#{CGI.escape(MessagePack.pack(get_params))}"
|
112
|
+
path += "/#{params[:where]['id']}"
|
113
|
+
params.delete(:where)
|
94
114
|
end
|
95
115
|
|
96
|
-
|
97
|
-
|
98
|
-
|
116
|
+
if params.size > 0 && request_type == Net::HTTP::Get
|
117
|
+
newpath = path + "?#{CGI.escape(MessagePack.pack(params))}"
|
118
|
+
if newpath.length > MAX_URI_LENGTH
|
119
|
+
request_type_override = Net::HTTP::Post
|
120
|
+
headers['X-Http-Method-Override'] = 'GET'
|
121
|
+
if body
|
122
|
+
body.merge!(params)
|
123
|
+
else
|
124
|
+
body = params
|
125
|
+
end
|
126
|
+
else
|
127
|
+
path = newpath
|
128
|
+
headers['Query-Encoding'] = 'application/msgpack'
|
129
|
+
end
|
99
130
|
end
|
131
|
+
|
132
|
+
request = (request_type_override || request_type).new(path)
|
133
|
+
headers.each { |k,v| request[k] = v }
|
100
134
|
request.instance_variable_set(:@sunstone_calculation, true) if [:calculate, :delete].include?(operation)
|
101
135
|
|
102
|
-
if
|
103
|
-
request.body = body
|
136
|
+
if body
|
137
|
+
request.body = body.to_json
|
104
138
|
end
|
105
139
|
|
106
140
|
request
|
@@ -55,6 +55,8 @@ module Arel
|
|
55
55
|
collector.operation = :select
|
56
56
|
end
|
57
57
|
|
58
|
+
collector = maybe_visit o.set_quantifier, collector
|
59
|
+
|
58
60
|
if o.source && !o.source.empty?
|
59
61
|
collector.table = o.source.left.name
|
60
62
|
end
|
@@ -296,13 +298,15 @@ module Arel
|
|
296
298
|
# visit o.expr, collector
|
297
299
|
# end
|
298
300
|
#
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
301
|
+
def visit_Arel_Nodes_Distinct o, collector
|
302
|
+
collector.distinct = true
|
303
|
+
collector
|
304
|
+
end
|
305
|
+
|
306
|
+
def visit_Arel_Nodes_DistinctOn o, collector
|
307
|
+
collector.distinct_on = o.expr.map(&:name)
|
308
|
+
collector
|
309
|
+
end
|
306
310
|
#
|
307
311
|
# def visit_Arel_Nodes_With o, collector
|
308
312
|
# collector << "WITH "
|
@@ -840,13 +844,7 @@ module Arel
|
|
840
844
|
end
|
841
845
|
|
842
846
|
def visit_Arel_Attributes_Cast(o, collector)
|
843
|
-
|
844
|
-
value = { :cast => o.name }
|
845
|
-
if key.is_a?(Hash)
|
846
|
-
add_to_bottom_of_hash(key, value)
|
847
|
-
else
|
848
|
-
{ key => value }
|
849
|
-
end
|
847
|
+
visit(o.relation, collector) # No casting yet
|
850
848
|
end
|
851
849
|
|
852
850
|
def visit_Arel_Attributes_Key o, collector
|
@@ -914,7 +912,7 @@ module Arel
|
|
914
912
|
end
|
915
913
|
|
916
914
|
def literal(o, collector)
|
917
|
-
o
|
915
|
+
o
|
918
916
|
end
|
919
917
|
alias :visit_Arel_Nodes_SqlLiteral :literal
|
920
918
|
alias :visit_Bignum :literal
|
data/lib/sunstone/connection.rb
CHANGED
@@ -42,16 +42,34 @@ module Sunstone
|
|
42
42
|
end
|
43
43
|
|
44
44
|
# Ping the Sunstone. If everything is configured and operating correctly
|
45
|
-
# <tt>"pong"</tt> will be returned. Otherwise and Sunstone::Exception should
|
46
|
-
# thrown.
|
45
|
+
# <tt>"pong"</tt> will be returned. Otherwise and Sunstone::Exception should
|
46
|
+
# be thrown.
|
47
47
|
#
|
48
48
|
# #!ruby
|
49
49
|
# Sunstone.ping # => "pong"
|
50
50
|
#
|
51
|
-
# Sunstone.ping # raises Sunstone::Exception::ServiceUnavailable if a
|
51
|
+
# Sunstone.ping # raises Sunstone::Exception::ServiceUnavailable if a
|
52
|
+
# 503 is returned
|
52
53
|
def ping
|
53
54
|
get('/ping').body
|
54
55
|
end
|
56
|
+
|
57
|
+
def connect!
|
58
|
+
@connection.start
|
59
|
+
end
|
60
|
+
|
61
|
+
def active?
|
62
|
+
@connection.active?
|
63
|
+
end
|
64
|
+
|
65
|
+
def reconnect!
|
66
|
+
disconnect!
|
67
|
+
connect!
|
68
|
+
end
|
69
|
+
|
70
|
+
def disconnect!
|
71
|
+
@connection.finish if @connection.active?
|
72
|
+
end
|
55
73
|
|
56
74
|
# Returns the User-Agent of the client. Defaults to:
|
57
75
|
# "sunstone-ruby/SUNSTONE_VERSION RUBY_VERSION-pPATCH_LEVEL PLATFORM"
|
@@ -150,6 +168,7 @@ module Sunstone
|
|
150
168
|
return_value = nil
|
151
169
|
retry_count = 0
|
152
170
|
begin
|
171
|
+
close_connection = false
|
153
172
|
@connection.request(request) do |response|
|
154
173
|
if response['Deprecation-Notice']
|
155
174
|
ActiveSupport::Deprecation.warn(response['Deprecation-Notice'])
|
@@ -159,17 +178,23 @@ module Sunstone
|
|
159
178
|
|
160
179
|
# Get the cookies
|
161
180
|
response.each_header do |key, value|
|
162
|
-
|
163
|
-
|
181
|
+
case key.downcase
|
182
|
+
when 'set-cookie'
|
183
|
+
if Thread.current[:sunstone_cookie_store]
|
184
|
+
Thread.current[:sunstone_cookie_store].set_cookie(request_uri, value)
|
185
|
+
end
|
186
|
+
when 'connection'
|
187
|
+
close_connection = (value == 'close')
|
164
188
|
end
|
165
189
|
end
|
166
190
|
|
167
191
|
if block_given?
|
168
|
-
return_value =yield(response)
|
192
|
+
return_value = yield(response)
|
169
193
|
else
|
170
|
-
return_value =response
|
194
|
+
return_value = response
|
171
195
|
end
|
172
196
|
end
|
197
|
+
@connection.finish if close_connection
|
173
198
|
rescue ActiveRecord::ConnectionNotEstablished
|
174
199
|
retry_count += 1
|
175
200
|
retry_count == 1 ? retry : raise
|
@@ -322,6 +347,7 @@ module Sunstone
|
|
322
347
|
headers['Accept'] = 'application/json'
|
323
348
|
headers['User-Agent'] = user_agent
|
324
349
|
headers['Api-Version'] = '0.1.0'
|
350
|
+
headers['Connection'] = 'keep-alive'
|
325
351
|
|
326
352
|
request_api_key = Thread.current[:sunstone_api_key] || api_key
|
327
353
|
headers['Api-Key'] = request_api_key if request_api_key
|
data/lib/sunstone/version.rb
CHANGED
data/sunstone.gemspec
CHANGED
@@ -28,6 +28,7 @@ Gem::Specification.new do |s|
|
|
28
28
|
s.add_development_dependency 'sdoc-templates-42floors'
|
29
29
|
s.add_development_dependency 'rgeo'
|
30
30
|
s.add_development_dependency 'simplecov'
|
31
|
+
s.add_development_dependency 'byebug'
|
31
32
|
|
32
33
|
# Runtime
|
33
34
|
s.add_runtime_dependency 'msgpack'
|
@@ -52,15 +52,23 @@ class ActiveRecord::PersistanceTest < Minitest::Test
|
|
52
52
|
test '#save attempts another request while in transaction' do
|
53
53
|
webmock(:get, '/test_model_bs/schema').to_return(
|
54
54
|
body: {
|
55
|
-
|
56
|
-
|
57
|
-
|
55
|
+
columns: {
|
56
|
+
id: {type: 'integer', primary_key: true, null: false, array: false},
|
57
|
+
name: {type: 'string', primary_key: false, null: true, array: false}
|
58
|
+
},
|
59
|
+
limit: 100
|
60
|
+
}.to_json,
|
61
|
+
headers: { 'StandardAPI-Version' => '5.0.0.5' }
|
58
62
|
)
|
59
63
|
webmock(:get, '/test_model_as/schema').to_return(
|
60
64
|
body: {
|
61
|
-
|
62
|
-
|
63
|
-
|
65
|
+
columns: {
|
66
|
+
id: {type: 'integer', primary_key: true, null: false, array: false},
|
67
|
+
name: {type: 'string', primary_key: false, null: true, array: false}
|
68
|
+
},
|
69
|
+
limit: 100
|
70
|
+
}.to_json,
|
71
|
+
headers: { 'StandardAPI-Version' => '5.0.0.5' }
|
64
72
|
)
|
65
73
|
|
66
74
|
assert_raises ActiveRecord::StatementInvalid do
|
@@ -2,9 +2,31 @@ require 'test_helper'
|
|
2
2
|
|
3
3
|
class ActiveRecord::QueryTest < Minitest::Test
|
4
4
|
|
5
|
-
test '::find' do
|
5
|
+
test '::find w/ old schema definition' do
|
6
|
+
replaced_stub = WebMock::StubRegistry.instance.global_stubs.find { |x|
|
7
|
+
x.request_pattern.uri_pattern.to_s == 'http://example.com/ships/schema'
|
8
|
+
}
|
9
|
+
WebMock::StubRegistry.instance.global_stubs.delete(replaced_stub)
|
10
|
+
|
11
|
+
new_stub = WebMock::API.stub_request(:get, "http://example.com/ships/schema").to_return(
|
12
|
+
body: {
|
13
|
+
id: {type: 'integer', primary_key: true, null: false, array: false},
|
14
|
+
fleet_id: {type: 'integer', primary_key: false, null: true, array: false},
|
15
|
+
name: {type: 'string', primary_key: false, null: true, array: false}
|
16
|
+
}.to_json
|
17
|
+
)
|
18
|
+
|
6
19
|
webmock(:get, "/ships", { where: {id: 42}, limit: 1 }).to_return(body: [{id: 42}].to_json)
|
20
|
+
|
21
|
+
assert_equal 42, Ship.find(42).id
|
7
22
|
|
23
|
+
WebMock::API.remove_request_stub(new_stub)
|
24
|
+
WebMock::StubRegistry.instance.global_stubs.push(replaced_stub)
|
25
|
+
end
|
26
|
+
|
27
|
+
test '::find' do
|
28
|
+
webmock(:get, "/ships", { where: {id: 42}, limit: 1 }).to_return(body: [{id: 42}].to_json)
|
29
|
+
|
8
30
|
assert_equal 42, Ship.find(42).id
|
9
31
|
end
|
10
32
|
|
@@ -21,7 +43,8 @@ class ActiveRecord::QueryTest < Minitest::Test
|
|
21
43
|
end
|
22
44
|
|
23
45
|
test '::find_each' do
|
24
|
-
webmock(:get, "/ships", { limit:
|
46
|
+
webmock(:get, "/ships", { limit: 100, offset: 0, order: [{id: :asc}] }).to_return(body: Array.new(100, { id: 1 }).to_json)
|
47
|
+
webmock(:get, "/ships", { limit: 100, offset: 100, order: [{id: :asc}] }).to_return(body: Array.new(10, { id: 2 }).to_json)
|
25
48
|
|
26
49
|
assert_nil Ship.find_each { |s| s }
|
27
50
|
end
|
@@ -63,6 +86,20 @@ class ActiveRecord::QueryTest < Minitest::Test
|
|
63
86
|
assert_equal [], Ship.where(nations: {id: 1}).limit(10).to_a
|
64
87
|
end
|
65
88
|
### end polymorphic test
|
89
|
+
|
90
|
+
# Distinct
|
91
|
+
test '::distinct query' do
|
92
|
+
webmock(:get, "/ships", distinct: true).to_return(body: [].to_json)
|
93
|
+
|
94
|
+
assert_equal [], Ship.distinct
|
95
|
+
end
|
96
|
+
|
97
|
+
# TODO: i need arel-extensions....
|
98
|
+
# test '::distinct_on query' do
|
99
|
+
# webmock(:get, "/ships", distinct_on: ['id']).to_return(body: [].to_json)
|
100
|
+
#
|
101
|
+
# assert_equal [], Ship.distinct_on(:id)
|
102
|
+
# end
|
66
103
|
|
67
104
|
test '::count' do
|
68
105
|
webmock(:get, "/ships/calculate", select: [{count: "*"}]).to_return(body: [10].to_json)
|
@@ -82,6 +119,16 @@ class ActiveRecord::QueryTest < Minitest::Test
|
|
82
119
|
assert_equal 10, Ship.sum(:weight)
|
83
120
|
end
|
84
121
|
|
122
|
+
test '::where(....big get request turns into post...)' do
|
123
|
+
name = 'q' * 3000
|
124
|
+
webmock(:post, "/ships").with(
|
125
|
+
headers: {'X-Http-Method-Override' => 'GET'},
|
126
|
+
body: {where: {name: name}}.to_json
|
127
|
+
).to_return(body: [{id: 42}].to_json)
|
128
|
+
|
129
|
+
assert_equal 42, Ship.where(name: name)[0].id
|
130
|
+
end
|
131
|
+
|
85
132
|
# Relation test
|
86
133
|
|
87
134
|
test '#to_sql' do
|
data/test/models.rb
CHANGED
@@ -1,56 +1,80 @@
|
|
1
|
-
|
2
|
-
|
3
1
|
WebMock::StubRegistry.instance.global_stubs.push(
|
4
2
|
WebMock::RequestStub.new(:get, "http://example.com/ping").to_return(
|
5
|
-
body: "pong"
|
3
|
+
body: "pong",
|
4
|
+
headers: { 'StandardAPI-Version' => '5.0.0.5' }
|
6
5
|
),
|
7
6
|
|
8
7
|
WebMock::RequestStub.new(:get, "http://example.com/tables").to_return(
|
9
|
-
body: %w(ships fleets sailors).to_json
|
8
|
+
body: %w(ships fleets sailors).to_json,
|
9
|
+
headers: { 'StandardAPI-Version' => '5.0.0.5' }
|
10
10
|
),
|
11
11
|
|
12
12
|
WebMock::RequestStub.new(:get, "http://example.com/ships/schema").to_return(
|
13
13
|
body: {
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
columns: {
|
15
|
+
id: {type: 'integer', primary_key: true, null: false, array: false},
|
16
|
+
fleet_id: {type: 'integer', primary_key: false, null: true, array: false},
|
17
|
+
name: {type: 'string', primary_key: false, null: true, array: false}
|
18
|
+
},
|
19
|
+
limit: 100
|
20
|
+
}.to_json,
|
21
|
+
headers: { 'StandardAPI-Version' => '5.0.0.5' }
|
18
22
|
),
|
19
23
|
|
20
24
|
WebMock::RequestStub.new(:get, "http://example.com/fleets/schema").to_return(
|
21
25
|
body: {
|
22
|
-
|
23
|
-
|
24
|
-
|
26
|
+
columns: {
|
27
|
+
id: {type: 'integer', primary_key: true, null: false, array: false},
|
28
|
+
name: {type: 'string', primary_key: false, null: true, array: false}
|
29
|
+
},
|
30
|
+
limit: 100
|
31
|
+
}.to_json,
|
32
|
+
headers: { 'StandardAPI-Version' => '5.0.0.5' }
|
25
33
|
),
|
26
34
|
|
27
35
|
WebMock::RequestStub.new(:get, "http://example.com/sailors/schema").to_return(
|
28
36
|
body: {
|
29
|
-
|
30
|
-
|
31
|
-
|
37
|
+
columns: {
|
38
|
+
id: {type: 'integer', primary_key: true, null: false, array: false},
|
39
|
+
name: {type: 'string', primary_key: false, null: true, array: false}
|
40
|
+
},
|
41
|
+
limit: 100
|
42
|
+
}.to_json,
|
43
|
+
headers: { 'StandardAPI-Version' => '5.0.0.5' }
|
32
44
|
),
|
33
45
|
|
34
46
|
WebMock::RequestStub.new(:get, "http://example.com/sailors_ships/schema").to_return(
|
35
47
|
body: {
|
36
|
-
|
37
|
-
|
38
|
-
|
48
|
+
columns: {
|
49
|
+
sailor_id: {type: 'integer', primary_key: false, null: false, array: false},
|
50
|
+
ship_id: {type: 'integer', primary_key: false, null: true, array: false}
|
51
|
+
},
|
52
|
+
limit: 100
|
53
|
+
}.to_json,
|
54
|
+
headers: { 'StandardAPI-Version' => '5.0.0.5' }
|
39
55
|
),
|
40
56
|
|
41
57
|
WebMock::RequestStub.new(:get, "http://example.com/countries/schema").to_return(
|
42
58
|
body: {
|
43
|
-
|
44
|
-
|
45
|
-
|
59
|
+
columns: {
|
60
|
+
id: {type: 'integer', primary_key: true, null: false, array: false},
|
61
|
+
name: {type: 'string', primary_key: false, null: true, array: false}
|
62
|
+
},
|
63
|
+
limit: 100
|
64
|
+
}.to_json,
|
65
|
+
headers: { 'StandardAPI-Version' => '5.0.0.5' }
|
46
66
|
),
|
47
67
|
|
48
68
|
WebMock::RequestStub.new(:get, "http://example.com/ownerships/schema").to_return(
|
49
69
|
body: {
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
70
|
+
columns: {
|
71
|
+
country_id: {type: 'integer', primary_key: false, null: false, array: false},
|
72
|
+
asset_type: {type: 'string', primary_key: false, null: false, array: false},
|
73
|
+
asset_id: {type: 'integer', primary_key: false, null: true, array: false}
|
74
|
+
},
|
75
|
+
limit: 100
|
76
|
+
}.to_json,
|
77
|
+
headers: { 'StandardAPI-Version' => '5.0.0.5' }
|
54
78
|
)
|
55
79
|
)
|
56
80
|
|
data/test/test_helper.rb
CHANGED
@@ -9,6 +9,7 @@ SimpleCov.start do
|
|
9
9
|
end
|
10
10
|
|
11
11
|
require 'rgeo'
|
12
|
+
require 'byebug'
|
12
13
|
require "minitest/autorun"
|
13
14
|
require 'minitest/unit'
|
14
15
|
require 'minitest/reporters'
|
@@ -18,13 +19,6 @@ require 'mocha/mini_test'
|
|
18
19
|
require 'sunstone'
|
19
20
|
require File.expand_path('../models.rb', __FILE__)
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
# require 'faker'
|
24
|
-
# require "mocha"
|
25
|
-
# require "mocha/mini_test"
|
26
|
-
# require 'active_support/testing/time_helpers'
|
27
|
-
|
28
22
|
Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
|
29
23
|
|
30
24
|
# File 'lib/active_support/testing/declarative.rb', somewhere in rails....
|
@@ -70,7 +64,7 @@ class Minitest::Test
|
|
70
64
|
|
71
65
|
def webmock(method, path, query=nil)
|
72
66
|
query = deep_transform_query(query) if query
|
73
|
-
|
67
|
+
|
74
68
|
stub_request(method, /^#{ExampleRecord.connection.instance_variable_get(:@connection).url}/).with do |req|
|
75
69
|
if query
|
76
70
|
req&.uri&.path == path && req.uri.query && unpack(req.uri.query.sub(/=true$/, '')) == query
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sunstone
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 5.0.0.
|
4
|
+
version: 5.0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jon Bracy
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-11-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -192,6 +192,20 @@ dependencies:
|
|
192
192
|
- - ">="
|
193
193
|
- !ruby/object:Gem::Version
|
194
194
|
version: '0'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: byebug
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - ">="
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0'
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ">="
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
195
209
|
- !ruby/object:Gem::Dependency
|
196
210
|
name: msgpack
|
197
211
|
requirement: !ruby/object:Gem::Requirement
|
@@ -286,6 +300,7 @@ files:
|
|
286
300
|
- lib/active_record/connection_adapters/sunstone/type/array.rb
|
287
301
|
- lib/active_record/connection_adapters/sunstone/type/date_time.rb
|
288
302
|
- lib/active_record/connection_adapters/sunstone/type/ewkb.rb
|
303
|
+
- lib/active_record/connection_adapters/sunstone/type/json.rb
|
289
304
|
- lib/active_record/connection_adapters/sunstone/type/uuid.rb
|
290
305
|
- lib/active_record/connection_adapters/sunstone/type_metadata.rb
|
291
306
|
- lib/active_record/connection_adapters/sunstone_adapter.rb
|
@@ -329,7 +344,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
329
344
|
version: '0'
|
330
345
|
requirements: []
|
331
346
|
rubyforge_project:
|
332
|
-
rubygems_version: 2.
|
347
|
+
rubygems_version: 2.6.4
|
333
348
|
signing_key:
|
334
349
|
specification_version: 4
|
335
350
|
summary: A library for interacting with REST APIs
|