sunstone 5.0.0.1 → 5.0.0.2
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/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
|