Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial commit.

  • Loading branch information...
commit 9af101ff417f9ad5b6d01639097d6e73587a02da 0 parents
Alexander Staubo authored
1  .gitignore
@@ -0,0 +1 @@
+/pkg
4 Gemfile
@@ -0,0 +1,4 @@
+source 'http://rubygems.org/'
+
+gem 'activesupport', '>= 2.2'
+gem 'activerecord', '>= 2.2'
20 LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2011 [name of plugin creator]
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
125 README.markdown
@@ -0,0 +1,125 @@
+Multidb
+=======
+
+A simple, no-nonsense ActiveRecord extension which allows the application to switch
+between multiple database connections, such as in a master/slave environment. For example:
+
+ Multidb.use(:slave) do
+ @posts = Post.all
+ end
+
+The extension was developed in order to support PostgreSQL 9.0's new hot standby
+support in a production environment.
+
+Randomized balancing of multiple connections within a group is supported. In the
+future, some kind of automatic balancing of read/write queries might be implemented.
+
+Testet with Rails 2.3.11. No guarantees about Rails 3.
+
+
+Comparison to other ActiveRecord extensions
+===========================================
+
+Unlike other, more full-featured extensions such as Octopus and Seamless Database Pool,
+Multidb strives to be:
+
+* Implemented using a minimal amount of
+monkeypatching magic. The only part of ActiveRecord that is overriden is
+`ActiveRecord::Base#connection`.
+
+* Non-invasive. Very small amounts of configuration and changes to the client
+application are required.
+
+* Orthogonal. Unlike Octopus, for example, connections follow context:
+
+ Multidb.use(:master) do
+ @post = Post.find(1)
+ Multidb.use(:slave) do
+ @post.authors # This will use the slave
+ end
+ end
+
+* Low-overhead. Since `connection` is called on every single
+database operation, it needs to be fast. Which it is: Multidb's implementation of
+`connection` incurs only a single hash lookup in `Thread.current`.
+
+However, Multidb also has fewer features. At the moment it will _not_ automatically
+split reads and writes between database backends.
+
+
+Getting started
+===============
+
+In Rails 2.x applications without a `Gemfile`, add this to `environment.rb`:
+
+ config.gem 'ar-multidb'
+
+In Bundler-based on Rails apps, add this to your `Gemfile`:
+
+ gem 'ar-multidb', :require => 'multidb'
+
+You may also install it as a plugin:
+
+ script/plugin install git://github.com/alexstaubo/multidb.git
+
+All that is needed is to set up your `database.yml` file:
+
+ production:
+ adapter: postgresql
+ database: myapp_production
+ username: ohoh
+ password: mymy
+ host: db1
+ multidb:
+ databases:
+ slave:
+ host: db-slave
+
+Each database entry may be a hash or an array. So this also works:
+
+ production:
+ adapter: postgresql
+ database: myapp_production
+ username: ohoh
+ password: mymy
+ host: db1
+ multidb:
+ databases:
+ slave:
+ - host: db-slave1
+ - host: db-slave2
+
+The database hashes follow the same format as the top-level adapter configuration. In
+other words, each database connection may override the adapter, database name, username
+and so on.
+
+To use the connection, modify your code by wrapping database access logic in blocks:
+
+ Multidb.use(:slave) do
+ @posts = Post.all
+ end
+
+To wrap entire controller requests, for example:
+
+ class PostsController < ApplicationController
+ around_filter :run_using_slave
+
+ def run_using_slave(&block)
+ Multidb.use(:slave, &block)
+ end
+ end
+
+You can also set the current connection for the remainder of the thread's execution:
+
+ Multidb.use(:slave)
+ # Do work
+ Multidb.use(:master)
+
+Note that the symbol `:default` will (unless you override it) refer to the default
+top-level ActiveRecord configuration.
+
+
+Legal
+=====
+
+Copyright (c) 2011 Alexander Staubo. Released under the MIT license. See the file LICENSE.
38 Rakefile
@@ -0,0 +1,38 @@
+# encoding: utf-8
+
+require 'rubygems'
+require 'rake'
+require 'rake/rdoctask'
+
+begin
+ require 'jeweler'
+ Jeweler::Tasks.new do |gem|
+ gem.name = 'ar-multidb'
+ gem.summary = gem.description = %Q{Multidb is an ActiveRecord extension for switching between multiple database connections, such as master/slave setups.}
+ gem.email = "alex@bengler.no"
+ gem.homepage = "http://github.com/alexstaubo/multidb"
+ gem.authors = ["Alexander Staubo"]
+ gem.has_rdoc = true
+ gem.require_paths = ["lib"]
+ gem.files = FileList[%W(
+ README.markdown
+ VERSION
+ LICENSE*
+ lib/**/*
+ )]
+ gem.add_dependency 'activesupport', '>= 2.2'
+ gem.add_dependency 'activerecord', '>= 2.2'
+ end
+ Jeweler::GemcutterTasks.new
+rescue LoadError
+ $stderr << "Warning: Gem-building tasks are not included as Jeweler (or a dependency) not available. Install it with: `gem install jeweler`.\n"
+end
+
+Rake::RDocTask.new do |rdoc|
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
+
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = "ruby-hdfs #{version}"
+ rdoc.rdoc_files.include('README*')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
1  VERSION
@@ -0,0 +1 @@
+0.1.0
54 ar-multidb.gemspec
@@ -0,0 +1,54 @@
+# Generated by jeweler
+# DO NOT EDIT THIS FILE DIRECTLY
+# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
+# -*- encoding: utf-8 -*-
+
+Gem::Specification.new do |s|
+ s.name = %q{ar-multidb}
+ s.version = "0.1.0"
+
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
+ s.authors = ["Alexander Staubo"]
+ s.date = %q{2011-05-18}
+ s.description = %q{Multidb is an ActiveRecord extension for switching between multiple database connections, such as master/slave setups.}
+ s.email = %q{alex@bengler.no}
+ s.extra_rdoc_files = [
+ "LICENSE",
+ "README.markdown"
+ ]
+ s.files = [
+ "LICENSE",
+ "README.markdown",
+ "VERSION",
+ "lib/multidb.rb",
+ "lib/multidb/balancer.rb",
+ "lib/multidb/configuration.rb",
+ "lib/multidb/model_extensions.rb"
+ ]
+ s.homepage = %q{http://github.com/alexstaubo/multidb}
+ s.require_paths = ["lib"]
+ s.rubygems_version = %q{1.5.0}
+ s.summary = %q{Multidb is an ActiveRecord extension for switching between multiple database connections, such as master/slave setups.}
+
+ if s.respond_to? :specification_version then
+ s.specification_version = 3
+
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
+ s.add_runtime_dependency(%q<activesupport>, [">= 2.2"])
+ s.add_runtime_dependency(%q<activerecord>, [">= 2.2"])
+ s.add_runtime_dependency(%q<activesupport>, [">= 2.2"])
+ s.add_runtime_dependency(%q<activerecord>, [">= 2.2"])
+ else
+ s.add_dependency(%q<activesupport>, [">= 2.2"])
+ s.add_dependency(%q<activerecord>, [">= 2.2"])
+ s.add_dependency(%q<activesupport>, [">= 2.2"])
+ s.add_dependency(%q<activerecord>, [">= 2.2"])
+ end
+ else
+ s.add_dependency(%q<activesupport>, [">= 2.2"])
+ s.add_dependency(%q<activerecord>, [">= 2.2"])
+ s.add_dependency(%q<activesupport>, [">= 2.2"])
+ s.add_dependency(%q<activerecord>, [">= 2.2"])
+ end
+end
+
2  init.rb
@@ -0,0 +1,2 @@
+# Init file for running as Rails plugin.
+require 'multidb'
25 lib/multidb.rb
@@ -0,0 +1,25 @@
+require 'multidb/configuration'
+require 'multidb/model_extensions'
+require 'multidb/balancer'
+
+module Multidb
+ class << self
+
+ def install!
+ configure!
+ if @configuration and @configuration.raw_configuration[:databases].any?
+ ActiveRecord::Base.class_eval do
+ include Multidb::ModelExtensions
+ end
+ @balancer = Balancer.new(@configuration)
+ end
+ end
+
+ attr_reader :balancer
+
+ delegate :use, :get, :to => :balancer
+
+ end
+end
+
+Multidb.install!
81 lib/multidb/balancer.rb
@@ -0,0 +1,81 @@
+module Multidb
+
+ class Candidate
+ def initialize(config)
+ adapter = config[:adapter]
+ begin
+ require "active_record/connection_adapters/#{adapter}_adapter"
+ rescue LoadError
+ raise "Please install the #{adapter} adapter: `gem install activerecord-#{adapter}-adapter` (#{$!})"
+ end
+ @connection_pool = ActiveRecord::ConnectionAdapters::ConnectionPool.new(
+ ActiveRecord::Base::ConnectionSpecification.new(config, "#{adapter}_connection"))
+ end
+
+ def connection
+ @connection_pool.connection
+ end
+ end
+
+ class Balancer
+
+ def initialize(configuration)
+ @candidates = {}.with_indifferent_access
+ @configuration = configuration
+ @configuration.raw_configuration[:databases].each_pair do |name, config|
+ configs = config.is_a?(Array) ? config : [config]
+ configs.each do |config|
+ candidate = Candidate.new(@configuration.default_adapter.merge(config))
+ @candidates[name] ||= []
+ @candidates[name].push(candidate)
+ end
+ end
+ unless @candidates.include?(:default)
+ @candidates[:default] = [Candidate.new(@configuration.default_adapter)]
+ end
+ end
+
+ def get(name, &block)
+ candidates = @candidates[name] || []
+ raise ArgumentError, "No such database connection '#{name}'" if candidates.blank?
+ candidate = candidates.sample
+ block_given? ? yield(candidate) : candidate
+ end
+
+ def use(name, &block)
+ result = nil
+ get(name) do |candidate|
+ connection = candidate.connection
+ if block_given?
+ previous_connection, Thread.current[:multidb_connection] =
+ Thread.current[:multidb_connection], connection
+ begin
+ result = yield
+ ensure
+ Thread.current[:multidb_connection] = previous_connection
+ end
+ result
+ else
+ result = Thread.current[:multidb_connection] = connection
+ end
+ end
+ result
+ end
+
+ def current_connection
+ Thread.current[:multidb_connection] ||= ActiveRecord::Base.connection_pool.connection
+ end
+
+ class << self
+ def use(name, &block)
+ Multidb.balancer.use(name, &block)
+ end
+
+ def current_connection
+ Multidb.balancer.current_connection
+ end
+ end
+
+ end
+
+end
25 lib/multidb/configuration.rb
@@ -0,0 +1,25 @@
+module Multidb
+
+ class << self
+
+ def configure!
+ activerecord_config = ActiveRecord::Base.connection_pool.connection.instance_variable_get(:@config).dup.with_indifferent_access
+ default_adapter, configuration_hash = activerecord_config, activerecord_config.delete(:multidb)
+ @configuration = Configuration.new(default_adapter, configuration_hash)
+ end
+
+ attr_reader :configuration
+
+ end
+
+ class Configuration
+ def initialize(default_adapter, configuration_hash)
+ @default_adapter = default_adapter
+ @raw_configuration = configuration_hash
+ end
+
+ attr_reader :default_adapter
+ attr_reader :raw_configuration
+ end
+
+end
26 lib/multidb/model_extensions.rb
@@ -0,0 +1,26 @@
+module Multidb
+ module ModelExtensions
+
+ class << self
+ def append_features(base)
+ base.extend(ClassMethods)
+ base.class_eval do
+ include Multidb::ModelExtensions::InstanceMethods
+ class << self
+ alias_method_chain :connection, :multidb
+ end
+ end
+ end
+ end
+
+ module ClassMethods
+ def connection_with_multidb
+ Multidb.balancer.current_connection
+ end
+ end
+
+ module InstanceMethods
+ end
+
+ end
+end
52 multidb.gemspec
@@ -0,0 +1,52 @@
+# Generated by jeweler
+# DO NOT EDIT THIS FILE DIRECTLY
+# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
+# -*- encoding: utf-8 -*-
+
+Gem::Specification.new do |s|
+ s.name = %q{ar-multidb}
+ s.version = "0.1.0"
+
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
+ s.authors = ["Alexander Staubo"]
+ s.date = %q{2010-08-30}
+ s.description = %q{Capistrano extension that adds syntax checks for Ruby, ERB and JavaScript files.}
+ s.email = %q{alex@bengler.no}
+ s.extra_rdoc_files = [
+ "LICENSE",
+ "LICENSE.closure",
+ "README.markdown"
+ ]
+ s.files = [
+ "LICENSE",
+ "LICENSE.closure",
+ "README.markdown",
+ "VERSION",
+ "lib/capistrano/ext/syntax_checking.rb",
+ "lib/capistrano/ext/syntax_checking/erb.rb",
+ "lib/capistrano/ext/syntax_checking/haml.rb",
+ "lib/capistrano/ext/syntax_checking/javascript.rb",
+ "lib/capistrano/ext/syntax_checking/ruby.rb",
+ "lib/capistrano/recipes/syntax_checking.rb",
+ "lib/closure/compiler.jar"
+ ]
+ s.homepage = %q{http://github.com/alexstaubo/capistrano-syntax-checking}
+ s.rdoc_options = ["--charset=UTF-8"]
+ s.require_paths = ["lib"]
+ s.rubygems_version = %q{1.3.7}
+ s.summary = %q{Capistrano extension that adds syntax checks for Ruby, ERB and JavaScript files.}
+
+ if s.respond_to? :specification_version then
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
+ s.specification_version = 3
+
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
+ s.add_runtime_dependency(%q<capistrano>, [">= 2.0"])
+ else
+ s.add_dependency(%q<capistrano>, [">= 2.0"])
+ end
+ else
+ s.add_dependency(%q<capistrano>, [">= 2.0"])
+ end
+end
+
Please sign in to comment.
Something went wrong with that request. Please try again.