querrel 1.2.0 → 1.3.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: 8a221606ea4c6afe6ec28d674c8a3991bb221d15
4
- data.tar.gz: 3c1bb8daff36108b3addd087d3769cbe3b5cefab
3
+ metadata.gz: ac00fa1731878f9644eeef4cba1df54454b70d3c
4
+ data.tar.gz: 3333ac82975cc0ab9a9359ea254db41c97330ded
5
5
  SHA512:
6
- metadata.gz: 971c487ef162a42b1874616ea1f267a13836d0671766fe12f83dc359fef3df071d0b4b9c6cf8e39d9cd85cf896184b806f3bfb6881c4f9fb5d102942c7c9e744
7
- data.tar.gz: d2aa19d3e6cb412bee5210107b616607847870baf61bc5d4c8948a13d756c771c278610aa578f5f5e4f1f0c6a225cb2af61a3b384a30a85751e00e52602681cd
6
+ metadata.gz: fa8d8b12c96c966a0b5e755749a175902f4ba5b1808f94d3cdb70179250d6f624eba73d69f754a9e0e642bc10c02960455ac1bafb25fccb69dd44e8980daaef3
7
+ data.tar.gz: a65135e0097d7ccdad1d7f276ab77adc0cc48f1787db0a413c12afa93920795558157ccda86737eb51a85e589ab86c6f2ed7910b771cb0155a89063e567f41e6
data/README.md CHANGED
@@ -18,6 +18,8 @@ Or install it yourself as:
18
18
 
19
19
  ## Usage
20
20
 
21
+ ### Basic
22
+
21
23
  You can use with Querrel directly via it's top level namespace, or by creating an instance:
22
24
 
23
25
  ```ruby
@@ -28,6 +30,35 @@ all_brands = Querrel.new(['db1', 'db2', 'db3']).query(Brand.all)
28
30
 
29
31
  Either of the above will give you an array of all the Brand objects from all the given databases. The records will be marked as readonly.
30
32
 
33
+ ### Advanced
34
+
35
+ `query` will yield a block with the passed in `ActiveRecord::Relation`, this allows you to do additional operations on the results before they are merged, for example you could use pluck:
36
+
37
+ ```ruby
38
+ all_brand_names = q.query(Brand.all) do |s|
39
+ s.pluck(:name)
40
+ end
41
+ ```
42
+
43
+ There is also a `run` method which instead of running a preprescribed scope and merging, will yield the `ConnectedModelFactory` class which allows you to wrap any ActiveRecord class so that you can query it in the thread, for instance:
44
+
45
+ ```ruby
46
+ require 'thread'
47
+ all_brands = Brand.all
48
+ b_s = Mutex.new
49
+ all_products = Product.all
50
+ p_s = Mutex.new
51
+
52
+ Querrel.run(on: dbs) do |q|
53
+ b_s.synchronize { all_brands += q[Brand].all.to_a }
54
+ p_s.synchronize { all_products += q[Product].all.to_a }
55
+ end
56
+ ```
57
+
58
+ At the moment, any queries using `run` and the ConnectedModelFactory won't return objects of the class passed in, but an anonymous class instead. However, `query` can marshall the results back into the original class.
59
+
60
+ ### Databases
61
+
31
62
  There are three ways in which you can instruct Querrel which databases to use:
32
63
 
33
64
  1. Pass in an array of environments, e.g. `Querrel.new([:customer1, :customer2])`
@@ -47,6 +78,14 @@ There are three ways in which you can instruct Querrel which databases to use:
47
78
  })
48
79
  ```
49
80
 
81
+ ### Configuration
82
+
83
+ By default Querrel will use a maximum of 20 threads, but you can adjust this using the `:threads` option:
84
+
85
+ ```ruby
86
+ q = Querrel.new(dbs, threads: 50)
87
+ ```
88
+
50
89
  ## Contributing
51
90
 
52
91
  1. Fork it ( https://github.com/meritec/querrel/fork )
@@ -6,6 +6,8 @@ module Querrel
6
6
  Class.new(model).tap do |m|
7
7
  m.send(:define_singleton_method, :name) { dynamic_class_name }
8
8
  m.establish_connection(con_spec.config)
9
+
10
+ Thread.current[:querrel_connected_models] << m
9
11
  end
10
12
  end
11
13
  end
@@ -6,6 +6,7 @@ module Querrel
6
6
  end
7
7
 
8
8
  def map(scope, options = {}, &blk)
9
+ options = @options.merge(options)
9
10
  if options.key?(:on)
10
11
  resolver = ConnectionResolver.new(options[:on], !!options[:db_names])
11
12
  dbs = resolver.configurations.keys
@@ -16,29 +17,38 @@ module Querrel
16
17
 
17
18
  query_model = scope.model
18
19
  results = {}
20
+ results_semaphore = Mutex.new
19
21
 
20
- threads = []
22
+ pool = StaticPool.new(options[:threads] || 20)
21
23
  dbs.each do |db|
22
- threads << Thread.new do
24
+ pool.enqueue do
25
+ Thread.current[:querrel_connected_models] = []
23
26
  con_spec = retrieve_connection_spec(db, resolver)
24
27
  Thread.current[:querrel_con_spec] = con_spec
25
28
  dynamic_class = ConnectedModelFactory[query_model, con_spec]
26
29
 
27
30
  begin
28
31
  local_scope = dynamic_class.all.merge(scope)
29
- results[db] = if block_given?
30
- res = yield(local_scope)
32
+ local_results = if block_given?
33
+ res = yield(local_scope, ConnectedModelFactory)
31
34
  res.to_a.each(&:readonly!) if res.is_a?(ActiveRecord::Relation)
32
35
  res
33
36
  else
34
- local_scope.to_a.each(&:readonly!)
37
+ local_scope.to_a.map do |r|
38
+ query_model.instantiate(r.attributes, {}).tap(&:readonly!)
39
+ end
35
40
  end
41
+
42
+ results_semaphore.synchronize { results[db] = local_results }
36
43
  ensure
37
- dynamic_class.connection_pool.release_connection
44
+ Thread.current[:querrel_connected_models].each do |m|
45
+ m.connection_pool.release_connection
46
+ end
47
+ Thread.current[:querrel_connected_models] = nil
38
48
  end
39
49
  end
40
50
  end
41
- threads.each(&:join)
51
+ pool.do_your_thang!
42
52
 
43
53
  results
44
54
  end
@@ -1,5 +1,6 @@
1
1
  require 'querrel/connected_model_factory'
2
2
  require 'querrel/connection_resolver'
3
+ require 'querrel/static_pool'
3
4
  require 'querrel/map_reduce'
4
5
 
5
6
  module Querrel
@@ -8,10 +9,12 @@ module Querrel
8
9
  attr_accessor :connection_resolver
9
10
 
10
11
  def initialize(dbs, options = {})
11
- @connection_resolver = ConnectionResolver.new(dbs, options[:db_names])
12
+ @connection_resolver = ConnectionResolver.new(dbs, options.delete(:db_names))
13
+ @options = options
12
14
  end
13
15
 
14
16
  def run(options = {}, &blk)
17
+ options = @options.merge(options)
15
18
  if options.key?(:on)
16
19
  resolver = ConnectionResolver.new(options[:on], !!options[:db_names])
17
20
  dbs = resolver.configurations.keys
@@ -20,15 +23,23 @@ module Querrel
20
23
  dbs = @connection_resolver.configurations.keys
21
24
  end
22
25
 
23
- threads = []
26
+ pool = StaticPool.new(options[:threads] || 20)
24
27
  dbs.each do |db|
25
- threads << Thread.new do
26
- con_spec = retrieve_connection_spec(db, resolver)
27
- Thread.current[:querrel_con_spec] = con_spec
28
- yield(ConnectedModelFactory)
28
+ pool.enqueue do
29
+ begin
30
+ Thread.current[:querrel_connected_models] = []
31
+ con_spec = retrieve_connection_spec(db, resolver)
32
+ Thread.current[:querrel_con_spec] = con_spec
33
+ yield(ConnectedModelFactory)
34
+ ensure
35
+ Thread.current[:querrel_connected_models].each do |m|
36
+ m.connection_pool.release_connection
37
+ end
38
+ Thread.current[:querrel_connected_models] = nil
39
+ end
29
40
  end
30
41
  end
31
- threads.each(&:join)
42
+ pool.do_your_thang!
32
43
  end
33
44
 
34
45
  def retrieve_connection_spec(db, resolver)
@@ -0,0 +1,26 @@
1
+ require 'thread'
2
+
3
+ module Querrel
4
+ class StaticPool
5
+ def initialize(size)
6
+ @size = size
7
+ @jobs = Queue.new
8
+ end
9
+
10
+ def enqueue(&job)
11
+ @jobs.push(job)
12
+ end
13
+
14
+ def do_your_thang!
15
+ threads = Array.new(@size) do
16
+ Thread.new do
17
+ while job = @jobs.pop(true) rescue nil
18
+ job.call
19
+ end
20
+ end
21
+ end
22
+
23
+ threads.each(&:join)
24
+ end
25
+ end
26
+ end
@@ -1,3 +1,3 @@
1
1
  module Querrel
2
- VERSION = "1.2.0"
2
+ VERSION = "1.3.0"
3
3
  end
@@ -14,6 +14,7 @@ class NonInstanceTest < Querrel::Test
14
14
 
15
15
  assert_equal num_brands * @dbs.length, res.length,
16
16
  "Not returning the correct number of results"
17
+ assert res.all?{ |r| r.is_a?(Brand) }
17
18
  end
18
19
 
19
20
  def test_map
@@ -0,0 +1,23 @@
1
+ require 'thread'
2
+ require_relative 'setup/test_helper'
3
+
4
+ class StaticPoolTest < Querrel::Test
5
+ def test_never_more_than_max_threads
6
+ max_threads = 10
7
+ p = Querrel::StaticPool.new(max_threads)
8
+
9
+ thread_counts = []
10
+ thread_semaphore = Mutex.new
11
+
12
+ 50.times do
13
+ p.enqueue do
14
+ thread_semaphore.synchronize do
15
+ thread_counts << Thread.list.count{ |t| t.status == "run" }
16
+ end
17
+ end
18
+ end
19
+ p.do_your_thang!
20
+
21
+ assert thread_counts.all?{ |c| c <= max_threads }
22
+ end
23
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: querrel
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Campbell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-21 00:00:00.000000000 Z
11
+ date: 2015-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -97,6 +97,7 @@ files:
97
97
  - lib/querrel/connection_resolver.rb
98
98
  - lib/querrel/map_reduce.rb
99
99
  - lib/querrel/querreller.rb
100
+ - lib/querrel/static_pool.rb
100
101
  - lib/querrel/version.rb
101
102
  - querrel.gemspec
102
103
  - test/instance_test.rb
@@ -110,6 +111,7 @@ files:
110
111
  - test/setup/querrel_test_class.rb
111
112
  - test/setup/schema.rb
112
113
  - test/setup/test_helper.rb
114
+ - test/static_pool_test.rb
113
115
  - test/test_test.rb
114
116
  homepage: https://github.com/meritec/querrel
115
117
  licenses:
@@ -147,4 +149,5 @@ test_files:
147
149
  - test/setup/querrel_test_class.rb
148
150
  - test/setup/schema.rb
149
151
  - test/setup/test_helper.rb
152
+ - test/static_pool_test.rb
150
153
  - test/test_test.rb