multiple_connection_handler 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/README +53 -0
  2. data/lib/multiple_connection_handler.rb +135 -0
  3. metadata +65 -0
data/README ADDED
@@ -0,0 +1,53 @@
1
+ multiple_connection_handler
2
+ =========
3
+
4
+ A gem for Rails that provides a class, MultipleConnectionHandler, that allows
5
+ for access to connections to any databases in the database.yml file for your
6
+ Rails application. Quick and dirty implementation to let us do dirty things.
7
+
8
+
9
+ Full disclosure
10
+ =========
11
+
12
+ Implemented as a singleton accessed solely through the class methods. Some
13
+ instance methods are exposed, but you should NOT use them directly. You should
14
+ also NOT try to instantiate multiple MultipleConnectionHandler objects.
15
+
16
+ Have NOT evaluated any thread-safety of this code. In fact, it's probably
17
+ not safe to multithread code that's will use the MultipleConnectionHandler,
18
+ as there is no locking on cached db connections.
19
+
20
+ NOTE NOTE NOTE NOTE NOTE NOTE
21
+ -----------------------------
22
+ This should only be used if you're being bad and are executing queries via
23
+ raw SQL, and not using ActiveRecord methods. If you just want to specify
24
+ different databases for your various models, see ActiveRecord::Base
25
+ documentation.
26
+
27
+ I admit up front this is pretty hacky in how we're piggy-backing on
28
+ ActiveRecord's connection pooling and ConnectionHandler. So please read
29
+ through the code (esp. the connection() instance method) before proceeding.
30
+
31
+
32
+ Example (yes, this is a terrible example)
33
+ ========
34
+
35
+ dev_migrations = MultipleConnectionHandler.connection(:development).execute(
36
+ "SELECT * FROM schema_migrations")
37
+ staging_migrations = MultipleConnectionHandler.connection(:staging).execute(
38
+ "SELECT * FROM schema_migrations")
39
+
40
+ if dev_migrations != staging_migrations
41
+ Rails.logger.error "Can't push data from staging to dev right now cause " +
42
+ "they're on different migrations"
43
+ end
44
+
45
+
46
+ Testing
47
+ =========
48
+
49
+ You may notice the lack of unit tests. Yeah, we just tested this manually.
50
+ Probably the best, as you basically need acess to multiple dbs in order to test
51
+ whether this functionality is working. I guess if you'd like, you could create
52
+ some models tied to different db instances, use this to directly modify those
53
+ models and then use the models to verify the changes. Meh.
@@ -0,0 +1,135 @@
1
+ # grants access to db connections for several environments at once.
2
+ # implemented as a singleton accessed through class methods.
3
+ class MultipleConnectionHandler
4
+
5
+ # raised when the requested connection isn't recognized from the db config
6
+ # file
7
+ #-----------------------------------------------------------------------------
8
+ class UnrecognizedDatabaseError < Exception
9
+ def initialize(unrecognized_db_name, db_config_filename)
10
+ super("Database specification for '#{unrecognized_db_name}' not found " +
11
+ " in '#{db_config_filename}'.")
12
+ end
13
+ end
14
+
15
+ # raised when attempting to re-initialize the handler after it's already
16
+ # been initialized. prevents competition between two clients of the class
17
+ # who may attempt to change the database config file on top of one another
18
+ #-----------------------------------------------------------------------------
19
+ class DoubleInitializationError < Exception
20
+ def initialize
21
+ super("Re-initializing MultipleConnectionHandler not allowed")
22
+ end
23
+ end
24
+
25
+
26
+ # just a little token class to hand to the ActiveRecord ConnectionHandler
27
+ # so we can piggy-back on its connection management infrastructure. it just
28
+ # requires an object that you can call .name on...though it's sort of
29
+ # expecting a (model) class object...
30
+ #-----------------------------------------------------------------------------
31
+ class DbKey
32
+ attr_accessor :name
33
+ def initialize(name)
34
+ self.name = name
35
+ end
36
+ def ==(other)
37
+ self.name == other.name
38
+ end
39
+ end
40
+
41
+ # return a db connection for the specified name, establishing new pools
42
+ # with the AR connection handler if necessary
43
+ #-----------------------------------------------------------------------------
44
+ def self.connection(spec_name)
45
+ instance.connection(spec_name)
46
+ end
47
+
48
+ # returns the db name for the given spec name
49
+ #-----------------------------------------------------------------------------
50
+ def self.db_name(spec_name)
51
+ instance.configurations[spec_name.to_s]['database']
52
+ end
53
+
54
+ @@static_initialized = false
55
+ # initialize the connections handler. only necessary to be called if
56
+ # overriding any default configuration. recommend against using this
57
+ # method explicitly and just allowing default.
58
+ #
59
+ # options:
60
+ # - :config_file => the database config file to use. default is the
61
+ # rails default of ..../config/database.yml
62
+ #-----------------------------------------------------------------------------
63
+ def self.init(options = {})
64
+ # don't allow re-initializing. just ignore if we're already initialized
65
+ # unless we're asked to use a different config file
66
+ if @@static_initialized
67
+ if @@instantiated && options[:config_file] &&
68
+ options[:config_file] != instance.db_config_file
69
+
70
+ raise DoubleInitializationError
71
+ end
72
+ else
73
+ @@db_config_file = options[:config_file] || "#{RAILS_ROOT}/config/database.yml"
74
+ @@static_initialized = true
75
+ end
76
+
77
+ end
78
+
79
+ attr_accessor :db_config_file, :configurations, :handler
80
+ attr_accessor :established_connections
81
+
82
+ # NOTE: this method is not declared protected so that the class method can
83
+ # call it...but really shouldn't ever be called by external code.
84
+
85
+ # the meat of the work is done here. retrieve a connection using cached
86
+ # connections or get a new one.
87
+ #-----------------------------------------------------------------------------
88
+ def connection(spec_name)
89
+ db_key = DbKey.new(spec_name.to_s)
90
+
91
+ unless established_connections.member? db_key
92
+ unless configurations.include? db_key.name
93
+ raise UnrecognizedDatabaseError.new(db_key.name, db_config_file)
94
+ end
95
+
96
+ spec = configurations[db_key.name]
97
+
98
+ handler.establish_connection(db_key.name,
99
+ ActiveRecord::Base::ConnectionSpecification.new(spec,
100
+ "#{spec['adapter']}_connection"))
101
+
102
+ established_connections.add db_key.name
103
+ end # end connection not established yet
104
+
105
+ handler.retrieve_connection db_key
106
+ end
107
+
108
+ protected
109
+
110
+ # constructor
111
+ # arguments:
112
+ # - db_config_file: filename of database configurations file (in yaml).
113
+ #-----------------------------------------------------------------------------
114
+ def initialize(config_file)
115
+ self.db_config_file = config_file
116
+ self.configurations = YAML::load(File.open(self.db_config_file))
117
+ self.handler = ActiveRecord::ConnectionAdapters::ConnectionHandler.new
118
+ self.established_connections = Set.new
119
+ end
120
+
121
+ @@db_config_file = nil
122
+ @@instantiated = false
123
+ # the singleton instance. create if not already created, using
124
+ # config filename specified in init()
125
+ #-----------------------------------------------------------------------------
126
+ def self.instance
127
+ unless @@instantiated
128
+ self.init
129
+ @@instance = self.new(@@db_config_file)
130
+ @@instantiated = true
131
+ end
132
+ @@instance
133
+ end
134
+
135
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: multiple_connection_handler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - bmpercy
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-15 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activerecord
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ~>
22
+ - !ruby/object:Gem::Version
23
+ version: "2.1"
24
+ version:
25
+ description: " Hacky utility to access dbs listed in Rails' database.yml. See README for more info.\n"
26
+ email:
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - lib/multiple_connection_handler.rb
35
+ - README
36
+ has_rdoc: true
37
+ homepage:
38
+ licenses: []
39
+
40
+ post_install_message:
41
+ rdoc_options: []
42
+
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: 1.8.1
50
+ version:
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ requirements: []
58
+
59
+ rubyforge_project:
60
+ rubygems_version: 1.3.5
61
+ signing_key:
62
+ specification_version: 3
63
+ summary: Rails utility for accessing arbitrary db connection listed in database.yml
64
+ test_files: []
65
+