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,159 @@
1
+ require 'uri'
2
+
3
+ module Keymap
4
+ class Base
5
+ class ConnectionSpecification #:nodoc:
6
+ attr_reader :config, :adapter_method
7
+
8
+ def initialize (config, adapter_method)
9
+ @config, @adapter_method = config, adapter_method
10
+ end
11
+
12
+ ##
13
+ # Builds a ConnectionSpecification from user input
14
+ class Resolver # :nodoc:
15
+
16
+ attr_reader :config, :configurations
17
+
18
+ def initialize(config, configurations)
19
+ @config = config
20
+ @configurations = configurations
21
+ end
22
+
23
+ def spec
24
+ case config
25
+ when nil
26
+ raise AdapterNotSpecified unless defined?(Keymap.env)
27
+ resolve_string_connection Keymap.env
28
+ when Symbol, String
29
+ resolve_string_connection config.to_s
30
+ when Hash
31
+ resolve_hash_connection config
32
+ else
33
+ # type code here
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def resolve_string_connection(spec) # :nodoc:
40
+ hash = configurations.fetch(spec) do |k|
41
+ connection_url_to_hash(k)
42
+ end
43
+
44
+ raise(AdapterNotSpecified, "#{spec} database is not configured") unless hash
45
+
46
+ resolve_hash_connection hash
47
+ end
48
+
49
+ def resolve_hash_connection(spec) # :nodoc:
50
+ spec = spec.symbolize_keys
51
+
52
+ raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)
53
+
54
+ begin
55
+ require "keymap/connection_adapters/#{spec[:adapter]}_adapter"
56
+ rescue LoadError => e
57
+ raise LoadError, "Please install the #{spec[:adapter]} adapter: `gem install keymap-#{spec[:adapter]}-adapter` (#{e.message})", e.backtrace
58
+ end
59
+
60
+ adapter_method = "#{spec[:adapter]}_connection"
61
+
62
+ ConnectionSpecification.new(spec, adapter_method)
63
+ end
64
+
65
+ def connection_url_to_hash(url) # :nodoc:
66
+ config = URI.parse url
67
+ adapter = config.scheme
68
+ spec = {:adapter => adapter,
69
+ :username => config.user,
70
+ :password => config.password,
71
+ :port => config.port,
72
+ :database => config.path.sub(%r{^/}, ""),
73
+ :host => config.host}
74
+ spec.reject! { |_, value| !value }
75
+ if config.query
76
+ options = Hash[config.query.split("&").map { |pair| pair.split("=") }].symbolize_keys
77
+ spec.merge!(options)
78
+ end
79
+ spec
80
+ end
81
+
82
+ end
83
+ end
84
+
85
+ ##
86
+ # :singleton-method:
87
+ # The connection handler
88
+ class_attribute :connection_handler, :instance_writer => false
89
+ self.connection_handler = Keymap::ConnectionAdapters::ConnectionHandler.new
90
+
91
+ def connection
92
+ self.class.connection
93
+ end
94
+
95
+ def self.establish_connection(spec = ENV["DATABASE_URL"])
96
+ resolver = Keymap::Base::ConnectionSpecification::Resolver.new spec, configurations
97
+ spec = resolver.spec
98
+
99
+ unless respond_to?(spec.adapter_method)
100
+ raise AdapterNotFound, "database configuration specifies nonexistent #{spec.config[:adapter]} adapter"
101
+ end
102
+
103
+ remove_connection
104
+ connection_handler.establish_connection name, spec
105
+ end
106
+
107
+ class << self
108
+ # Returns the connection currently associated with the class. This can
109
+ # also be used to "borrow" the connection to do database work unrelated
110
+ # to any of the specific Active Records.
111
+ def connection
112
+ retrieve_connection
113
+ end
114
+
115
+ def connection_id
116
+ Thread.current['Keymap::Base.connection_id']
117
+ end
118
+
119
+ def connection_id=(connection_id)
120
+ Thread.current['Keymap::Base.connection_id'] = connection_id
121
+ end
122
+
123
+ # Returns the configuration of the associated connection as a hash:
124
+ #
125
+ # ActiveRecord::Base.connection_config
126
+ # # => {:pool=>5, :timeout=>5000, :database=>"db/development.sqlite3", :adapter=>"sqlite3"}
127
+ #
128
+ # Please use only for reading.
129
+ def connection_config
130
+ connection_pool.spec.config
131
+ end
132
+
133
+ def connection_pool
134
+ connection_handler.retrieve_connection_pool(self) or raise ConnectionNotEstablished
135
+ end
136
+
137
+ def retrieve_connection
138
+ connection_handler.retrieve_connection(self)
139
+ end
140
+
141
+ # Returns true if Keymap is connected.
142
+ def connected?
143
+ connection_handler.connected?(self)
144
+ end
145
+
146
+ def remove_connection(klass = self)
147
+ connection_handler.remove_connection(klass)
148
+ end
149
+
150
+ def clear_active_connections!
151
+ connection_handler.clear_active_connections!
152
+ end
153
+
154
+ delegate :clear_reloadable_connections!,
155
+ :clear_all_connections!, :verify_active_connections!, :to => :connection_handler
156
+ end
157
+
158
+ end
159
+ end
@@ -0,0 +1,39 @@
1
+ require 'keymap/errors'
2
+
3
+ module Keymap
4
+ module ConnectionAdapters # :nodoc:
5
+ module TransactionManagement
6
+
7
+ # Runs the given block in a database transaction, and returns the result
8
+ # of the block.
9
+ def transaction(options = {})
10
+ transaction_open = false
11
+ @_current_transaction_records ||= []
12
+ begin
13
+ if block_given?
14
+ begin_db_transaction
15
+ transaction_open = true
16
+ @_current_transaction_records.push([])
17
+ yield
18
+ end
19
+ rescue Exception => database_transaction_rollback
20
+ if transaction_open && !outside_transaction?
21
+ transaction_open = false
22
+ rollback_db_transaction
23
+ end
24
+ raise unless database_transaction_rollback.is_a?(Rollback)
25
+ end
26
+ ensure
27
+ if transaction_open
28
+ begin
29
+ commit_db_transaction
30
+ rescue Exception => database_transaction_rollback
31
+ rollback_db_transaction
32
+ raise
33
+ end
34
+ end
35
+ end
36
+
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,139 @@
1
+ require 'monitor'
2
+ require 'active_support/callbacks'
3
+ require 'active_support/dependencies/autoload'
4
+ require 'keymap/connection_adapters/abstract/transaction_management'
5
+
6
+ module Keymap
7
+
8
+ module ConnectionAdapters # :nodoc:
9
+
10
+ extend ActiveSupport::Autoload
11
+
12
+ autoload_under 'abstract' do
13
+ autoload :ConnectionPool
14
+ autoload :ConnectionHandler, 'keymap/connection_adapters/abstract/connection_pool'
15
+ autoload :ConnectionManagement, 'keymap/connection_adapters/abstract/connection_pool'
16
+ autoload :ConnectionSpecification
17
+ end
18
+
19
+ class AbstractAdapter
20
+
21
+ include ActiveSupport::Callbacks
22
+ include TransactionManagement
23
+ include MonitorMixin
24
+
25
+ define_callbacks :checkout, :checkin
26
+ attr_accessor :pool
27
+ attr_reader :last_use, :in_use, :logger
28
+ alias :in_use? :in_use
29
+
30
+ def initialize(connection, logger = nil, pool = nil) #:nodoc:
31
+ super()
32
+
33
+ @active = nil
34
+ @connection = connection
35
+ @logger = logger
36
+ @pool = pool
37
+
38
+ @in_use = false
39
+ @last_use = false
40
+ end
41
+
42
+ def lease
43
+ synchronize do
44
+ unless in_use
45
+ @in_use = true
46
+ @last_use = Time.now
47
+ end
48
+ end
49
+ end
50
+
51
+ def expire
52
+ @in_use = false
53
+ end
54
+
55
+ # Returns the human-readable name of the adapter. Use mixed case - one
56
+ # can always use downcase if needed.
57
+ def adapter_name
58
+ 'Abstract'
59
+ end
60
+
61
+ # CONNECTION MANAGEMENT ====================================
62
+
63
+ # Checks whether the connection to the database is still active. This includes
64
+ # checking whether the kv-store is actually capable of responding, i.e. whether
65
+ # the connection isn't stale.
66
+ def active?
67
+ @active
68
+ end
69
+
70
+ # Disconnects from the database if already connected, and establishes a
71
+ # new connection with the database.
72
+ def reconnect!
73
+ @active = true
74
+ end
75
+
76
+ # Disconnects from the kv-store if already connected. Otherwise, this
77
+ # method does nothing.
78
+ def disconnect!
79
+ @active = false
80
+ end
81
+
82
+ # Reset the state of this connection, directing the kv-store to clear
83
+ # transactions and other connection-related server-side state. Usually a
84
+ # implementation-dependent operation.
85
+ #
86
+ # The default implementation does nothing; the implementation should be
87
+ # overridden by concrete adapters.
88
+ def reset!
89
+ # this should be overridden by concrete adapters
90
+ end
91
+
92
+ ###
93
+ # Clear any caching the kv-store adapter may be doing. This is kv-store
94
+ # specific.
95
+ def clear_cache!
96
+ # this should be overridden by concrete adapters
97
+ end
98
+
99
+ # Returns true if its required to reload the connection between requests for development mode.
100
+ def requires_reloading?
101
+ false
102
+ end
103
+
104
+ # Checks whether the connection to the kv-store is still active (i.e. not stale).
105
+ # This is done under the hood by calling <tt>active?</tt>. If the connection
106
+ # is no longer active, then this method will reconnect to the kv-store.
107
+ def verify!
108
+ reconnect! unless active?
109
+ end
110
+
111
+ # Provides access to the underlying kv-store driver for this adapter. For
112
+ # example, this method returns a Redis object in case of RedisAdapter.
113
+ #
114
+ # This is useful for when you need to call a proprietary method.
115
+ def raw_connection
116
+ @connection
117
+ end
118
+
119
+ # Begins the transaction (and turns off auto-committing).
120
+ def begin_db_transaction()
121
+ end
122
+
123
+ # Commits the transaction (and turns on auto-committing).
124
+ def commit_db_transaction()
125
+ end
126
+
127
+ # Rolls back the transaction (and turns on auto-committing). Must be
128
+ # done if the transaction block raises an exception or returns false.
129
+ def rollback_db_transaction()
130
+ end
131
+
132
+ # Check the connection back in to the connection pool
133
+ def close
134
+ pool.checkin self
135
+ end
136
+ end
137
+
138
+ end
139
+ end
@@ -0,0 +1,113 @@
1
+ require 'keymap/connection_adapters/abstract_adapter'
2
+ require 'redis'
3
+
4
+ module Keymap
5
+
6
+ class Base
7
+ def self.redis_connection(config)
8
+ config = config.symbolize_keys
9
+ config[:port] ||= 6379
10
+ ConnectionAdapters::RedisAdapter.new(nil, nil, config)
11
+ end
12
+ end
13
+
14
+ module ConnectionAdapters
15
+
16
+ class RedisAdapter < AbstractAdapter
17
+
18
+ attr_reader :config
19
+
20
+ def initialize(connection, pool, config)
21
+ super(nil)
22
+ @config = config
23
+ reconnect!
24
+ end
25
+
26
+ def adapter_name
27
+ 'redis'
28
+ end
29
+
30
+ def active?
31
+ return false unless @connection
32
+ @connection.ping == "PONG"
33
+ end
34
+
35
+ def reconnect!
36
+ disconnect!
37
+ connect
38
+ super
39
+ end
40
+
41
+ alias :reset! :reconnect!
42
+
43
+ # Disconnects from the database if already connected.
44
+ # Otherwise, this method does nothing.
45
+ def disconnect!
46
+ super
47
+ unless @connection.nil?
48
+ @connection.quit
49
+ @connection = nil
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def connect
56
+ @connection = Redis.new(config)
57
+ end
58
+
59
+ public
60
+
61
+ def begin_db_transaction
62
+ raw_connection.multi
63
+ end
64
+
65
+ def commit_db_transaction
66
+ raw_connection.exec
67
+ end
68
+
69
+ def rollback_db_transaction
70
+ raw_connection.discard
71
+ end
72
+
73
+ # Retrieves the hash whose name is identified by key.
74
+ def hash (key)
75
+ coll = KeyStoreHash.new
76
+ coll.connection = raw_connection
77
+ coll.key = key
78
+ coll
79
+ #
80
+ # Perhaps there is a more experienced ruby developer who could help with this.
81
+ # I'd rather do the following but could not figure out how to get it to work:
82
+ #
83
+ #def create_getter(connection, key)
84
+ # lambda { |field| connection.hget key, field }
85
+ #end
86
+ #
87
+ #def create_setter(connection, key)
88
+ # lambda { |field, value| connection.hset key, field, value }
89
+ #end
90
+ #
91
+ #coll.class.send :define_method, "[]", create_getter(raw_connection, key)
92
+ #coll.class.send :define_method, "[]=", create_setter(raw_connection, key)
93
+ #coll
94
+ end
95
+ end
96
+
97
+ private
98
+
99
+ class KeyStoreHash < Hash
100
+
101
+ attr_accessor :connection, :key
102
+
103
+ def [] (field)
104
+ connection.hget key, field
105
+ end
106
+
107
+ def []=(field, value)
108
+ connection.hset key, field, value
109
+ end
110
+ end
111
+
112
+ end
113
+ end