dr-apartment 0.14.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. data/.gitignore +6 -0
  2. data/.rspec +2 -0
  3. data/.rvmrc +1 -0
  4. data/Gemfile +22 -0
  5. data/HISTORY.md +133 -0
  6. data/README.md +152 -0
  7. data/Rakefile +79 -0
  8. data/apartment.gemspec +32 -0
  9. data/lib/apartment.rb +69 -0
  10. data/lib/apartment/adapters/abstract_adapter.rb +176 -0
  11. data/lib/apartment/adapters/jdbcpostgresql_adapter.rb +115 -0
  12. data/lib/apartment/adapters/mysql_adapter.rb +18 -0
  13. data/lib/apartment/adapters/postgresql_adapter.rb +114 -0
  14. data/lib/apartment/console.rb +12 -0
  15. data/lib/apartment/database.rb +57 -0
  16. data/lib/apartment/delayed_job/active_record.rb +20 -0
  17. data/lib/apartment/delayed_job/enqueue.rb +20 -0
  18. data/lib/apartment/delayed_job/hooks.rb +25 -0
  19. data/lib/apartment/delayed_job/requirements.rb +23 -0
  20. data/lib/apartment/elevators/subdomain.rb +27 -0
  21. data/lib/apartment/migrator.rb +23 -0
  22. data/lib/apartment/railtie.rb +54 -0
  23. data/lib/apartment/reloader.rb +24 -0
  24. data/lib/apartment/version.rb +3 -0
  25. data/lib/tasks/apartment.rake +70 -0
  26. data/spec/adapters/mysql_adapter_spec.rb +36 -0
  27. data/spec/adapters/postgresql_adapter_spec.rb +137 -0
  28. data/spec/apartment_spec.rb +11 -0
  29. data/spec/config/database.yml +13 -0
  30. data/spec/dummy/Rakefile +7 -0
  31. data/spec/dummy/app/controllers/application_controller.rb +6 -0
  32. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  33. data/spec/dummy/app/models/company.rb +3 -0
  34. data/spec/dummy/app/models/user.rb +3 -0
  35. data/spec/dummy/app/views/application/index.html.erb +1 -0
  36. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  37. data/spec/dummy/config.ru +4 -0
  38. data/spec/dummy/config/application.rb +47 -0
  39. data/spec/dummy/config/boot.rb +10 -0
  40. data/spec/dummy/config/database.yml +16 -0
  41. data/spec/dummy/config/environment.rb +5 -0
  42. data/spec/dummy/config/environments/development.rb +26 -0
  43. data/spec/dummy/config/environments/production.rb +49 -0
  44. data/spec/dummy/config/environments/test.rb +35 -0
  45. data/spec/dummy/config/initializers/apartment.rb +4 -0
  46. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  47. data/spec/dummy/config/initializers/inflections.rb +10 -0
  48. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  49. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  50. data/spec/dummy/config/initializers/session_store.rb +8 -0
  51. data/spec/dummy/config/locales/en.yml +5 -0
  52. data/spec/dummy/config/routes.rb +3 -0
  53. data/spec/dummy/db/migrate/20110613152810_create_dummy_models.rb +37 -0
  54. data/spec/dummy/db/migrate/20111202022214_create_table_books.rb +13 -0
  55. data/spec/dummy/db/schema.rb +48 -0
  56. data/spec/dummy/db/seeds.rb +8 -0
  57. data/spec/dummy/db/test.sqlite3 +0 -0
  58. data/spec/dummy/lib/fake_dj_class.rb +6 -0
  59. data/spec/dummy/public/404.html +26 -0
  60. data/spec/dummy/public/422.html +26 -0
  61. data/spec/dummy/public/500.html +26 -0
  62. data/spec/dummy/public/favicon.ico +0 -0
  63. data/spec/dummy/public/stylesheets/.gitkeep +0 -0
  64. data/spec/dummy/script/rails +6 -0
  65. data/spec/integration/apartment_rake_integration_spec.rb +74 -0
  66. data/spec/integration/database_integration_spec.rb +200 -0
  67. data/spec/integration/delayed_job_integration_spec.rb +100 -0
  68. data/spec/integration/middleware/subdomain_elevator_spec.rb +63 -0
  69. data/spec/spec_helper.rb +31 -0
  70. data/spec/support/apartment_helpers.rb +32 -0
  71. data/spec/support/capybara_sessions.rb +15 -0
  72. data/spec/support/config.rb +11 -0
  73. data/spec/tasks/apartment_rake_spec.rb +118 -0
  74. data/spec/unit/config_spec.rb +78 -0
  75. data/spec/unit/middleware/subdomain_elevator_spec.rb +20 -0
  76. data/spec/unit/migrator_spec.rb +87 -0
  77. data/spec/unit/reloader_spec.rb +22 -0
  78. metadata +144 -0
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ *.log
6
+
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format documentation
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm --create ruby-1.9.2-p180@apartment
data/Gemfile ADDED
@@ -0,0 +1,22 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'rails', '~> 3.1.2'
4
+ gem 'rake', '~> 0.8.7'
5
+ gem 'rspec', '~> 2.6.0'
6
+ gem 'rspec-rails', '~> 2.6.1'
7
+ gem 'capybara', '1.0.0'
8
+ gem 'delayed_job', '~> 2.1.4'
9
+
10
+ platforms :jruby do
11
+ gem 'activerecord-jdbcpostgresql-adapter', :git => 'git://github.com/dryade/activerecord-jdbc-adapter.git'
12
+ gem 'activerecord-jdbcsqlite3-adapter'
13
+ gem 'activerecord-jdbcmysql-adapter'
14
+ gem 'jruby-openssl'
15
+ end
16
+
17
+ platforms :ruby do
18
+ gem 'pg', '~> 0.11.0'
19
+ gem "silent-postgres", "~> 0.1.1"
20
+ gem 'mysql2', '~> 0.3.7'
21
+ gem 'sqlite3'
22
+ end
@@ -0,0 +1,133 @@
1
+ # 0.14.1
2
+ * Dec 13, 2011
3
+
4
+ - Fix ActionDispatch::Callbacks deprecation warnings
5
+
6
+ # 0.14.0
7
+ * Dec 13, 2011
8
+
9
+ - Rails 3.1 Support
10
+
11
+ # 0.13.1
12
+ * Nov 8, 2011
13
+
14
+ - Reset prepared statement cache for rails 3.1.1 before switching dbs when using postgresql schemas
15
+ - Only necessary until the next release which will be more schema aware
16
+
17
+ # 0.13.0
18
+ * Oct 25, 2011
19
+
20
+ - `process` will now rescue with reset if the previous schema/db is no longer available
21
+ - `create` now takes an optional block which allows you to process within the newly created db
22
+ - Fixed Rails version >= 3.0.10 and < 3.1 because there have been significant testing problems with 3.1, next version will hopefully fix this
23
+
24
+ # 0.12.0
25
+ * Oct 4, 2011
26
+
27
+ - Added a `drop` method for removing databases/schemas
28
+ - Refactored abstract adapter to further remove duplication in concrete implementations
29
+ - Excluded models now take string references so they are properly reloaded in development
30
+ - Better silencing of `schema.rb` loading using `verbose` flag
31
+
32
+ # 0.11.1
33
+ * Sep 22, 2011
34
+
35
+ - Better use of Railties for initializing apartment
36
+ - The following changes were necessary as I haven't figured out how to properly hook into Rails reloading
37
+ - Added reloader middleware in development to init Apartment on each request
38
+ - Override `reload!` in console to also init Apartment
39
+
40
+ # 0.11.0
41
+ * Sep 20, 2011
42
+
43
+ - Excluded models no longer use a different connection when using postgresql schemas. Instead their table_name is prefixed with `public.`
44
+
45
+ # 0.10.3
46
+ * Sep 20, 2011
47
+
48
+ - Fix improper raising of exceptions on create and reset
49
+
50
+ # 0.10.2
51
+ * Sep 15, 2011
52
+
53
+ - Remove all the annoying logging for loading db schema and seeding on create
54
+
55
+ # 0.10.1
56
+ * Aug 11, 2011
57
+
58
+ - Fixed bug in DJ where new objects (that hadn't been pulled from the db) didn't have the proper database assigned
59
+
60
+ # 0.10.0
61
+ * July 29, 2011
62
+
63
+ - Added better support for Delayed Job
64
+ - New config option that enables Delayed Job wrappers
65
+ - Note that DJ support uses a work-around in order to get queues stored in the public schema, not sure why it doesn't work out of the box, will look into it, until then, see documentation on queue'ng jobs
66
+
67
+ # 0.9.2
68
+ * July 4, 2011
69
+
70
+ - Migrations now run associated rails migration fully, fixes schema.rb not being reloaded after migrations
71
+
72
+ # 0.9.1
73
+ * June 24, 2011
74
+
75
+ - Hooks now take the payload object as an argument to fetch the proper db for DJ hooks
76
+
77
+ # 0.9.0
78
+ * June 23, 2011
79
+
80
+ - Added module to provide delayed job hooks
81
+
82
+ # 0.8.0
83
+ * June 23, 2011
84
+
85
+ - Added #current_database which will return the current database (or schema) name
86
+
87
+ # 0.7.0
88
+ * June 22, 2011
89
+
90
+ - Added apartment:seed rake task for seeding all dbs
91
+
92
+ # 0.6.0
93
+ * June 21, 2011
94
+
95
+ - Added #process to connect to new db, perform operations, then ensure a reset
96
+
97
+ # 0.5.1
98
+ * June 21, 2011
99
+
100
+ - Fixed db migrate up/down/rollback
101
+ - added db:redo
102
+
103
+ # 0.5.0
104
+ * June 20, 2011
105
+
106
+ - Added the concept of an "Elevator", a rack based strategy for db switching
107
+ - Added the Subdomain Elevator middleware to enabled db switching based on subdomain
108
+
109
+ # 0.4.0
110
+ * June 14, 2011
111
+
112
+ - Added `configure` method on Apartment instead of using yml file, allows for dynamic setting of db names to migrate for rake task
113
+ - Added `seed_after_create` config option to import seed data to new db on create
114
+
115
+ # 0.3.0
116
+ * June 10, 2011
117
+
118
+ - Added full support for database migration
119
+ - Added in method to establish new connection for excluded models on startup rather than on each switch
120
+
121
+ # 0.2.0
122
+ * June 6, 2011 *
123
+
124
+ - Refactor to use more rails/active_support functionality
125
+ - Refactor config to lazily load apartment.yml if exists
126
+ - Remove OStruct and just use hashes for fetching methods
127
+ - Added schema load on create instead of migrating from scratch
128
+
129
+ # 0.1.3
130
+ * March 30, 2011 *
131
+
132
+ - Original pass from Ryan
133
+
@@ -0,0 +1,152 @@
1
+ # Apartment
2
+ *Multitenancy for Rails 3*
3
+
4
+ Apartment provides tools to help you deal with multiple databases in your Rails
5
+ application. If you need to have certain data sequestered based on account or company,
6
+ but still allow some data to exist in a common database, Apartment can help.
7
+
8
+
9
+ ## Installation
10
+
11
+ ### Rails 3
12
+
13
+ Add the following to your Gemfile:
14
+
15
+ gem 'apartment'
16
+
17
+ That's all you need to set up the Apartment libraries. If you want to switch databases
18
+ on a per-user basis, look under "Usage - Switching databases per request", below.
19
+
20
+ > NOTE: If using [postgresl schemas](http://www.postgresql.org/docs/9.0/static/ddl-schemas.html) you must use:
21
+ >
22
+ > * for Rails 3.0.x: _Rails ~> 3.0.10_, it contains a [patch](https://github.com/rails/rails/pull/1607) that has better postgresql schema support
23
+ > * for Rails 3.1.x: _Rails ~> 3.1.2_, it contains a [patch](https://github.com/rails/rails/pull/3232) that makes prepared statements work with multiple schemas
24
+
25
+ ## Usage
26
+
27
+ ### Creating new Databases
28
+
29
+ Before you can switch to a new apartment database, you will need to create it. Whenever
30
+ you need to create a new database, you can run the following command:
31
+
32
+ Apartment::Database.create('database_name')
33
+
34
+ Apartment will create a new database in the following format: "_environment_\_database_name".
35
+ In the case of a sqlite database, this will be created in your 'db/migrate' folder. With
36
+ other databases, the database will be created as a new DB within the system.
37
+
38
+ When you create a new database, all migrations will be run against that database, so it will be
39
+ up to date when create returns.
40
+
41
+ #### Notes on PostgreSQL
42
+
43
+ PostgreSQL works slightly differently than other databases when creating a new DB. If you
44
+ are using PostgreSQL, Apartment by default will set up a new **schema** and migrate into there. This
45
+ provides better performance, and allows Apartment to work on systems like Heroku, which
46
+ would not allow a full new database to be created.
47
+
48
+ One can optionally use the full database creation instead if they want, though this is not recommended
49
+
50
+ ### Switching Databases
51
+
52
+ To switch databases using Apartment, use the following command:
53
+
54
+ Apartment::Database.switch('database_name')
55
+
56
+ When switch is called, all requests coming to ActiveRecord will be routed to the database
57
+ you specify (with the exception of excluded models, see below). To return to the 'root'
58
+ database, call switch with no arguments.
59
+
60
+ ### Switching Databases per request
61
+
62
+ You can have Apartment route to the appropriate database by adding some Rack middleware.
63
+ Apartment can support many different "Elevators" that can take care of this routing to your data.
64
+ In house, we use the subdomain elevator, which analyzes the subdomain of the request and switches
65
+ to a database schema of the same name. It can be used like so:
66
+
67
+ # application.rb
68
+ module My Application
69
+ class Application < Rails::Application
70
+
71
+ config.middleware.use 'Apartment::Elevators::Subdomain'
72
+ end
73
+ end
74
+
75
+ ## Config
76
+
77
+ The following config options should be set up in a Rails initializer such as:
78
+
79
+ config/initializers/apartment.rb
80
+
81
+ To set config options, add this to your initializer:
82
+
83
+ Apartment.configure do |config|
84
+ # set your options (described below) here
85
+ end
86
+
87
+ ### Excluding models
88
+
89
+ If you have some models that should always access the 'root' database, you can specify this by configuring
90
+ Apartment using `Apartment.configure`. This will yield a config object for you. You can set excluded models like so:
91
+
92
+ config.excluded_models = ["User", "Company"] # these models will not be multi-tenanted, but remain in the global (public) namespace
93
+
94
+ Note that a string representation of the model name is now the standard so that models are properly constantized when reloaded in development
95
+
96
+ ### Handling Environments
97
+
98
+ By default, when not using postgresql schemas, Apartment will prepend the environment to the database name
99
+ to ensure there is no conflict between your environments. This is mainly for the benefit of your development
100
+ and test environments. If you wish to turn this option off in production, you could do something like:
101
+
102
+ config.prepend_environment = !Rails.env.production?
103
+
104
+ ### Managing Migrations
105
+
106
+ In order to migrate all of your databases (or posgresql schemas) you need to provide a list
107
+ of dbs to Apartment. You can make this dynamic by providing a Proc object to be called on migrations.
108
+ This object should yield an array of string representing each database name. Example:
109
+
110
+ # Dynamically get database names to migrate
111
+ config.database_names = lambda{ Customer.select(:database_name).map(&:database_name) }
112
+
113
+ # Use a static list of database names for migrate
114
+ config.database_names = ['db1', 'db2']
115
+
116
+ You can then migration your databases using the rake task:
117
+
118
+ rake apartment:migrate
119
+
120
+ This basically invokes `Apartment::Database.migrate(#{db_name})` for each database name supplied
121
+ from `Apartment.database_names`
122
+
123
+ ### Delayed::Job
124
+
125
+ In order to make ActiveRecord models play nice with DJ and Apartment, include `Apartment::Delayed::Requirements` in any model that is being serialized by DJ. Also ensure that the `database` attribute (provided by Apartment::Delayed::Requirements) is set on this model *before* it is serialized, to ensure that when it is fetched again, it is done so in the proper Apartment db context. For example:
126
+
127
+ class SomeModel < ActiveRecord::Base
128
+ include Apartment::Delayed::Requirements
129
+ end
130
+
131
+ class SomeDJ
132
+
133
+ def initialize(model)
134
+ @model = model
135
+ @model.database = Apartment::Database.current_database
136
+ end
137
+
138
+ def perform
139
+ # do some stuff
140
+ end
141
+ end
142
+
143
+ ## Development
144
+
145
+ * The Local setup for development assumes that a root user with no password exists for both mysql and postgresl
146
+ * Rake tasks (see the Rakefile) will help you setup your dbs necessary to run tests
147
+ * Please issue pull requests to the `development` branch. All development happens here, master is used for releases
148
+ * Ensure that your code is accompanied with tests. No code will be merged without tests
149
+
150
+ ## TODO
151
+
152
+ * Shared examples for testing to ensure consistency across all adapters
@@ -0,0 +1,79 @@
1
+ require 'bundler' rescue 'You must `gem install bundler` and `bundle install` to run rake tasks'
2
+ Bundler.setup
3
+ Bundler::GemHelper.install_tasks
4
+
5
+ require "rspec"
6
+ require "rspec/core/rake_task"
7
+
8
+ RSpec::Core::RakeTask.new(:spec => "db:test:prepare") do |spec|
9
+ spec.pattern = "spec/**/*_spec.rb"
10
+ end
11
+
12
+ namespace :spec do
13
+
14
+ [:tasks, :unit, :adapters, :integration].each do |type|
15
+ RSpec::Core::RakeTask.new(type => :spec) do |spec|
16
+ spec.pattern = "spec/#{type}/**/*_spec.rb"
17
+ end
18
+ end
19
+
20
+ end
21
+
22
+ task :default => :spec
23
+
24
+ namespace :db do
25
+ namespace :test do
26
+ task :prepare => %w{postgres:drop_db postgres:build_db mysql:drop_db mysql:build_db}
27
+ end
28
+ end
29
+
30
+ namespace :postgres do
31
+ require 'active_record'
32
+ require "#{File.join(File.dirname(__FILE__), 'spec', 'support', 'config')}"
33
+
34
+ desc 'Build the PostgreSQL test databases'
35
+ task :build_db do
36
+ %x{ createdb -E UTF8 #{pg_config['database']} } rescue "test db already exists"
37
+ ActiveRecord::Base.establish_connection pg_config
38
+ ActiveRecord::Migrator.migrate('spec/dummy/db/migrate')
39
+ end
40
+
41
+ desc "drop the PostgreSQL test database"
42
+ task :drop_db do
43
+ puts "dropping database #{pg_config['database']}"
44
+ %x{ dropdb #{pg_config['database']} }
45
+ end
46
+
47
+ end
48
+
49
+ namespace :mysql do
50
+ require 'active_record'
51
+ require "#{File.join(File.dirname(__FILE__), 'spec', 'support', 'config')}"
52
+
53
+ desc 'Build the MySQL test databases'
54
+ task :build_db do
55
+ %x{ mysqladmin -u root create #{my_config['database']} } rescue "test db already exists"
56
+ ActiveRecord::Base.establish_connection my_config
57
+ ActiveRecord::Migrator.migrate('spec/dummy/db/migrate')
58
+ end
59
+
60
+ desc "drop the MySQL test database"
61
+ task :drop_db do
62
+ puts "dropping database #{my_config['database']}"
63
+ %x{ mysqladmin -u root drop #{my_config['database']} --force}
64
+ end
65
+
66
+ end
67
+
68
+ # TODO clean this up
69
+ def config
70
+ Apartment::Test.config['connections']
71
+ end
72
+
73
+ def pg_config
74
+ config['postgresql']
75
+ end
76
+
77
+ def my_config
78
+ config['mysql']
79
+ end
@@ -0,0 +1,32 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $: << File.expand_path("../lib", __FILE__)
3
+ require "apartment/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = %q{dr-apartment}
7
+ s.version = Apartment::VERSION
8
+
9
+ s.authors = ["Ryan Brunner", "Brad Robertson"]
10
+ s.summary = %q{A Ruby gem for managing database multitenancy in Rails applications}
11
+ s.description = %q{Apartment allows Rails applications to deal with database multitenancy}
12
+ s.email = %w{ryan@ryanbrunner.com bradleyrobertson@gmail.com}
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {spec}/*`.split("\n")
15
+
16
+ s.homepage = %q{http://github.com/bradrobertson/apartment}
17
+ s.licenses = ["MIT"]
18
+ s.require_paths = ["lib"]
19
+ s.rubygems_version = %q{1.3.7}
20
+
21
+ # s.add_dependency 'rails', '~> 3.1.2'
22
+ # s.add_development_dependency 'rake', '~> 0.8.7'
23
+ # s.add_development_dependency 'sqlite3'
24
+ # s.add_development_dependency 'rspec', '~> 2.6.0'
25
+ # s.add_development_dependency 'rspec-rails', '~> 2.6.1'
26
+ # s.add_development_dependency 'capybara', '1.0.0'
27
+ # s.add_development_dependency 'pg', '~> 0.11.0'
28
+ # s.add_development_dependency 'mysql2', '~> 0.3.7'
29
+ # s.add_development_dependency "silent-postgres", "~> 0.1.1"
30
+ # s.add_development_dependency 'delayed_job', '~> 2.1.4'
31
+ # s.add_development_dependency 'activerecord-jdbcpostgresql-adapter', '~> 1.2.1'
32
+ end
@@ -0,0 +1,69 @@
1
+ require 'apartment/railtie' if defined?(Rails)
2
+
3
+ module Apartment
4
+
5
+ class << self
6
+ attr_accessor :use_postgres_schemas, :seed_after_create, :prepend_environment
7
+ attr_writer :database_names, :excluded_models
8
+
9
+ # configure apartment with available options
10
+ def configure
11
+ yield self if block_given?
12
+ end
13
+
14
+ # Be careful not to use `return` here so both Proc and lambda can be used without breaking
15
+ def database_names
16
+ @database_names.respond_to?(:call) ? @database_names.call : @database_names
17
+ end
18
+
19
+ # Default to none
20
+ def excluded_models
21
+ @excluded_models || []
22
+ end
23
+
24
+ end
25
+
26
+ autoload :Database, 'apartment/database'
27
+ autoload :Migrator, 'apartment/migrator'
28
+ autoload :Reloader, 'apartment/reloader'
29
+
30
+ module Adapters
31
+ autoload :AbstractAdapter, 'apartment/adapters/abstract_adapter'
32
+ # Specific adapters will be loaded dynamically based on adapter in config
33
+ end
34
+
35
+ module Elevators
36
+ autoload :Subdomain, 'apartment/elevators/subdomain'
37
+ end
38
+
39
+ module Delayed
40
+
41
+ autoload :Requirements, 'apartment/delayed_job/requirements'
42
+
43
+ module Job
44
+ autoload :Hooks, 'apartment/delayed_job/hooks'
45
+ end
46
+ end
47
+
48
+ # Exceptions
49
+ class ApartmentError < StandardError; end
50
+
51
+ # Raised when apartment cannot find the adapter specified in <tt>config/database.yml</tt>
52
+ class AdapterNotFound < ApartmentError; end
53
+
54
+ # Raised when database cannot find the specified database
55
+ class DatabaseNotFound < ApartmentError; end
56
+
57
+ # Raised when trying to create a database that already exists
58
+ class DatabaseExists < ApartmentError; end
59
+
60
+ # Raised when database cannot find the specified schema
61
+ class SchemaNotFound < ApartmentError; end
62
+
63
+ # Raised when trying to create a schema that already exists
64
+ class SchemaExists < ApartmentError; end
65
+
66
+ # Raised when an ActiveRecord object does not have the required database field on it
67
+ class DJSerializationError < ApartmentError; end
68
+
69
+ end