authorize 0.0.1

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.
Files changed (124) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +42 -0
  4. data/LICENSE +20 -0
  5. data/README +155 -0
  6. data/Rakefile +25 -0
  7. data/TODO.txt +9 -0
  8. data/authorize.gemspec +25 -0
  9. data/generators/authorize/USAGE +8 -0
  10. data/generators/authorize/authorize_generator.rb +7 -0
  11. data/generators/authorize/templates/migrate/create_authorizations.rb +26 -0
  12. data/install.rb +1 -0
  13. data/lib/authorize.rb +2 -0
  14. data/lib/authorize/action_controller.rb +59 -0
  15. data/lib/authorize/action_view.rb +4 -0
  16. data/lib/authorize/active_record.rb +37 -0
  17. data/lib/authorize/bitmask.rb +84 -0
  18. data/lib/authorize/exceptions.rb +30 -0
  19. data/lib/authorize/graph.rb +4 -0
  20. data/lib/authorize/graph/directed_acyclic_graph.rb +10 -0
  21. data/lib/authorize/graph/directed_acyclic_graph_reverse_traverser.rb +27 -0
  22. data/lib/authorize/graph/directed_acyclic_graph_traverser.rb +30 -0
  23. data/lib/authorize/graph/directed_graph.rb +27 -0
  24. data/lib/authorize/graph/edge.rb +58 -0
  25. data/lib/authorize/graph/factory.rb +39 -0
  26. data/lib/authorize/graph/fixtures.rb +33 -0
  27. data/lib/authorize/graph/graph.rb +55 -0
  28. data/lib/authorize/graph/traverser.rb +89 -0
  29. data/lib/authorize/graph/undirected_graph.rb +14 -0
  30. data/lib/authorize/graph/vertex.rb +53 -0
  31. data/lib/authorize/permission.rb +97 -0
  32. data/lib/authorize/redis.rb +2 -0
  33. data/lib/authorize/redis/array.rb +36 -0
  34. data/lib/authorize/redis/base.rb +165 -0
  35. data/lib/authorize/redis/connection_manager.rb +88 -0
  36. data/lib/authorize/redis/connection_specification.rb +16 -0
  37. data/lib/authorize/redis/factory.rb +64 -0
  38. data/lib/authorize/redis/fixtures.rb +22 -0
  39. data/lib/authorize/redis/hash.rb +34 -0
  40. data/lib/authorize/redis/model_reference.rb +21 -0
  41. data/lib/authorize/redis/model_set.rb +19 -0
  42. data/lib/authorize/redis/set.rb +42 -0
  43. data/lib/authorize/redis/string.rb +17 -0
  44. data/lib/authorize/resource.rb +4 -0
  45. data/lib/authorize/resource_pool.rb +87 -0
  46. data/lib/authorize/role.rb +115 -0
  47. data/lib/authorize/test_helper.rb +42 -0
  48. data/lib/authorize/trustee.rb +4 -0
  49. data/lib/authorize/version.rb +3 -0
  50. data/rails/init.rb +5 -0
  51. data/tasks/authorize_tasks.rake +4 -0
  52. data/test/Rakefile +7 -0
  53. data/test/app/controllers/application_controller.rb +5 -0
  54. data/test/app/controllers/thingy_controller.rb +11 -0
  55. data/test/app/controllers/widgets_controller.rb +2 -0
  56. data/test/app/models/public.rb +14 -0
  57. data/test/app/models/user.rb +8 -0
  58. data/test/app/models/widget.rb +7 -0
  59. data/test/config/boot.rb +109 -0
  60. data/test/config/database.yml +25 -0
  61. data/test/config/environment.rb +28 -0
  62. data/test/config/environments/development.rb +4 -0
  63. data/test/config/environments/test.rb +0 -0
  64. data/test/config/initializers/mask.rb +1 -0
  65. data/test/config/initializers/redis.rb +8 -0
  66. data/test/config/routes.rb +5 -0
  67. data/test/db/.gitignore +1 -0
  68. data/test/db/schema.rb +26 -0
  69. data/test/log/.gitignore +2 -0
  70. data/test/public/javascripts/application.js +2 -0
  71. data/test/public/javascripts/controls.js +963 -0
  72. data/test/public/javascripts/dragdrop.js +972 -0
  73. data/test/public/javascripts/effects.js +1120 -0
  74. data/test/public/javascripts/prototype.js +4225 -0
  75. data/test/script/about +3 -0
  76. data/test/script/console +3 -0
  77. data/test/script/dbconsole +3 -0
  78. data/test/script/destroy +3 -0
  79. data/test/script/generate +3 -0
  80. data/test/script/performance/benchmarker +3 -0
  81. data/test/script/performance/profiler +3 -0
  82. data/test/script/performance/request +3 -0
  83. data/test/script/plugin +3 -0
  84. data/test/script/process/inspector +3 -0
  85. data/test/script/process/reaper +3 -0
  86. data/test/script/process/spawner +3 -0
  87. data/test/script/runner +3 -0
  88. data/test/script/server +3 -0
  89. data/test/test/fixtures/authorize/role_graph.yml +11 -0
  90. data/test/test/fixtures/permissions.yml +27 -0
  91. data/test/test/fixtures/redis/redis.yml +8 -0
  92. data/test/test/fixtures/redis/role_graph.yml +29 -0
  93. data/test/test/fixtures/roles.yml +28 -0
  94. data/test/test/fixtures/users.yml +12 -0
  95. data/test/test/fixtures/widgets.yml +12 -0
  96. data/test/test/functional/controller_class_test.rb +36 -0
  97. data/test/test/functional/controller_test.rb +46 -0
  98. data/test/test/test_helper.rb +35 -0
  99. data/test/test/unit/bitmask_test.rb +112 -0
  100. data/test/test/unit/fixture_test.rb +59 -0
  101. data/test/test/unit/graph_directed_acyclic_graph_reverse_traverser_test.rb +43 -0
  102. data/test/test/unit/graph_directed_acyclic_graph_traverser_test.rb +57 -0
  103. data/test/test/unit/graph_directed_graph_test.rb +66 -0
  104. data/test/test/unit/graph_edge_test.rb +53 -0
  105. data/test/test/unit/graph_graph_test.rb +50 -0
  106. data/test/test/unit/graph_traverser_test.rb +43 -0
  107. data/test/test/unit/graph_vertex_test.rb +57 -0
  108. data/test/test/unit/permission_test.rb +123 -0
  109. data/test/test/unit/redis_array_test.rb +60 -0
  110. data/test/test/unit/redis_connection_manager_test.rb +54 -0
  111. data/test/test/unit/redis_factory_test.rb +85 -0
  112. data/test/test/unit/redis_fixture_test.rb +18 -0
  113. data/test/test/unit/redis_hash_test.rb +43 -0
  114. data/test/test/unit/redis_model_reference_test.rb +39 -0
  115. data/test/test/unit/redis_set_test.rb +68 -0
  116. data/test/test/unit/redis_string_test.rb +25 -0
  117. data/test/test/unit/redis_test.rb +121 -0
  118. data/test/test/unit/resource_pool_test.rb +93 -0
  119. data/test/test/unit/resource_test.rb +33 -0
  120. data/test/test/unit/role_test.rb +143 -0
  121. data/test/test/unit/trustee_test.rb +35 -0
  122. data/test/tmp/.gitignore +2 -0
  123. data/uninstall.rb +1 -0
  124. metadata +319 -0
@@ -0,0 +1,14 @@
1
+ require 'authorize/redis'
2
+
3
+ module Authorize
4
+ module Graph
5
+ class UndirectedGraph < Graph
6
+ # Join two vertices symmetrically so that they become adjacent. Graphs built uniquely with
7
+ # this method will be undirected.
8
+ def join(id, v0, v1, *args)
9
+ edge_id = id || subordinate_key("_edges", true)
10
+ !!(edge(edge_id + "-01", v0, v1, *args) && edge(edge_id + "-10", v1, v0, *args))
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,53 @@
1
+ module Authorize
2
+ module Graph
3
+ class Vertex < Redis::Hash
4
+ def self.exists?(id)
5
+ super(subordinate_key(id, '_'))
6
+ end
7
+
8
+ def self.load_all(namespace = name)
9
+ redis_glob = subordinate_key(namespace, '*', '_')
10
+ re = Regexp.new(subordinate_key(namespace, ".+(?=#{NAMESPACE_SEPARATOR})"))
11
+ keys = db.keys(redis_glob)
12
+ keys = keys.map{|m| m.slice(re)}
13
+ keys.map{|id| load(id)}
14
+ end
15
+
16
+ def initialize(properties = {})
17
+ super()
18
+ # Because a degenerate vertex can have neither properties nor edges, we must store a marker to indicate existence
19
+ self.class.db.set(subordinate_key('_'), nil)
20
+ merge(properties) if properties.any?
21
+ end
22
+
23
+ def destroy
24
+ outbound_edges.each{|e| e.destroy}
25
+ outbound_edges.destroy
26
+ inbound_edges.each{|e| e.destroy}
27
+ inbound_edges.destroy
28
+ self.class.db.del(subordinate_key('_'))
29
+ super
30
+ end
31
+
32
+ def adjacencies
33
+ outbound_edges.map(&:to)
34
+ end
35
+ alias neighbors adjacencies
36
+
37
+ def outbound_edges
38
+ @edges || Redis::ModelSet.new(subordinate_key('edge_ids'), Edge)
39
+ end
40
+ alias edges outbound_edges
41
+
42
+ # This index is required for efficient backlinking, such as when deleting a vertex.
43
+ def inbound_edges
44
+ @inbound_edges || Redis::ModelSet.new(subordinate_key('inbound_edge_ids'), Edge)
45
+ end
46
+
47
+ # Visit this vertex via the given edge
48
+ def visit(edge, &block)
49
+ yield self
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,97 @@
1
+ require 'authorize/bitmask'
2
+
3
+ class Authorize::Permission < ActiveRecord::Base
4
+ class Mask < Authorize::Bitmask;end
5
+
6
+ set_table_name 'authorize_permissions'
7
+ # This is of questionable value given the specific implementation of mask attribute methods. It also requires
8
+ # table inspection, so we skip it if the table does not yet exist.
9
+ cache_attributes('mask') if table_exists?
10
+
11
+ belongs_to :_resource, :polymorphic => true, :foreign_type => 'resource_type', :foreign_key => 'resource_id'
12
+ belongs_to :role, :class_name => "Authorize::Role"
13
+ validates_presence_of :role
14
+ validates_presence_of :resource
15
+
16
+ before_save :set_mandatory_list_mode
17
+
18
+ # Returns the explicit authorizations over a subject. The resource can be any one of the following
19
+ # Object global permissions are returned
20
+ # <Resource Class> global and class permissions are returned
21
+ # <Resource Instance> global, class and instance permissions are returned
22
+ # This exists to simplify finding and creating global and class permissions. For resource instance
23
+ # permissions, use the standard Rails association (#permissions) created for authorizable resources.
24
+ named_scope :for, lambda {|resource|
25
+ resource_conditions = if (resource == Object) then
26
+ {:resource_id => nil, :resource_type => nil}
27
+ elsif resource.is_a?(Class) then
28
+ {:resource_id => nil, :resource_type => resource.to_s}
29
+ else
30
+ {:resource_id => resource.id, :resource_type => resource.class.to_s}
31
+ end
32
+ {:conditions => resource_conditions}
33
+ }
34
+ # Returns the effective permissions over a resource.
35
+ named_scope :over, lambda {|resource|
36
+ resource_conditions = if (resource == Object) then
37
+ {:resource_id => nil, :resource_type => nil}
38
+ elsif resource.is_a?(Class) then
39
+ c1 = sanitize_sql_hash_for_conditions(:resource_type => nil)
40
+ c2 = sanitize_sql_hash_for_conditions(:resource_type => resource.base_class.name, :resource_id => nil)
41
+ "#{c1} OR (#{c2})"
42
+ else
43
+ c1 = sanitize_sql_hash_for_conditions(:resource_type => nil)
44
+ c2 = sanitize_sql_hash_for_conditions(:resource_type => resource.class.base_class.name)
45
+ c3 = sanitize_sql_hash_for_conditions(:resource_id => resource.quoted_id)
46
+ c4 = sanitize_sql_hash_for_conditions(:resource_id => nil)
47
+ "#{c1} OR (#{c2} AND (#{c3} OR #{c4}))"
48
+ end
49
+ {:conditions => resource_conditions}
50
+ }
51
+ named_scope :to_do, lambda {|mask| {:conditions =>"mask & #{mask.to_i} = #{mask.to_i}"}}
52
+ named_scope :as, lambda {|roles| {:conditions => {:role_id => roles.map(&:id)}}}
53
+ named_scope :global, :conditions => {:resource_type => nil, :resource_id => nil}
54
+
55
+ # Determine if the aggregate mask includes the requested modes.
56
+ def self.permit?(requested_modes)
57
+ requested_modes.subset?(aggregate_mask)
58
+ end
59
+
60
+ # Find the aggregate permission mask for the current scope
61
+ # This calculation could be more effectively performed at the database using an aggregate function. For
62
+ # MySQL, a bit_or function exists. For SQLite3, it is necessary to code an extension. For an example,
63
+ # see: http://snippets.dzone.com/posts/show/3717
64
+ def self.aggregate_mask
65
+ Mask.new(all.inject(Set.new){|memo, p| memo | p.mask})
66
+ end
67
+
68
+ # Because the list mode is always assumed to be set for performance, we expose that assumption explicitly.
69
+ def set_mandatory_list_mode
70
+ self['mask'] |= Mask.name_values[:list]
71
+ @attributes_cache.delete('mask')
72
+ end
73
+
74
+ # Virtual attribute that expands the common belongs_to association with a three-level hierarchy
75
+ def resource
76
+ return Object unless resource_type
77
+ return resource_type.constantize unless resource_id
78
+ return _resource
79
+ end
80
+
81
+ def resource=(res)
82
+ return self._resource = res unless res.kind_of?(Class)
83
+ self.resource_id = nil
84
+ return self[:resource_type] = nil if res == Object
85
+ return self[:resource_type] = res.to_s
86
+ end
87
+
88
+ def mask(reload = false)
89
+ cached = @attributes_cache['mask'] # undocumented hash of cache nicely invalidated by write_attribute
90
+ return cached if cached && !reload
91
+ @attributes_cache['mask'] = Mask.new(read_attribute('mask')) # Ensure we always return a Mask instance
92
+ end
93
+
94
+ def to_s
95
+ "#{role} over #{resource} (#{mask})"
96
+ end
97
+ end
@@ -0,0 +1,2 @@
1
+ require 'redis'
2
+ require 'authorize/redis/base'
@@ -0,0 +1,36 @@
1
+ module Authorize
2
+ module Redis
3
+ class Array < Base
4
+ undef to_a # In older versions of Ruby, Object#to_a is invoked and #method_missing is never called.
5
+
6
+ def valid?
7
+ %w(none list).include?(db.type(id))
8
+ end
9
+
10
+ def [](index)
11
+ if index.respond_to?(:first)
12
+ db.lrange(id, index.first, index.last)
13
+ else
14
+ db.lindex(id, index)
15
+ end
16
+ end
17
+
18
+ def []=(index, v)
19
+ db.lset(id, index, v)
20
+ end
21
+
22
+ def push(v)
23
+ db.rpush(id, v)
24
+ end
25
+ alias << push
26
+
27
+ def pop
28
+ db.rpop(id)
29
+ end
30
+
31
+ def __getobj__
32
+ db.lrange(id, 0, -1)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,165 @@
1
+ module Authorize
2
+ module Redis
3
+ # The key feature of this module is that it presents a coherent view of the database in memory. For
4
+ # each database entry, at most one in-memory Ruby object will exist, and all state for the object will
5
+ # be atomically persisted to the database. This behavior introduces the following constraints:
6
+ # 1. The database is viewed through an identity map (http://en.wikipedia.org/wiki/Identity_map) to
7
+ # ensure in-thread coherency. Consequently, the record's key must be known prior to initialization,
8
+ # allowing new objects to be instantiated only if no previously instantiated object with that key is
9
+ # already in memory.
10
+ # 2. In order to allow Redis::Base#initialize to set values (which are atomically persisted), the id must
11
+ # be available at the _start_ of initialization. This is accomplished by overriding Redis.new and
12
+ # assigning the id immediately after allocation.
13
+ # TODO: YAML serialization (http://groups.google.com/group/comp.lang.ruby/browse_thread/thread/c855253c9d8f482e)
14
+ class Base
15
+ NAMESPACE_SEPARATOR = '::'
16
+ @base = true
17
+ class << self
18
+ attr_writer :logger
19
+ attr_writer :connection_specification
20
+
21
+ # Should this class establish a connection instead of relying on a superclass' connection?
22
+ def connection_base?
23
+ @base || @connection_specification
24
+ end
25
+
26
+ # Search up the inheritance chain for a manager unless a connection is specified here.
27
+ def connection_manager
28
+ @manager ||= (connection_base? ? Redis::ConnectionManager.new(@connection_specification) : superclass.connection_manager)
29
+ end
30
+
31
+ def connection
32
+ connection_manager.connection
33
+ end
34
+ alias db connection
35
+ end
36
+
37
+ def self.logger
38
+ @logger ||= (@base ? nil : superclass.logger)
39
+ end
40
+
41
+ def self.subordinate_key(*keys)
42
+ keys.compact.join(NAMESPACE_SEPARATOR)
43
+ end
44
+
45
+ def self.next_counter(key)
46
+ db.incr(key)
47
+ end
48
+
49
+ def self.generate_key
50
+ subordinate_key(name, next_counter(name))
51
+ end
52
+
53
+ def self.index
54
+ @index ||= ::Hash.new
55
+ end
56
+
57
+ def self.exists?(id)
58
+ db.exists(id)
59
+ end
60
+
61
+ # Load all model objects in the given namespace
62
+ def self.load_all(namespace = name)
63
+ redis_glob = subordinate_key(namespace, '*')
64
+ re = Regexp.new(subordinate_key(namespace, ".+(?=#{NAMESPACE_SEPARATOR})"))
65
+ db.keys(redis_glob).map{|m| m.slice(re)}.map{|id| load(id)}
66
+ end
67
+
68
+ def self.new(id = nil, *args, &block)
69
+ id ||= generate_key
70
+ index[id] = allocate.tap do |o|
71
+ o.instance_variable_set(:@id, id)
72
+ o.send(:initialize, *args, &block)
73
+ end
74
+ end
75
+
76
+ def self.load(id)
77
+ index[id] ||= allocate.tap do |o|
78
+ o.instance_variable_set(:@id, id)
79
+ o.send(:reload)
80
+ end
81
+ end
82
+ def self._load(id);load(id);end
83
+
84
+ attr_reader :id
85
+ alias to_s id
86
+
87
+ def logger
88
+ self.class.logger
89
+ end
90
+
91
+ def db
92
+ self.class.db
93
+ end
94
+
95
+ def eql?(other)
96
+ id.eql?(other.id) && other.is_a?(self.class)
97
+ end
98
+
99
+ def hash
100
+ id.hash
101
+ end
102
+
103
+ def ==(other)
104
+ id.eql?(other.id) && other.is_a?(self.class)
105
+ end
106
+
107
+ # Note that requesting a counter value "steals" from the class counter.
108
+ def subordinate_key(name, counter = false)
109
+ k = self.class.subordinate_key(id, name)
110
+ counter ? self.class.subordinate_key(k, self.class.next_counter(k)) : k
111
+ end
112
+
113
+ # This hook restores a re-instantiated object that has previously been initialized and then persisted.
114
+ # Non-idempotent operations should be used with great care.
115
+ def reload;end
116
+
117
+ def _dump(depth = nil)
118
+ id
119
+ end
120
+
121
+ # Emit this Redis object with a a magic type and simple scalar identifier. The (poorly documented) "type id" format
122
+ # allows for a succinct one-line YAML expression for a Redis instance (no indented attributes hash required) which in
123
+ # turn simplifies automatic YAMLification of collections of Redis objects. Arguably, it's more readable as well.
124
+ def to_yaml(opts = {})
125
+ YAML.quick_emit(self.id, opts) {|out| out.scalar("tag:hapgoods.com,2010-08-11:#{self.class.name}", id)}
126
+ end
127
+
128
+ def destroy
129
+ db.del(id) # This operation will remove all native Redis types (String, Hash, List, Set, etc.) in one shot.
130
+ self.class.index.delete(id)
131
+ freeze
132
+ end
133
+
134
+ def exists?
135
+ self.class.exists?(id)
136
+ end
137
+
138
+ def valid?
139
+ raise "Abstract class requires implementation"
140
+ end
141
+
142
+ # Methods that don't change the state of the object can safely delegate to a Ruby proxy object
143
+ def __getobj__
144
+ raise "Abstract class requires implementation"
145
+ end
146
+
147
+ def method_missing(m, *args, &block)
148
+ proxy = __getobj__ # Performance tweak
149
+ return super unless proxy.respond_to?(m) # If there is going to be an explosion, let superclass handle it.
150
+ proxy.freeze.__send__(m, *args, &block) # Ensure no state can be changed and send the method on its way.
151
+ end
152
+
153
+ def respond_to?(m, include_private = false)
154
+ return true if super
155
+ __getobj__.respond_to?(m, include_private)
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+ YAML.add_domain_type("hapgoods.com,2010-08-11", "") do |type, val|
162
+ md = /tag:(.*),([^:]*):((?:\w+)(?:::\w+)*)/.match(type)
163
+ domain, version, klass = *md[1..3]
164
+ klass.constantize.load(val)
165
+ end
@@ -0,0 +1,88 @@
1
+ require 'monitor'
2
+ require 'set'
3
+
4
+ module Authorize
5
+ module Redis
6
+ # This class arbitrates access to a Redis server ensuring that threads don't concurrently access the same connection
7
+ # http://yehudakatz.com/2010/08/14/threads-in-ruby-enough-already/
8
+ # http://blog.headius.com/2008/08/qa-what-thread-safe-rails-means.html
9
+ # Inspired by the ConnectionPool class in Rails 2.3
10
+ # Notes on thread-safety: Because instances of this class are expected to be shared
11
+ # across threads, access to all non-local variables and "constants" need to be synchronized.
12
+ # We assume that Hash read/assign operations are thread-safe. We also require that the
13
+ # value returned by #current_connection_id not be shared across threads.
14
+ class ConnectionManager
15
+ class ConnectionError < RuntimeError; end
16
+
17
+ attr_reader :pool
18
+
19
+ # Creates a new ConnectionPool object. +specification+ is a ConnectionSpecification
20
+ # object which describes database connection parameters
21
+ def initialize(specification, options = {})
22
+ @options = {:size => 5}.merge(options)
23
+ @pool = ResourcePool.new(@options[:size], lambda {specification.connect!})
24
+ @connection_map = {} # Connections mapped to threads
25
+ @mutex = Monitor.new
26
+ end
27
+
28
+ # Retrieve the connection associated with the current thread, or checkout one from the pool as required.
29
+ # #connection can be called any number of times; the connection is held in a hash with a thread-specific key.
30
+ def acquire_connection
31
+ @connection_map[current_connection_id] ||= @pool.checkout(10)
32
+ end
33
+ alias connection acquire_connection
34
+
35
+ # Signal that the thread is finished with the current connection.
36
+ # #release_connection releases the connection-thread association
37
+ # and returns the connection to the pool.
38
+ def release_connection
39
+ c = @connection_map.delete(current_connection_id)
40
+ @pool.checkin(c) if c
41
+ end
42
+
43
+ # Checks out a connection from the pool, yields it to a block and checks it back into the pool when the block finishes.
44
+ def with_connection
45
+ c = @pool.checkout
46
+ yield c
47
+ ensure
48
+ @pool.checkin(c)
49
+ end
50
+
51
+ # Identifies connections in the death grip of defunct threads, removes them from the map and checks them back into the pool
52
+ # Because this method operates across connections for multiple threads (not just the current thread), concurrent execution
53
+ # needs to be synchronized to be thread-safe.
54
+ def recover_unused_connections
55
+ tids = Thread.list.select{|t| t.alive?}.map(&:object_id)
56
+ @mutex.synchronize do
57
+ cids = @connection_map.keys
58
+ (cids - tids).each do |sid|
59
+ c = @connection_map.delete(sid)
60
+ @pool.checkin(c)
61
+ end
62
+ end
63
+ end
64
+
65
+ # Expire stale connections
66
+ def expire_stale_connections!
67
+ @pool.expire do |connection, reserved_flag|
68
+ !connection.client.connected?
69
+ end
70
+ end
71
+
72
+ # Revert to a freshly initialized state
73
+ def reset!
74
+ @mutex.synchronize do
75
+ @pool.clear!
76
+ @connection_map.clear
77
+ end
78
+ self
79
+ end
80
+
81
+ private
82
+ # In order to guarantee thread-safety, this value must never be shared across threads.
83
+ def current_connection_id #:nodoc:
84
+ Thread.current.object_id
85
+ end
86
+ end
87
+ end
88
+ end