sqlcached_client 0.1.2 → 0.2.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/sqlcached_client/entity.rb +123 -30
- data/lib/sqlcached_client/resultset.rb +1 -1
- data/lib/sqlcached_client/server.rb +27 -9
- data/lib/sqlcached_client/tree_visitor.rb +19 -0
- data/lib/sqlcached_client/version.rb +1 -1
- data/spec/sqlcached_client/entity_spec.rb +26 -5
- data/spec/sqlcached_client/server_spec.rb +16 -7
- data/spec/sqlcached_client/tree_visitor_spec.rb +55 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aeb070751a16b6997e533f2cca908ab9de07e1b4
|
4
|
+
data.tar.gz: c4bb3a0d14aab39201c4eb037d3d3c47e84c6786
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 684c26e56298b9fb7f69375371849359cfc5a2222a7f1c2a82728be476c0dfa236d77e9ef3e544b063ec2878df4cccd1a8579d4306047d0316fe398deec5ada6
|
7
|
+
data.tar.gz: fd4181382f54cdddac87c174c199ddf316301d71ed1115967712ddb3a689bbc3d75b304c7d8fdd9865a2301d175c8192ddbdc31d74f00c22d9ce6f7b13c5d36d
|
@@ -1,17 +1,19 @@
|
|
1
1
|
require 'sqlcached_client/resultset'
|
2
2
|
require 'sqlcached_client/server'
|
3
3
|
require 'sqlcached_client/arel'
|
4
|
+
require 'sqlcached_client/tree_visitor'
|
4
5
|
|
5
6
|
module SqlcachedClient
|
6
7
|
class Entity
|
7
8
|
extend Arel
|
9
|
+
extend TreeVisitor
|
8
10
|
|
9
11
|
attr_reader :attributes
|
10
12
|
|
11
13
|
# @param attributes [Hash]
|
12
14
|
def initialize(attributes)
|
13
15
|
@attributes = attributes
|
14
|
-
|
16
|
+
define_readers(attributes.keys)
|
15
17
|
end
|
16
18
|
|
17
19
|
|
@@ -74,7 +76,7 @@ module SqlcachedClient
|
|
74
76
|
request =
|
75
77
|
begin
|
76
78
|
paramIterator = -> (parameter) {
|
77
|
-
server.
|
79
|
+
server.build_request_item(query_id, query, parameter, cache)
|
78
80
|
}
|
79
81
|
if params.is_a?(Array)
|
80
82
|
params.map { |p| instance_exec(p, ¶mIterator) }
|
@@ -87,7 +89,7 @@ module SqlcachedClient
|
|
87
89
|
else
|
88
90
|
data =
|
89
91
|
server.session do |server, session|
|
90
|
-
server.run_query(session, server.
|
92
|
+
server.run_query(session, server.build_request(
|
91
93
|
request.is_a?(Array) ? request : [request]
|
92
94
|
))
|
93
95
|
end
|
@@ -120,10 +122,10 @@ module SqlcachedClient
|
|
120
122
|
association = -> (this, foreign_entity, join_attributes, dry_run) {
|
121
123
|
foreign_entity.where(Hash[ join_attributes.map do |attr_names|
|
122
124
|
attr_value =
|
123
|
-
if attr_names[1]
|
124
|
-
this.send(attr_names[1])
|
125
|
-
else
|
125
|
+
if this.join_constant_value?(attr_names[1])
|
126
126
|
attr_names[1]
|
127
|
+
else
|
128
|
+
this.send(attr_names[1])
|
127
129
|
end
|
128
130
|
[ attr_names[0], attr_value ]
|
129
131
|
end ], dry_run)
|
@@ -137,11 +139,11 @@ module SqlcachedClient
|
|
137
139
|
# get the associated entity class
|
138
140
|
foreign_entity = Module.const_get(foreign_class_name)
|
139
141
|
if dry_run
|
140
|
-
association.
|
142
|
+
association.(self, foreign_entity, join_attributes, true)
|
141
143
|
else
|
142
144
|
instance_variable_get(memoize_var) ||
|
143
145
|
instance_variable_set(memoize_var,
|
144
|
-
association.
|
146
|
+
association.(self, foreign_entity, join_attributes, false))
|
145
147
|
end
|
146
148
|
end
|
147
149
|
# define the setter method
|
@@ -152,7 +154,11 @@ module SqlcachedClient
|
|
152
154
|
Resultset.new(foreign_entity, array))
|
153
155
|
end
|
154
156
|
# save the newly created association
|
155
|
-
register_association(
|
157
|
+
register_association(OpenStruct.new({
|
158
|
+
accessor_name: accessor_name.to_sym,
|
159
|
+
class_name: foreign_class_name,
|
160
|
+
join_attributes: join_attributes
|
161
|
+
}))
|
156
162
|
end
|
157
163
|
|
158
164
|
# Defines a 'has_one' relationship. See 'has_many' for the available
|
@@ -166,25 +172,19 @@ module SqlcachedClient
|
|
166
172
|
end
|
167
173
|
end
|
168
174
|
|
175
|
+
|
169
176
|
def registered_associations
|
170
177
|
@registered_associations || []
|
171
178
|
end
|
172
179
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
define_method(attr_name) do
|
182
|
-
attributes[attr_name]
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
186
|
-
@_readers_defined = true
|
187
|
-
end
|
180
|
+
|
181
|
+
def association_names
|
182
|
+
registered_associations.map(&:accessor_name)
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
def is_an_association?(name)
|
187
|
+
association_names.include?(name.to_sym)
|
188
188
|
end
|
189
189
|
|
190
190
|
# Configures the caching timing if a parameter is provided, otherwise
|
@@ -203,39 +203,132 @@ module SqlcachedClient
|
|
203
203
|
end
|
204
204
|
end
|
205
205
|
|
206
|
+
|
207
|
+
def build_query_tree
|
208
|
+
# returns the subtrees (associated classes) of the given entity class
|
209
|
+
get_associated_entities = -> (entity) {
|
210
|
+
entity.registered_associations.map do |a|
|
211
|
+
Module.const_get(a.class_name)
|
212
|
+
end
|
213
|
+
}
|
214
|
+
|
215
|
+
# function to apply to each node while traversing the tree
|
216
|
+
visit = -> (entity, parent, index) {
|
217
|
+
entity.server.build_request_item(
|
218
|
+
entity.query_id,
|
219
|
+
entity.query,
|
220
|
+
# query_params
|
221
|
+
if parent
|
222
|
+
Hash[
|
223
|
+
parent.registered_associations[index].join_attributes.map do |j_attr|
|
224
|
+
[ j_attr[0], {
|
225
|
+
value: j_attr[1],
|
226
|
+
type:
|
227
|
+
if entity.join_constant_value?(j_attr[1])
|
228
|
+
'constant'
|
229
|
+
else
|
230
|
+
'parent_attribute'
|
231
|
+
end
|
232
|
+
} ]
|
233
|
+
end
|
234
|
+
]
|
235
|
+
else
|
236
|
+
nil
|
237
|
+
end,
|
238
|
+
entity.cache
|
239
|
+
).merge(associations: entity.association_names)
|
240
|
+
}
|
241
|
+
|
242
|
+
# builds the result of a visit step
|
243
|
+
result_builder = -> (root, subtrees) {
|
244
|
+
{ root: root, subtrees: subtrees }
|
245
|
+
}
|
246
|
+
|
247
|
+
# traverse the tree
|
248
|
+
visit_in_preorder(get_associated_entities, visit, result_builder)
|
249
|
+
end
|
250
|
+
|
251
|
+
|
252
|
+
def join_constant_value?(value)
|
253
|
+
!value.is_a?(Symbol)
|
254
|
+
end
|
255
|
+
|
256
|
+
# Like 'where' but loads every associated entity recursively at any level,
|
257
|
+
# with only one interaction with the server
|
258
|
+
# @param root_conditions [Array]
|
259
|
+
def load_tree(root_conditions)
|
260
|
+
server.session do |server, session|
|
261
|
+
server.run_query(session, server.build_tree_request(
|
262
|
+
build_query_tree, root_conditions))
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
206
266
|
private
|
207
267
|
|
208
|
-
def register_association(
|
268
|
+
def register_association(association_struct)
|
209
269
|
@registered_associations ||= []
|
210
|
-
@registered_associations <<
|
270
|
+
@registered_associations << association_struct
|
211
271
|
end
|
212
272
|
end # class << self
|
213
273
|
|
274
|
+
# Define the readers for the attribute names specified
|
275
|
+
# @param attr_names [Array]
|
276
|
+
def define_readers(attr_names)
|
277
|
+
attr_names.each do |attr_name|
|
278
|
+
if respond_to?(attr_name)
|
279
|
+
if self.class.is_an_association?(attr_name)
|
280
|
+
# lazy instantiate associated records
|
281
|
+
association_writer = method("#{attr_name}=")
|
282
|
+
association_reader = method(attr_name)
|
283
|
+
define_singleton_method(attr_name) do
|
284
|
+
association_writer.(attributes[attr_name])
|
285
|
+
association_reader.()
|
286
|
+
end
|
287
|
+
else
|
288
|
+
raise "Cannot define accessor: #{attr_name}"
|
289
|
+
end
|
290
|
+
else
|
291
|
+
define_singleton_method(attr_name) do
|
292
|
+
attributes[attr_name]
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
|
299
|
+
def join_constant_value?(value)
|
300
|
+
self.class.join_constant_value?(value)
|
301
|
+
end
|
302
|
+
|
303
|
+
|
214
304
|
def get_association_requests
|
215
|
-
self.class.
|
305
|
+
self.class.association_names.map do |a_name|
|
216
306
|
send(a_name, true)
|
217
307
|
end
|
218
308
|
end
|
219
309
|
|
310
|
+
|
220
311
|
def set_associations_data(associations_data)
|
221
|
-
self.class.
|
312
|
+
self.class.association_names.map.with_index do |a_name, i|
|
222
313
|
send("#{a_name}=", associations_data[i])
|
223
314
|
end
|
224
315
|
end
|
225
316
|
|
317
|
+
|
226
318
|
def get_associations
|
227
|
-
self.class.
|
319
|
+
self.class.association_names.map do |a_name|
|
228
320
|
send(a_name)
|
229
321
|
end
|
230
322
|
end
|
231
323
|
|
324
|
+
|
232
325
|
def build_associations(max_depth = false)
|
233
326
|
Resultset.new(self.class, [self]).build_associations(max_depth)
|
234
327
|
end
|
235
328
|
|
329
|
+
|
236
330
|
def to_h
|
237
331
|
attributes
|
238
332
|
end
|
239
|
-
|
240
333
|
end # class Entity
|
241
334
|
end
|
@@ -6,6 +6,7 @@ module SqlcachedClient
|
|
6
6
|
class Server
|
7
7
|
attr_reader :host, :port
|
8
8
|
|
9
|
+
# @param config [Hash] something like { host: 'localhost', port: 8081 }
|
9
10
|
def initialize(config)
|
10
11
|
@host = config[:host]
|
11
12
|
@port = config[:port]
|
@@ -25,7 +26,7 @@ module SqlcachedClient
|
|
25
26
|
if 200 == resp.code.to_i
|
26
27
|
resp_body
|
27
28
|
else
|
28
|
-
raise "Got
|
29
|
+
raise "Got HTTP response #{resp.code} from server - #{resp_body.inspect}"
|
29
30
|
end
|
30
31
|
end
|
31
32
|
|
@@ -44,28 +45,45 @@ module SqlcachedClient
|
|
44
45
|
end
|
45
46
|
end
|
46
47
|
|
47
|
-
|
48
|
-
|
48
|
+
# Builds a 'standard' request body
|
49
|
+
# @param ary [Array]
|
50
|
+
# @return [Hash]
|
51
|
+
def build_request(ary)
|
49
52
|
{ batch: ary }
|
50
53
|
end
|
51
54
|
|
55
|
+
# Builds a request body suitable for a 'tree' request
|
56
|
+
# @param tree [Hash]
|
57
|
+
# @param root_parameters [Array] a vector of actual condition parameters
|
58
|
+
# for the root query
|
59
|
+
def build_tree_request(tree, root_parameters)
|
60
|
+
{ tree: tree, root_parameters: root_parameters }
|
61
|
+
end
|
52
62
|
|
53
|
-
|
63
|
+
# Formats the parameters passed in the way the server expects
|
64
|
+
# @param query_id [String] unique identifier for this query
|
65
|
+
# @param query_template [String] the sql template
|
66
|
+
# @param params [Hash] the parameter to fill the template
|
67
|
+
# @param cache [Integer] number of seconds the data should be cached
|
68
|
+
# @return [Hash]
|
69
|
+
def build_request_item(query_id, query_template, params, cache)
|
54
70
|
{
|
55
|
-
|
56
|
-
|
57
|
-
|
71
|
+
query_id: query_id,
|
72
|
+
query_template: query_template,
|
73
|
+
query_params: params,
|
58
74
|
cache: cache
|
59
75
|
}
|
60
76
|
end
|
61
77
|
|
62
|
-
|
78
|
+
# @return [Net::HTTP] an http session on the server
|
63
79
|
def get_session
|
64
80
|
url = server_url
|
65
81
|
Net::HTTP.start(url.host, url.port)
|
66
82
|
end
|
67
83
|
|
68
|
-
|
84
|
+
# Starts an http session yielding the block passed. Closes the connession
|
85
|
+
# when the block returns.
|
86
|
+
# @return the value returned by the block
|
69
87
|
def session
|
70
88
|
s = get_session
|
71
89
|
ret_value = yield(self, s) if block_given?
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module SqlcachedClient
|
2
|
+
module TreeVisitor
|
3
|
+
|
4
|
+
# @param get_subtrees [Proc]
|
5
|
+
# @param visit [Proc]
|
6
|
+
# @param result_builder [Proc]
|
7
|
+
# @param parent [Object]
|
8
|
+
# @param index [Integer]
|
9
|
+
def visit_in_preorder(get_subtrees, visit, result_builder,
|
10
|
+
parent = nil, index = nil)
|
11
|
+
result_builder.(
|
12
|
+
visit.(self, parent, index),
|
13
|
+
get_subtrees.(self).map.with_index do |item, i|
|
14
|
+
item.visit_in_preorder(get_subtrees, visit, result_builder, self, i)
|
15
|
+
end
|
16
|
+
)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -100,8 +100,8 @@ describe SqlcachedClient::Entity do
|
|
100
100
|
|
101
101
|
context "if dry_run" do
|
102
102
|
it "should return the request that would be sent to the server" do
|
103
|
-
entity_class.server(double(
|
104
|
-
expect(entity_class.server).to receive(:
|
103
|
+
entity_class.server(double(build_request_item: "this is the request"))
|
104
|
+
expect(entity_class.server).to receive(:build_request_item).with("foo", "bar", { baz: "biz" }, true)
|
105
105
|
expect(entity_class.where({ baz: "biz" }, true)).to eq("this is the request")
|
106
106
|
end
|
107
107
|
end
|
@@ -109,11 +109,11 @@ describe SqlcachedClient::Entity do
|
|
109
109
|
context "if not dry_run" do
|
110
110
|
it "should create a new ResultSet" do
|
111
111
|
entity_class.server(double(
|
112
|
-
|
113
|
-
|
112
|
+
build_request_item: "this is the request",
|
113
|
+
build_request: "request body",
|
114
114
|
session: [ [{ key: "value" }], [{ key: "value" }] ]
|
115
115
|
))
|
116
|
-
expect(entity_class.server).to receive(:
|
116
|
+
expect(entity_class.server).to receive(:build_request_item).with(
|
117
117
|
"foo", "bar", { baz: "biz" }, true)
|
118
118
|
expect(entity_class.where({ baz: "biz" })).to be_instance_of(
|
119
119
|
SqlcachedClient::Resultset)
|
@@ -160,4 +160,25 @@ describe SqlcachedClient::Entity do
|
|
160
160
|
expect(entity.to_h).to eq(h)
|
161
161
|
end
|
162
162
|
end
|
163
|
+
|
164
|
+
|
165
|
+
describe :join_constant_value? do
|
166
|
+
context "if value is a Symbol" do
|
167
|
+
it "should be false" do
|
168
|
+
expect(SqlcachedClient::Entity.join_constant_value?(:foo)).to eq(false)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
context "if value is not a Symbol" do
|
173
|
+
it "should be true" do
|
174
|
+
expect(SqlcachedClient::Entity.join_constant_value?('bar')).to eq(true)
|
175
|
+
expect(SqlcachedClient::Entity.join_constant_value?(1)).to eq(true)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
|
181
|
+
describe :build_query_tree do
|
182
|
+
pending
|
183
|
+
end
|
163
184
|
end
|
@@ -13,19 +13,28 @@ describe SqlcachedClient::Server do
|
|
13
13
|
end
|
14
14
|
|
15
15
|
|
16
|
-
describe :
|
16
|
+
describe :build_request do
|
17
17
|
it "should put the passed value into an hash" do
|
18
|
-
expect(server.
|
18
|
+
expect(server.build_request("foo")).to eq({ batch: "foo" })
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
22
|
|
23
|
-
describe :
|
23
|
+
describe :build_tree_request do
|
24
|
+
it "should be an Hash with 'tree' and 'root_parameters' keys" do
|
25
|
+
expect(server.build_tree_request('tree', 'root')).to eq({
|
26
|
+
tree: 'tree', root_parameters: 'root'
|
27
|
+
})
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
describe :build_request_item do
|
24
33
|
it "should be an hash with id, template, params keys" do
|
25
|
-
expect(server.
|
26
|
-
|
27
|
-
|
28
|
-
|
34
|
+
expect(server.build_request_item("foo", "bar", "baz", "cache")).to eq({
|
35
|
+
query_id: "foo",
|
36
|
+
query_template: "bar",
|
37
|
+
query_params: "baz",
|
29
38
|
cache: "cache"
|
30
39
|
})
|
31
40
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'sqlcached_client/tree_visitor'
|
2
|
+
|
3
|
+
describe SqlcachedClient::TreeVisitor do
|
4
|
+
|
5
|
+
let(:fake_class) do
|
6
|
+
Class.new(Object) do
|
7
|
+
include SqlcachedClient::TreeVisitor
|
8
|
+
|
9
|
+
attr_reader :subtrees, :depth
|
10
|
+
|
11
|
+
def initialize(depth, subtrees)
|
12
|
+
@depth = depth; @subtrees = subtrees
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:fake_tree) do
|
18
|
+
fake_class.new(0, [
|
19
|
+
fake_class.new(1, [
|
20
|
+
fake_class.new(2, [
|
21
|
+
fake_class.new(3, []), fake_class.new(3, [])
|
22
|
+
])
|
23
|
+
]),
|
24
|
+
fake_class.new(1, [])
|
25
|
+
])
|
26
|
+
end
|
27
|
+
|
28
|
+
let(:result_builder) do
|
29
|
+
-> (root, subtrees) { [root, subtrees] }
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
describe :visit_in_preorder do
|
34
|
+
|
35
|
+
context "with an empty tree" do
|
36
|
+
it "is expected to visit the root element only" do
|
37
|
+
tree = double(element: 'root', subtrees: [])
|
38
|
+
tree.extend(SqlcachedClient::TreeVisitor)
|
39
|
+
values = []
|
40
|
+
tree.visit_in_preorder(:subtrees.to_proc,
|
41
|
+
-> (el, parent, index) { values << el.element },
|
42
|
+
result_builder)
|
43
|
+
expect(values).to eq(['root'])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it "is expected to be 0, 1, 2, 3, 3, 1" do
|
48
|
+
values = []
|
49
|
+
fake_tree.visit_in_preorder(:subtrees.to_proc,
|
50
|
+
-> (item, parent, index) { values << item.depth },
|
51
|
+
result_builder)
|
52
|
+
expect(values).to eq([0, 1, 2, 3, 3, 1])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sqlcached_client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Roberto Maestroni
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-06-
|
11
|
+
date: 2015-06-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -125,10 +125,12 @@ files:
|
|
125
125
|
- lib/sqlcached_client/entity.rb
|
126
126
|
- lib/sqlcached_client/resultset.rb
|
127
127
|
- lib/sqlcached_client/server.rb
|
128
|
+
- lib/sqlcached_client/tree_visitor.rb
|
128
129
|
- lib/sqlcached_client/version.rb
|
129
130
|
- spec/sqlcached_client/entity_spec.rb
|
130
131
|
- spec/sqlcached_client/resultset_spec.rb
|
131
132
|
- spec/sqlcached_client/server_spec.rb
|
133
|
+
- spec/sqlcached_client/tree_visitor_spec.rb
|
132
134
|
- sqlcached_client.gemspec
|
133
135
|
homepage: https://github.com/rmaestroni/sqlcached_client
|
134
136
|
licenses:
|
@@ -158,3 +160,4 @@ test_files:
|
|
158
160
|
- spec/sqlcached_client/entity_spec.rb
|
159
161
|
- spec/sqlcached_client/resultset_spec.rb
|
160
162
|
- spec/sqlcached_client/server_spec.rb
|
163
|
+
- spec/sqlcached_client/tree_visitor_spec.rb
|