keymap 0.1.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.
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ -fs
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in keymap.gemspec
4
+ gemspec
5
+
6
+ gem 'rake'
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Robert Buck
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,47 @@
1
+ # Keymap
2
+
3
+ A NoSQL database abstraction similar in design to ActiveRecord but solely
4
+ focussed on NoSQL databases. These are the goals for this project:
5
+
6
+ 1. Provide a simple abstraction layer over NoSQL databases, allowing easy
7
+ migration from one to another without having to rewrite application code.
8
+
9
+ 2. Provide a natural Ruby integration with NoSQL database, allowing direct
10
+ use of Enumerable operations on returned values (implying returned values
11
+ are always lists or hashes, or are manipulated to be represented in these
12
+ forms).
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ gem 'keymap'
19
+
20
+ And then execute:
21
+
22
+ $ bundle
23
+
24
+ Or install it yourself as:
25
+
26
+ $ gem install keymap
27
+
28
+ ## Usage
29
+
30
+ TBD
31
+
32
+ ## Contributing
33
+
34
+ 1. Fork it
35
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
36
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
37
+ 4. Push to the branch (`git push origin my-new-feature`)
38
+ 5. Create new Pull Request
39
+
40
+ ## Writing New Adapters
41
+
42
+ 1. Create a new adapter file named #{database_name}_adapter.rb, placing it
43
+ in the connection adapters directory.
44
+ 2. Add an entry to the list of supported databases in the Rakefile.
45
+ 3. Add test configuration entries in the spec/support/config.yml file.
46
+ 4. Verify all tests pass when the KEYMAP_CONN=#{database_name} environment
47
+ variable is set.
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require 'rubygems'
4
+ require 'rake'
5
+ require 'rake/clean'
6
+ require 'rake/testtask'
7
+ require 'rake/packagetask'
8
+ require 'rdoc/task'
9
+ require 'bundler'
10
+ require 'bundler/gem_tasks'
11
+
12
+ require File.expand_path(File.dirname(__FILE__)) + "/spec/support/config"
13
+ require File.expand_path(File.dirname(__FILE__)) + "/tasks/rspec"
14
+
15
+ Bundler::GemHelper.install_tasks
16
+
17
+ load 'keymap.gemspec'
18
+
19
+ Dir['tasks/**/*.rb'].each { |file| load file }
20
+
21
+ GEM_NAME = "keymap"
22
+ GEM_VERSION = Keymap::VERSION
23
+
24
+ CLEAN.include('pkg')
25
+
26
+ task :default => :spec
27
+
28
+ desc "Push gem packages"
29
+ task :push => :build do
30
+ sh "gem push #{GEM_NAME}*.gem"
31
+ end
32
+
33
+ desc "Installs the gem"
34
+ task :install => :build do
35
+ sh %{gem install pkg/#{GEM_NAME}-#{GEM_VERSION} --no-rdoc --no-ri}
36
+ end
37
+
38
+ task :uninstall do
39
+ sh %{gem uninstall #{GEM_NAME} -x -v #{GEM_VERSION}}
40
+ end
41
+
42
+ desc "Tags git with the latest gem version"
43
+ task :tag do
44
+ sh %{git tag v#{GEM_VERSION}}
45
+ end
46
+
47
+ desc "Release version #{Keymap::VERSION}"
48
+ task :release => [:tag, :push]
49
+
50
+ desc "Provides tasks for each adapter type, e.g. test_redis"
51
+ %w( redis ).each do |adapter|
52
+ Rake::TestTask.new("test_#{adapter}") { |t|
53
+ adapter_short = adapter[/^[a-z0-9]+/]
54
+ t.libs << 'test'
55
+ t.test_files = (Dir.glob("spec/cases/**/*_spec.rb").reject {
56
+ |x| x =~ /\/adapters\//
57
+ } + Dir.glob("spec/functional/adapters/#{adapter_short}/**/*_spec.rb")).sort
58
+
59
+ t.verbose = true
60
+ t.warning = true
61
+ }
62
+
63
+ # Set the connection environment for the adapter
64
+ namespace adapter do
65
+ task :test => "test_#{adapter}"
66
+ task(:env) { ENV['KEYMAPPCONN'] = adapter }
67
+ end
68
+
69
+ # Make sure the adapter test evaluates the env setting task
70
+ task "test_#{adapter}" => "#{adapter}:env"
71
+ end
72
+
73
+ desc "Prints lines of code metrics"
74
+ task :lines do
75
+ lines, codelines, total_lines, total_codelines = 0, 0, 0, 0
76
+
77
+ FileList["lib/keymap/**/*.rb"].each { |file_name|
78
+ next if file_name =~ /vendor/
79
+ f = File.open(file_name)
80
+
81
+ while (line = f.gets)
82
+ lines += 1
83
+ next if line =~ /^\s*$/
84
+ next if line =~ /^\s*#/
85
+ codelines += 1
86
+ end
87
+ puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}"
88
+
89
+ total_lines += lines
90
+ total_codelines += codelines
91
+
92
+ lines, codelines = 0, 0
93
+ }
94
+
95
+ puts "Total: Lines #{total_lines}, LOC #{total_codelines}"
96
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'keymap/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "keymap"
8
+ spec.version = Keymap::VERSION
9
+ spec.authors = ['Robert Buck']
10
+ spec.email = 'buck.robert.j@gmail.com'
11
+ spec.description = %q{ActiveRecord like Key-Value Store API for Ruby}
12
+ spec.summary = %q{Abstracts choosing a key-value store implementation, and provides a common API.}
13
+ spec.homepage = 'https://github.com/rbuck/keymap'
14
+ spec.date = '2012-10-28'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(tasks|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "erubis", "~> 2.7.0"
22
+ spec.add_dependency "redis", "~> 3.0.2"
23
+ spec.add_dependency "activesupport", "~> 3.2.8"
24
+
25
+ %w(rake rdoc simplecov).each { |gem| spec.add_development_dependency gem }
26
+ %w(rspec rspec-core rspec-expectations rspec-mocks).each { |gem| spec.add_development_dependency gem, "~> 2.11.0" }
27
+
28
+ end
@@ -0,0 +1,32 @@
1
+ require 'active_support'
2
+ require 'keymap/version'
3
+
4
+ module Keymap
5
+
6
+ extend ActiveSupport::Autoload
7
+
8
+ eager_autoload do
9
+ autoload :KeymapError, 'keymap/errors'
10
+ autoload :ConnectionNotEstablished, 'keymap/errors'
11
+ autoload :ConnectionAdapters, 'keymap/connection_adapters/abstract_adapter'
12
+ autoload :Base
13
+ end
14
+
15
+ module ConnectionAdapters
16
+ extend ActiveSupport::Autoload
17
+
18
+ eager_autoload do
19
+ autoload :AbstractAdapter
20
+ autoload :ConnectionManagement, 'keymap/connection_adapters/abstract/connection_pool'
21
+ end
22
+ end
23
+
24
+ def env
25
+ @_env ||= ActiveSupport::StringInquirer.new(ENV["KEYMAP_ENV"] || ENV["KEYMAP_ENV"] || "development")
26
+ end
27
+
28
+ def env=(environment)
29
+ @_env = ActiveSupport::StringInquirer.new(environment)
30
+ end
31
+
32
+ end
@@ -0,0 +1,65 @@
1
+ require 'active_support/dependencies'
2
+ require 'active_support/descendants_tracker'
3
+
4
+ require 'active_support/core_ext/class/attribute'
5
+ require 'active_support/core_ext/class/attribute_accessors'
6
+ require 'active_support/core_ext/hash/indifferent_access'
7
+ require 'active_support/core_ext/module/delegation'
8
+
9
+ module Keymap
10
+
11
+ class Base
12
+ ##
13
+ # :singleton-method:
14
+ # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class,
15
+ # which is then passed on to any new database connections made and which can be retrieved on both
16
+ # a class and instance level by calling +logger+.
17
+ cattr_accessor :logger, :instance_writer => false
18
+
19
+ ##
20
+ # :singleton-method:
21
+ # Contains the database configuration - as is typically stored in config/database.yml -
22
+ # as a Hash.
23
+ #
24
+ # For example, the following database.yml...
25
+ #
26
+ # development:
27
+ # adapter: redis
28
+ # database: db/development.sqlite3
29
+ #
30
+ # production:
31
+ # adapter: redis
32
+ # database: db/production.sqlite3
33
+ #
34
+ # ...would result in Keymap::Base.configurations to look like this:
35
+ #
36
+ # {
37
+ # 'development' => {
38
+ # 'adapter' => 'sqlite3',
39
+ # 'database' => 'db/development.sqlite3'
40
+ # },
41
+ # 'production' => {
42
+ # 'adapter' => 'sqlite3',
43
+ # 'database' => 'db/production.sqlite3'
44
+ # }
45
+ # }
46
+ cattr_accessor :configurations, :instance_writer => false
47
+ @@configurations = {}
48
+
49
+ public
50
+
51
+ def initialize(attributes = nil, options = {})
52
+ yield self if block_given?
53
+ #run_callbacks :initialize
54
+ end
55
+
56
+ private
57
+
58
+ extend ActiveSupport::DescendantsTracker
59
+
60
+ include ActiveSupport::Callbacks
61
+
62
+ end
63
+ end
64
+ require 'keymap/connection_adapters/abstract/connection_specification'
65
+ ActiveSupport.run_load_hooks(:keymap, Keymap::Base)
@@ -0,0 +1,451 @@
1
+ require 'thread'
2
+ require 'monitor'
3
+ require 'set'
4
+
5
+ module Keymap
6
+ # Raised when a connection could not be obtained within the connection
7
+ # acquisition timeout period.
8
+ class ConnectionTimeoutError < ConnectionNotEstablished
9
+ end
10
+
11
+ module ConnectionAdapters
12
+ # Connection pool base class for managing connections.
13
+ #
14
+ # == Introduction
15
+ #
16
+ # A connection pool synchronizes thread access to a limited number of
17
+ # connections. The basic idea is that each thread checks out a
18
+ # connection from the pool, uses that connection, and checks the
19
+ # connection back in. ConnectionPool is completely thread-safe, and will
20
+ # ensure that a connection cannot be used by two threads at the same time,
21
+ # as long as ConnectionPool's contract is correctly followed. It will also
22
+ # handle cases in which there are more threads than connections: if all
23
+ # connections have been checked out, and a thread tries to checkout a
24
+ # connection anyway, then ConnectionPool will wait until some other thread
25
+ # has checked in a connection.
26
+ #
27
+ # == Obtaining (checking out) a connection
28
+ #
29
+ # Connections can be obtained and used from a connection pool in several
30
+ # ways:
31
+ #
32
+ # 1. Simply use Keymap::Base.connection. Eventually, when you're done with
33
+ # the connection(s) and wish it to be returned to the pool, you call
34
+ # Keymap::Base.clear_active_connections!.
35
+ # 2. Manually check out a connection from the pool with
36
+ # Keymap::Base.connection_pool.checkout. You are responsible for
37
+ # returning this connection to the pool when finished by calling
38
+ # Keymap::Base.connection_pool.checkin(connection).
39
+ # 3. Use Keymap::Base.connection_pool.with_connection(&block), which
40
+ # obtains a connection, yields it as the sole argument to the block,
41
+ # and returns it to the pool after the block completes.
42
+ #
43
+ # Connections in the pool are actually AbstractAdapter objects (or objects
44
+ # compatible with AbstractAdapter's interface).
45
+ #
46
+ # == Options
47
+ #
48
+ # There are two connection-pooling-related options that you can add to
49
+ # your database connection configuration:
50
+ #
51
+ # * +pool+: number indicating size of connection pool (default 5)
52
+ # * +checkout _timeout+: number of seconds to block and wait for a
53
+ # connection before giving up and raising a timeout error
54
+ # (default 5 seconds).
55
+ class ConnectionPool
56
+ include MonitorMixin
57
+
58
+ attr_accessor :automatic_reconnect
59
+ attr_reader :spec, :connections
60
+
61
+ # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
62
+ # object which describes database connection information (e.g. adapter,
63
+ # host name, username, password, etc), as well as the maximum size for
64
+ # this ConnectionPool.
65
+ #
66
+ # The default ConnectionPool maximum size is 5.
67
+ def initialize(spec)
68
+ super()
69
+
70
+ @spec = spec
71
+
72
+ # The cache of reserved connections mapped to threads
73
+ @reserved_connections = {}
74
+
75
+ @queue = new_cond
76
+ # 'wait_timeout', the backward-compatible key, conflicts with spec key
77
+ # used by mysql2 for something entirely different, checkout_timeout
78
+ # preferred to avoid conflict and allow independent values.
79
+ @timeout = spec.config[:checkout_timeout] || spec.config[:wait_timeout] || 5
80
+
81
+ # default max pool size to 5
82
+ @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
83
+
84
+ @connections = []
85
+ @automatic_reconnect = true
86
+ end
87
+
88
+ # Retrieve the connection associated with the current thread, or call
89
+ # #checkout to obtain one if necessary.
90
+ #
91
+ # #connection can be called any number of times; the connection is
92
+ # held in a hash keyed by the thread id.
93
+ def connection
94
+ synchronize do
95
+ @reserved_connections[current_connection_id] ||= checkout
96
+ end
97
+ end
98
+
99
+ # Is there an open connection that is being used for the current thread?
100
+ def active_connection?
101
+ synchronize do
102
+ @reserved_connections.fetch(current_connection_id) {
103
+ return false
104
+ }.in_use?
105
+ end
106
+ end
107
+
108
+ # Signal that the thread is finished with the current connection.
109
+ # #release_connection releases the connection-thread association
110
+ # and returns the connection to the pool.
111
+ def release_connection(with_id = current_connection_id)
112
+ conn = synchronize { @reserved_connections.delete(with_id) }
113
+ checkin conn if conn
114
+ end
115
+
116
+ # If a connection already exists yield it to the block. If no connection
117
+ # exists checkout a connection, yield it to the block, and checkin the
118
+ # connection when finished.
119
+ def with_connection
120
+ fresh_connection = false
121
+ connection_id = current_connection_id
122
+ fresh_connection = true unless active_connection?
123
+ yield connection
124
+ ensure
125
+ release_connection(connection_id) if fresh_connection
126
+ end
127
+
128
+ # Returns true if a connection has already been opened.
129
+ def connected?
130
+ synchronize { @connections.any? }
131
+ end
132
+
133
+ # Disconnects all connections in the pool, and clears the pool.
134
+ def disconnect!
135
+ synchronize do
136
+ @reserved_connections = {}
137
+ @connections.each do |conn|
138
+ checkin conn
139
+ conn.disconnect!
140
+ end
141
+ @connections = []
142
+ end
143
+ end
144
+
145
+ # Clears the cache which maps classes.
146
+ def clear_reloadable_connections!
147
+ synchronize do
148
+ @reserved_connections = {}
149
+ @connections.each do |conn|
150
+ checkin conn
151
+ conn.disconnect! if conn.requires_reloading?
152
+ end
153
+ @connections.delete_if do |conn|
154
+ conn.requires_reloading?
155
+ end
156
+ end
157
+ end
158
+
159
+ # Verify active connections and remove and disconnect connections
160
+ # associated with stale threads.
161
+ def verify_active_connections! #:nodoc:
162
+ synchronize do
163
+ clear_stale_cached_connections!
164
+ @connections.each do |connection|
165
+ connection.verify!
166
+ end
167
+ end
168
+ end
169
+
170
+ # Return any checked-out connections back to the pool by threads that
171
+ # are no longer alive.
172
+ def clear_stale_cached_connections!
173
+ keys = @reserved_connections.keys - Thread.list.find_all { |t|
174
+ t.alive?
175
+ }.map { |thread| thread.object_id }
176
+ keys.each do |key|
177
+ conn = @reserved_connections[key]
178
+ ActiveSupport::Deprecation.warn(<<-eowarn) if conn.in_use?
179
+ Database connections will not be closed automatically, please close your
180
+ database connection at the end of the thread by calling `close` on your
181
+ connection. For example: ActiveRecord::Base.connection.close
182
+ eowarn
183
+ checkin conn
184
+ @reserved_connections.delete(key)
185
+ end
186
+ end
187
+
188
+ # Check-out a database connection from the pool, indicating that you want
189
+ # to use it. You should call #checkin when you no longer need this.
190
+ #
191
+ # This is done by either returning an existing connection, or by creating
192
+ # a new connection. If the maximum number of connections for this pool has
193
+ # already been reached, but the pool is empty (i.e. they're all being used),
194
+ # then this method will wait until a thread has checked in a connection.
195
+ # The wait time is bounded however: if no connection can be checked out
196
+ # within the timeout specified for this pool, then a ConnectionTimeoutError
197
+ # exception will be raised.
198
+ #
199
+ # Returns: an AbstractAdapter object.
200
+ #
201
+ # Raises:
202
+ # - ConnectionTimeoutError: no connection can be obtained from the pool
203
+ # within the timeout period.
204
+ def checkout
205
+ synchronize do
206
+ waited_time = 0
207
+
208
+ loop do
209
+ conn = @connections.find { |c| c.lease }
210
+
211
+ unless conn
212
+ if @connections.size < @size
213
+ conn = checkout_new_connection
214
+ conn.lease
215
+ end
216
+ end
217
+
218
+ if conn
219
+ checkout_and_verify conn
220
+ return conn
221
+ end
222
+
223
+ if waited_time >= @timeout
224
+ raise ConnectionTimeoutError, "could not obtain a database connection#{" within #@timeout seconds" if @timeout} (waited #{waited_time} seconds). The max pool size is currently #@size; consider increasing it."
225
+ end
226
+
227
+ # Sometimes our wait can end because a connection is available,
228
+ # but another thread can snatch it up first. If timeout hasn't
229
+ # passed but no connection is avail, looks like that happened --
230
+ # loop and wait again, for the time remaining on our timeout.
231
+ before_wait = Time.now
232
+ @queue.wait([@timeout - waited_time, 0].max)
233
+ waited_time += (Time.now - before_wait)
234
+
235
+ # Will go away in Rails 4, when we don't clean up
236
+ # after leaked connections automatically anymore. Right now, clean
237
+ # up after we've returned from a 'wait' if it looks like it's
238
+ # needed, then loop and try again.
239
+ if active_connections.size >= @connections.size
240
+ clear_stale_cached_connections!
241
+ end
242
+ end
243
+ end
244
+ end
245
+
246
+ # Check-in a database connection back into the pool, indicating that you
247
+ # no longer need this connection.
248
+ #
249
+ # +conn+: an AbstractAdapter object, which was obtained by earlier by
250
+ # calling +checkout+ on this pool.
251
+ def checkin(conn)
252
+ synchronize do
253
+ conn.run_callbacks :checkin do
254
+ conn.expire
255
+ @queue.signal
256
+ end
257
+
258
+ release conn
259
+ end
260
+ end
261
+
262
+ private
263
+
264
+ def release(conn)
265
+ synchronize do
266
+ if @reserved_connections[current_connection_id] == conn
267
+ thread_id = current_connection_id
268
+ else
269
+ thread_id = @reserved_connections.keys.find { |k|
270
+ @reserved_connections[k] == conn
271
+ }
272
+ end
273
+ @reserved_connections.delete thread_id if thread_id
274
+ end
275
+ end
276
+
277
+ def new_connection
278
+ Keymap::Base.send(spec.adapter_method, spec.config)
279
+ end
280
+
281
+ def current_connection_id #:nodoc:
282
+ Keymap::Base.connection_id ||= Thread.current.object_id
283
+ end
284
+
285
+ def checkout_new_connection
286
+ raise ConnectionNotEstablished unless @automatic_reconnect
287
+
288
+ c = new_connection
289
+ c.pool = self
290
+ @connections << c
291
+ c
292
+ end
293
+
294
+ def checkout_and_verify(c)
295
+ c.run_callbacks :checkout do
296
+ c.verify!
297
+ end
298
+ c
299
+ end
300
+
301
+ def active_connections
302
+ @connections.find_all { |c| c.in_use? }
303
+ end
304
+ end
305
+
306
+ # ConnectionHandler is a collection of ConnectionPool objects. It is used
307
+ # for keeping separate connection pools for Active Record models that connect
308
+ # to different databases.
309
+ #
310
+ # For example, suppose that you have 5 models, with the following hierarchy:
311
+ #
312
+ # |
313
+ # +-- Book
314
+ # | |
315
+ # | +-- ScaryBook
316
+ # | +-- GoodBook
317
+ # +-- Author
318
+ # +-- BankAccount
319
+ #
320
+ # Suppose that Book is to connect to a separate database (i.e. one other
321
+ # than the default database). Then Book, ScaryBook and GoodBook will all use
322
+ # the same connection pool. Likewise, Author and BankAccount will use the
323
+ # same connection pool. However, the connection pool used by Author/BankAccount
324
+ # is not the same as the one used by Book/ScaryBook/GoodBook.
325
+ #
326
+ # Normally there is only a single ConnectionHandler instance, accessible via
327
+ # ActiveRecord::Base.connection_handler. Active Record models use this to
328
+ # determine that connection pool that they should use.
329
+ class ConnectionHandler
330
+ attr_reader :connection_pools
331
+
332
+ def initialize(pools = {})
333
+ @connection_pools = pools
334
+ @class_to_pool = {}
335
+ end
336
+
337
+ def establish_connection(name, spec)
338
+ @connection_pools[spec] ||= ConnectionAdapters::ConnectionPool.new(spec)
339
+ @class_to_pool[name] = @connection_pools[spec]
340
+ end
341
+
342
+ # Returns true if there are any active connections among the connection
343
+ # pools that the ConnectionHandler is managing.
344
+ def active_connections?
345
+ connection_pools.values.any? { |pool| pool.active_connection? }
346
+ end
347
+
348
+ # Returns any connections in use by the current thread back to the pool.
349
+ def clear_active_connections!
350
+ @connection_pools.each_value { |pool| pool.release_connection }
351
+ end
352
+
353
+ # Clears the cache which maps classes.
354
+ def clear_reloadable_connections!
355
+ @connection_pools.each_value { |pool| pool.clear_reloadable_connections! }
356
+ end
357
+
358
+ def clear_all_connections!
359
+ @connection_pools.each_value { |pool| pool.disconnect! }
360
+ end
361
+
362
+ # Verify active connections.
363
+ def verify_active_connections! #:nodoc:
364
+ @connection_pools.each_value { |pool| pool.verify_active_connections! }
365
+ end
366
+
367
+ # Locate the connection of the nearest super class. This can be an
368
+ # active or defined connection: if it is the latter, it will be
369
+ # opened and set as the active connection for the class it was defined
370
+ # for (not necessarily the current class).
371
+ def retrieve_connection(klass) #:nodoc:
372
+ pool = retrieve_connection_pool(klass)
373
+ (pool && pool.connection) or raise ConnectionNotEstablished
374
+ end
375
+
376
+ # Returns true if a connection that's accessible to this class has
377
+ # already been opened.
378
+ def connected?(klass)
379
+ conn = retrieve_connection_pool(klass)
380
+ conn && conn.connected?
381
+ end
382
+
383
+ # Remove the connection for this class. This will close the active
384
+ # connection and the defined connection (if they exist). The result
385
+ # can be used as an argument for establish_connection, for easily
386
+ # re-establishing the connection.
387
+ def remove_connection(klass)
388
+ pool = @class_to_pool.delete(klass.name)
389
+ return nil unless pool
390
+
391
+ @connection_pools.delete pool.spec
392
+ pool.automatic_reconnect = false
393
+ pool.disconnect!
394
+ pool.spec.config
395
+ end
396
+
397
+ def retrieve_connection_pool(klass)
398
+ pool = @class_to_pool[klass.name]
399
+ return pool if pool
400
+ return nil if Keymap::Base == klass
401
+ retrieve_connection_pool klass.superclass
402
+ end
403
+ end
404
+
405
+ class ConnectionManagement
406
+ class Proxy # :nodoc:
407
+ attr_reader :body, :testing
408
+
409
+ def initialize(body, testing = false)
410
+ @body = body
411
+ @testing = testing
412
+ end
413
+
414
+ def method_missing(method_sym, *arguments, &block)
415
+ @body.send(method_sym, *arguments, &block)
416
+ end
417
+
418
+ def respond_to?(method_sym, include_private = false)
419
+ @body.respond_to?(method_sym)
420
+ end
421
+
422
+ def each(&block)
423
+ body.each(&block)
424
+ end
425
+
426
+ def close
427
+ body.close if body.respond_to?(:close)
428
+
429
+ # Don't return connection (and perform implicit rollback) if
430
+ # this request is a part of integration test
431
+ Keymap::Base.clear_active_connections! unless testing
432
+ end
433
+ end
434
+
435
+ def initialize(app)
436
+ @app = app
437
+ end
438
+
439
+ def call(env)
440
+ testing = env.key?('rack.test')
441
+
442
+ status, headers, body = @app.call(env)
443
+
444
+ [status, headers, Proxy.new(body, testing)]
445
+ rescue
446
+ Keymap::Base.clear_active_connections! unless testing
447
+ raise
448
+ end
449
+ end
450
+ end
451
+ end