sqlcached_client 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|