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 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