Permalink
Browse files

Merge branch 'development' of github.com:bradrobertson/apartment into…

… development
  • Loading branch information...
2 parents 5826fec + ccd3cbc commit c08f2ecdf9dcbb122bca7218845f9c1dacf3c91d @bradrobertson bradrobertson committed Jun 17, 2012
View
@@ -0,0 +1,3 @@
+if defined?(Rails) && Rails.env
+ extend Rails::ConsoleMethods
+end
View
@@ -1,3 +1,12 @@
+# 0.16.0
+ * June 1, 2012
+
+ - Apartment now supports a default_schema to be set, rather than relying on ActiveRecord's default schema_search_path
+ - Additional schemas can always be maintained in the schema_search_path by configuring persistent_schemas [ryanbrunner]
+ - This means Hstore is officially supported!!
+ - There is now a full domain based elevator to switch dbs based on the whole domain [lcowell]
+ - There is now a generic elevator that takes a Proc to switch dbs based on the return value of that proc.
+
# 0.15.0
* March 18, 2012
View
@@ -1,8 +1,8 @@
# Apartment
-*Multitenancy for Rails 3 and ActiveRecord*
-
[![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/bradrobertson/apartment)
+*Multitenancy for Rails 3 and ActiveRecord*
+
Apartment provides tools to help you deal with multiple databases in your Rails
application. If you need to have certain data sequestered based on account or company,
but still allow some data to exist in a common database, Apartment can help.
@@ -62,8 +62,9 @@ database, call switch with no arguments.
You can have Apartment route to the appropriate database by adding some Rack middleware.
Apartment can support many different "Elevators" that can take care of this routing to your data.
-In house, we use the subdomain elevator, which analyzes the subdomain of the request and switches
-to a database schema of the same name. It can be used like so:
+
+**Switch on subdomain**
+In house, we use the subdomain elevator, which analyzes the subdomain of the request and switches to a database schema of the same name. It can be used like so:
# application.rb
module My Application
@@ -73,6 +74,29 @@ to a database schema of the same name. It can be used like so:
end
end
+**Switch on domain**
+To switch based on full domain (excluding subdomains *ie 'www'* and top level domains *ie '.com'* ) use the following:
+
+ # application.rb
+ module My Application
+ class Application < Rails::Application
+
+ config.middleware.use 'Apartment::Elevators::Domain'
+ end
+ end
+
+**Custom Elevator**
+A Generic Elevator exists that allows you to pass a `Proc` (or anything that responds to `call`) to the middleware. This Object will be passed in an `ActionDispatch::Request` object when called for you to do your magic. Apartment will use the return value of this proc to switch to the appropriate database. Use like so:
+
+ # application.rb
+ module My Application
+ class Application < Rails::Application
+ # Obviously not a contrived example
+ config.middleware.use 'Apartment::Elevators::Generic', Proc.new { |request| request.host.reverse }
+ end
+ end
+
+
## Config
The following config options should be set up in a Rails initializer such as:
@@ -87,20 +111,33 @@ To set config options, add this to your initializer:
### Excluding models
-If you have some models that should always access the 'root' database, you can specify this by configuring
-Apartment using `Apartment.configure`. This will yield a config object for you. You can set excluded models like so:
+If you have some models that should always access the 'root' database, you can specify this by configuring Apartment using `Apartment.configure`. This will yield a config object for you. You can set excluded models like so:
config.excluded_models = ["User", "Company"] # these models will not be multi-tenanted, but remain in the global (public) namespace
Note that a string representation of the model name is now the standard so that models are properly constantized when reloaded in development
-### Handling Environments
+### Postgresql Schemas
-By default, when not using postgresql schemas, Apartment will prepend the environment to the database name
-to ensure there is no conflict between your environments. This is mainly for the benefit of your development
-and test environments. If you wish to turn this option off in production, you could do something like:
+**Providing a Different default_schema**
+By default, ActiveRecord will use `"$user", public` as the default `schema_search_path`. This can be modified if you wish to use a different default schema be setting:
- config.prepend_environment = !Rails.env.production?
+ config.default_schema = "some_other_schema"
+
+With that set, all excluded models will use this schema as the table name prefix instead of `public` and `reset` on `Apartment::Database` will return to this schema also
+
+**Persistent Schemas**
+Apartment will normally just switch the `schema_search_path` whole hog to the one passed in. This can lead to problems if you want other schemas to always be searched as well. Enter `persistent_schemas`. You can configure a list of other schemas that will always remain in the search path, while the default gets swapped out:
+
+ config.persistent_schemas = ['some', 'other', 'schemas']
+
+This has numerous useful applications. [Hstore](http://www.postgresql.org/docs/9.1/static/hstore.html), for instance, is a popular storage engine for Postgresql. In order to use Hstore, you have to install to a specific schema and have that always in the `schema_search_path`. This could be achieved like so:
+
+ # In a rake task, or on the console...
+ ActiveRecord::Base.connection.execute("CREATE SCHEMA hstore; CREATE EXTENSION HSTORE SCHEMA hstore")
+
+ # configure Apartment to maintain the `hstore` schema in the `schema_search_path`
+ config.persistent_schemas = ['hstore']
### Managing Migrations
@@ -121,7 +158,15 @@ You can then migration your databases using the rake task:
This basically invokes `Apartment::Database.migrate(#{db_name})` for each database name supplied
from `Apartment.database_names`
-### Delayed::Job
+### Handling Environments
+
+By default, when not using postgresql schemas, Apartment will prepend the environment to the database name
+to ensure there is no conflict between your environments. This is mainly for the benefit of your development
+and test environments. If you wish to turn this option off in production, you could do something like:
+
+ config.prepend_environment = !Rails.env.production?
+
+## Delayed::Job
If using Rails ~> 3.2, you *must* use `delayed_job ~> 3.0`. It has better Rails 3 support plus has some major changes that affect the serialization of models.
I haven't been able to get `psych` working whatsoever as the YAML parser, so to get things to work properly, you must explicitly set the parser to `syck` *before* requiring `delayed_job`
@@ -141,18 +186,25 @@ In order to make ActiveRecord models play nice with DJ and Apartment, include `A
include Apartment::Delayed::Requirements
end
+Any classes that are being used as a Delayed::Job Job need to include the `Apartment::Delayed::Job::Hooks` module into the class. This ensures that when a job runs, it switches to the appropriate tenant before performing its task. It is also required (manually at the moment) that you set a `@database` attribute on your job so the hooks know what tennant to switch to
+
class SomeDJ
- def initialize(model)
- @model = model
- @model.database = Apartment::Database.current_database
+ include Apartment::Delayed::Job::Hooks
+
+ def initialize
+ @database = Apartment::Database.current_database
end
def perform
- # do some stuff
+ # do some stuff (will automatically switch to @database before performing and switch back after)
end
end
+All jobs *must* stored in the global (public) namespace, so add it to the list of excluded models:
+
+ config.excluded_models = ["Delayed::Job"]
+
## Development
* The Local setup for development assumes that a root user with no password exists for both mysql and postgresl
View
@@ -7,16 +7,17 @@ require "rspec/core/rake_task"
RSpec::Core::RakeTask.new(:spec => "db:test:prepare") do |spec|
spec.pattern = "spec/**/*_spec.rb"
+ # spec.rspec_opts = '--order rand:16996'
end
namespace :spec do
-
+
[:tasks, :unit, :adapters, :integration].each do |type|
RSpec::Core::RakeTask.new(type => :spec) do |spec|
spec.pattern = "spec/#{type}/**/*_spec.rb"
end
end
-
+
end
task :default => :spec
@@ -30,39 +31,39 @@ end
namespace :postgres do
require 'active_record'
require "#{File.join(File.dirname(__FILE__), 'spec', 'support', 'config')}"
-
+
desc 'Build the PostgreSQL test databases'
task :build_db do
%x{ createdb -E UTF8 #{pg_config['database']} -Upostgres } rescue "test db already exists"
ActiveRecord::Base.establish_connection pg_config
ActiveRecord::Migrator.migrate('spec/dummy/db/migrate')
end
-
+
desc "drop the PostgreSQL test database"
task :drop_db do
puts "dropping database #{pg_config['database']}"
%x{ dropdb #{pg_config['database']} -Upostgres }
end
-
+
end
namespace :mysql do
require 'active_record'
require "#{File.join(File.dirname(__FILE__), 'spec', 'support', 'config')}"
-
+
desc 'Build the MySQL test databases'
task :build_db do
%x{ mysqladmin -u root create #{my_config['database']} } rescue "test db already exists"
ActiveRecord::Base.establish_connection my_config
ActiveRecord::Migrator.migrate('spec/dummy/db/migrate')
end
-
+
desc "drop the MySQL test database"
task :drop_db do
puts "dropping database #{my_config['database']}"
%x{ mysqladmin -u root drop #{my_config['database']} --force}
end
-
+
end
# TODO clean this up
View
@@ -17,15 +17,16 @@ Gem::Specification.new do |s|
s.licenses = ["MIT"]
s.require_paths = ["lib"]
s.rubygems_version = %q{1.3.7}
-
+
s.add_dependency 'activerecord', '>= 3.1.2' # must be >= 3.1.2 due to bug in prepared_statements
- s.add_dependency 'rack', '~> 1.4.0'
-
+ s.add_dependency 'rack', '>= 1.3.6'
+
+ s.add_development_dependency 'pry', '~> 0.9.9'
s.add_development_dependency 'rails', '>= 3.1.2'
s.add_development_dependency 'rake', '~> 0.9.2'
s.add_development_dependency 'sqlite3'
- s.add_development_dependency 'rspec', '~> 2.8.0'
- s.add_development_dependency 'rspec-rails', '~> 2.8.1'
+ s.add_development_dependency 'rspec', '~> 2.10.0'
+ s.add_development_dependency 'rspec-rails', '~> 2.10.0'
s.add_development_dependency 'capybara', '~> 1.0.0'
s.add_development_dependency 'pg', '>= 0.11.0'
s.add_development_dependency 'mysql2', '~> 0.3.10'
View
@@ -3,8 +3,11 @@
module Apartment
class << self
- attr_accessor :use_postgres_schemas, :seed_after_create, :prepend_environment
- attr_writer :database_names, :excluded_models
+ ACCESSOR_METHODS = [:use_postgres_schemas, :seed_after_create, :prepend_environment]
+ WRITER_METHODS = [:database_names, :excluded_models, :default_schema, :persistent_schemas]
+
+ attr_accessor(*ACCESSOR_METHODS)
+ attr_writer(*WRITER_METHODS)
# configure apartment with available options
def configure
@@ -21,6 +24,19 @@ def excluded_models
@excluded_models || []
end
+ def default_schema
+ @default_schema || "public"
+ end
+
+ def persistent_schemas
+ @persistent_schemas || []
+ end
+
+ # Reset all the config for Apartment
+ def reset
+ (ACCESSOR_METHODS + WRITER_METHODS).each{|method| instance_variable_set(:"@#{method}", nil) }
+ end
+
end
autoload :Database, 'apartment/database'
@@ -33,7 +49,9 @@ module Adapters
end
module Elevators
- autoload :Subdomain, 'apartment/elevators/subdomain'
+ autoload :Generic, 'apartment/elevators/generic'
+ autoload :Subdomain, 'apartment/elevators/subdomain'
+ autoload :Domain, 'apartment/elevators/domain'
end
module Delayed
@@ -1,16 +1,16 @@
module Apartment
module Database
-
+
def self.mysql2_adapter(config)
- Adapters::MysqlAdapter.new config
+ Adapters::Mysql2Adapter.new config
end
end
-
+
module Adapters
-
- class MysqlAdapter < AbstractAdapter
-
+
+ class Mysql2Adapter < AbstractAdapter
+
protected
# Connect to new database
@@ -26,4 +26,4 @@ def connect_to_new(database)
end
end
end
-end
+end
Oops, something went wrong.

0 comments on commit c08f2ec

Please sign in to comment.