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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c1d7081154f231b08f1e85a1fa5a7104fc0fd3fd
4
- data.tar.gz: e9417935129734d01bb8963812dc7bad64f06d1d
3
+ metadata.gz: aeb070751a16b6997e533f2cca908ab9de07e1b4
4
+ data.tar.gz: c4bb3a0d14aab39201c4eb037d3d3c47e84c6786
5
5
  SHA512:
6
- metadata.gz: 5209a595a579fe676cafb93a560c3029a109a4bfc68aac344e909191fbae6b8693aa5d51b39db251bca2c59bed59a6323f103ca57d6ecb3f687858ba33af1d28
7
- data.tar.gz: 6114598d416b573c17c69bc49084f760ac0aa06f126bae6061c8dabb1f2179b4e851de3505be98fb37056953d83c5a983d7bd785a4f426c3fd32f8fa5523dbd0
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
- self.class.define_readers(attributes.keys)
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.format_request(query_id, query, parameter, cache)
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, &paramIterator) }
@@ -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.build_request_body(
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].is_a?(Symbol)
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.call(self, foreign_entity, join_attributes, true)
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.call(self, foreign_entity, join_attributes, false))
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(accessor_name)
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
- # Define the readers for the attribute names specified
174
- # @param attr_names [Array]
175
- def define_readers(attr_names)
176
- if @_readers_defined.nil?
177
- attr_names.each do |attr_name|
178
- if method_defined?(attr_name)
179
- raise "Cannot define accessor: #{attr_name}"
180
- else
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(association_name)
268
+ def register_association(association_struct)
209
269
  @registered_associations ||= []
210
- @registered_associations << association_name.to_sym
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.registered_associations.map do |a_name|
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.registered_associations.map.with_index do |a_name, i|
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.registered_associations.map do |a_name|
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
@@ -29,7 +29,7 @@ module SqlcachedClient
29
29
  next_batch =
30
30
  server.run_query(
31
31
  session,
32
- server.build_request_body(
32
+ server.build_request(
33
33
  batch
34
34
  )
35
35
  ).map.with_index do |resultset_data, i|
@@ -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 http response #{resp.code} from server - #{resp_body.inspect}"
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
- def build_request_body(ary)
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
- def format_request(query_id, query_template, params, cache)
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
- queryId: query_id,
56
- queryTemplate: query_template,
57
- queryParams: params,
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
@@ -1,3 +1,3 @@
1
1
  module SqlcachedClient
2
- VERSION = "0.1.2"
2
+ VERSION = "0.2.0"
3
3
  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(format_request: "this is the request"))
104
- expect(entity_class.server).to receive(:format_request).with("foo", "bar", { baz: "biz" }, true)
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
- format_request: "this is the request",
113
- build_request_body: "request body",
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(:format_request).with(
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 :build_request_body do
16
+ describe :build_request do
17
17
  it "should put the passed value into an hash" do
18
- expect(server.build_request_body("foo")).to eq({ batch: "foo" })
18
+ expect(server.build_request("foo")).to eq({ batch: "foo" })
19
19
  end
20
20
  end
21
21
 
22
22
 
23
- describe :format_request do
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.format_request("foo", "bar", "baz", "cache")).to eq({
26
- queryId: "foo",
27
- queryTemplate: "bar",
28
- queryParams: "baz",
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.1.2
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-03 00:00:00.000000000 Z
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