innkeeper 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.pryrc +3 -0
  4. data/.travis.yml +15 -0
  5. data/Appraisals +20 -0
  6. data/Gemfile +10 -0
  7. data/Guardfile +24 -0
  8. data/HISTORY.md +337 -0
  9. data/README.md +485 -0
  10. data/Rakefile +92 -0
  11. data/TODO.md +51 -0
  12. data/gemfiles/rails_5_1.gemfile +12 -0
  13. data/innkeeper.gemspec +42 -0
  14. data/lib/generators/innkeeper/install/USAGE +5 -0
  15. data/lib/generators/innkeeper/install/install_generator.rb +10 -0
  16. data/lib/generators/innkeeper/install/templates/innkeeper.rb +76 -0
  17. data/lib/innkeeper.rb +110 -0
  18. data/lib/innkeeper/adapters/abstract_adapter.rb +172 -0
  19. data/lib/innkeeper/adapters/mysql2_adapter.rb +47 -0
  20. data/lib/innkeeper/adapters/postgresql_adapter.rb +112 -0
  21. data/lib/innkeeper/console.rb +12 -0
  22. data/lib/innkeeper/deprecation.rb +10 -0
  23. data/lib/innkeeper/elevators/domain.rb +20 -0
  24. data/lib/innkeeper/elevators/generic.rb +32 -0
  25. data/lib/innkeeper/elevators/host_hash.rb +22 -0
  26. data/lib/innkeeper/elevators/subdomain.rb +62 -0
  27. data/lib/innkeeper/migrator.rb +33 -0
  28. data/lib/innkeeper/railtie.rb +56 -0
  29. data/lib/innkeeper/resolvers/abstract.rb +15 -0
  30. data/lib/innkeeper/resolvers/database.rb +11 -0
  31. data/lib/innkeeper/resolvers/schema.rb +14 -0
  32. data/lib/innkeeper/tasks/enhancements.rb +36 -0
  33. data/lib/innkeeper/tenant.rb +47 -0
  34. data/lib/innkeeper/version.rb +3 -0
  35. data/lib/tasks/innkeeper.rake +128 -0
  36. data/notes.md +31 -0
  37. data/test/config_test.rb +52 -0
  38. data/test/databases.yml.sample +37 -0
  39. data/test/decorator_test.rb +21 -0
  40. data/test/domain_elevator_test.rb +38 -0
  41. data/test/dummy/Rakefile +7 -0
  42. data/test/dummy/app/controllers/application_controller.rb +6 -0
  43. data/test/dummy/app/helpers/application_helper.rb +2 -0
  44. data/test/dummy/app/models/company.rb +3 -0
  45. data/test/dummy/app/models/user.rb +3 -0
  46. data/test/dummy/app/views/application/index.html.erb +1 -0
  47. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  48. data/test/dummy/config.ru +4 -0
  49. data/test/dummy/config/application.rb +49 -0
  50. data/test/dummy/config/boot.rb +11 -0
  51. data/test/dummy/config/database.yml.sample +38 -0
  52. data/test/dummy/config/environment.rb +5 -0
  53. data/test/dummy/config/environments/development.rb +27 -0
  54. data/test/dummy/config/environments/production.rb +51 -0
  55. data/test/dummy/config/environments/test.rb +34 -0
  56. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  57. data/test/dummy/config/initializers/inflections.rb +10 -0
  58. data/test/dummy/config/initializers/innkeeper.rb +4 -0
  59. data/test/dummy/config/initializers/mime_types.rb +5 -0
  60. data/test/dummy/config/initializers/secret_token.rb +7 -0
  61. data/test/dummy/config/initializers/session_store.rb +8 -0
  62. data/test/dummy/config/locales/en.yml +5 -0
  63. data/test/dummy/config/routes.rb +3 -0
  64. data/test/dummy/db/schema.rb +19 -0
  65. data/test/dummy/db/seeds.rb +5 -0
  66. data/test/dummy/db/seeds/import.rb +5 -0
  67. data/test/dummy/public/404.html +26 -0
  68. data/test/dummy/public/422.html +26 -0
  69. data/test/dummy/public/500.html +26 -0
  70. data/test/dummy/public/favicon.ico +0 -0
  71. data/test/dummy/public/stylesheets/.gitkeep +0 -0
  72. data/test/dummy/script/rails +6 -0
  73. data/test/excluded_models_test.rb +32 -0
  74. data/test/generic_elevator_test.rb +63 -0
  75. data/test/host_hash_elevator_test.rb +42 -0
  76. data/test/innkeeper_test.rb +96 -0
  77. data/test/mocks/adapter_mock.rb +11 -0
  78. data/test/multithreading_test.rb +37 -0
  79. data/test/mysql2_adapter_test.rb +17 -0
  80. data/test/postgresql_adapter_test.rb +39 -0
  81. data/test/railtie_test.rb +31 -0
  82. data/test/rake_task_test.rb +57 -0
  83. data/test/resolver_test.rb +21 -0
  84. data/test/shared/shared_adapter_tests.rb +95 -0
  85. data/test/subdomain_elevator_test.rb +75 -0
  86. data/test/test_helper.rb +24 -0
  87. metadata +325 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 560d0c28790b3fe5ca5be5aaf652504314fc25c3678ceb57759ac443592b11de
4
+ data.tar.gz: 37cfd45f70289bbda7bfb73ee868d3b379eaf108ac51581f7a5e41e486bbccc0
5
+ SHA512:
6
+ metadata.gz: 5bfd05b70e590c273e78249f6c74d859038d1711f4e551e775dc8cd5b176c6020596e0362f12708dd5216d61e630bb14df8e1fa2cd1a3719dbbd6797272eec78
7
+ data.tar.gz: 4256631c4d73af87b7f38f27493cb81ec4b2837faf8a47955067736bd4d97fe290eadcc08990304dc163da1809eef021e16f7aeb9b97b7f98ae3a62829fcaee8
@@ -0,0 +1,15 @@
1
+ *.gem
2
+ .bundle
3
+ *.lock
4
+ gemfiles/*.lock
5
+ pkg/*
6
+ *.log
7
+ .idea
8
+ *.sw[pno]
9
+ test/databases.yml
10
+ test/dummy/config/database.yml
11
+ cookbooks
12
+ tmp
13
+ test/dummy/db/*.sqlite3
14
+ .DS_Store
15
+ test/debug.log
data/.pryrc ADDED
@@ -0,0 +1,3 @@
1
+ if defined?(Rails) && Rails.env
2
+ extend Rails::ConsoleMethods
3
+ end
@@ -0,0 +1,15 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.4
4
+ - 2.3.1
5
+ - jruby-9.0.5.0
6
+ gemfile:
7
+ - gemfiles/rails_5_1.gemfile
8
+ bundler_args: --without local
9
+ before_install:
10
+ - gem install bundler -v '> 1.5.0'
11
+ env:
12
+ RUBY_GC_MALLOC_LIMIT: 90000000
13
+ RUBY_FREE_MIN: 200000
14
+ matrix:
15
+ fast_finish: true
@@ -0,0 +1,20 @@
1
+ appraise "rails-3-2" do
2
+ gem "rails", "~> 3.2"
3
+ gem "test-unit", "~> 3.0"
4
+ end
5
+
6
+ appraise "rails-4-0" do
7
+ gem "rails", "~> 4.0"
8
+ end
9
+
10
+ appraise "rails-4-1" do
11
+ gem "rails", "~> 4.1"
12
+ end
13
+
14
+ appraise "rails-4-2" do
15
+ gem "rails", "~> 4.2"
16
+ end
17
+
18
+ appraise "rails-5-0" do
19
+ gem "rails", "~> 5.0"
20
+ end
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'rails', '>= 5.1.0'
6
+
7
+ group :local do
8
+ gem 'pry'
9
+ gem 'guard-rspec', '~> 4.2'
10
+ end
@@ -0,0 +1,24 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :rspec do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/innkeeper/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
7
+ watch(%r{^lib/innkeeper/(.+)\.rb$}) { |m| "spec/integration/#{m[1]}_spec.rb" }
8
+ watch('spec/spec_helper.rb') { "spec" }
9
+
10
+ # # Rails example
11
+ # watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
12
+ # watch(%r{^app/(.*)(\.erb|\.haml|\.slim)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
13
+ # watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
14
+ # watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
15
+ # watch('config/routes.rb') { "spec/routing" }
16
+ # watch('app/controllers/application_controller.rb') { "spec/controllers" }
17
+
18
+ # # Capybara features specs
19
+ # watch(%r{^app/views/(.+)/.*\.(erb|haml|slim)$}) { |m| "spec/features/#{m[1]}_spec.rb" }
20
+
21
+ # # Turnip features and steps
22
+ # watch(%r{^spec/acceptance/(.+)\.feature$})
23
+ # watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
24
+ end
@@ -0,0 +1,337 @@
1
+ # 1.2.0
2
+ * July 28, 2016
3
+
4
+ - Official Rails 5 support
5
+
6
+ # 1.1.0
7
+ * May 26, 2016
8
+
9
+ - Reset tenant after each request
10
+ - [Support callbacks](https://github.com/influitive/apartment/commit/ff9c9d092a781026502f5997c0bbedcb5748bc83) on switch [cbeer]
11
+ - Preliminary support for [separate database hosts](https://github.com/influitive/apartment/commit/abdffbf8cd9fba87243f16c86390da13e318ee1f) [apneadiving]
12
+
13
+ # 1.0.2
14
+ * July 2, 2015
15
+
16
+ - Fix pg_dump env vars - pull/208 [MitinPavel]
17
+ - Allow custom seed data file - pull/234 [typeoneerror]
18
+
19
+ # 1.0.1
20
+ * April 28, 2015
21
+
22
+ - Fix `Apartment::Deprecation` which was rescuing all exceptions
23
+
24
+ # 1.0.0
25
+ * Feb 3, 2015
26
+
27
+ - [BREAKING CHANGE] `Apartment::Tenant.process` is deprecated in favour of `Apartment::Tenant.switch`
28
+ - [BREAKING CHANGE] `Apartment::Tenant.switch` without a block is deprecated in favour of `Apartment::Tenant.switch!`
29
+ - Raise proper `TenantNotFound`, `TenantExists` exceptions
30
+ - Deprecate old `SchemaNotFound`, `DatabaseNotFound` exceptions
31
+
32
+ # 0.26.1
33
+ * Jan 13, 2015
34
+
35
+ - Fixed [schema quoting bug](https://github.com/influitive/apartment/issues/198#issuecomment-69782651) [jonsgreen]
36
+
37
+ # 0.26.0
38
+ * Jan 5, 2015
39
+
40
+ - Rails 4.2 support
41
+
42
+ # 0.25.2
43
+ * Sept 8, 2014
44
+
45
+ - Heroku fix on `assets:precompile` - pull/169 [rabbitt]
46
+
47
+ # 0.25.1
48
+ * July 17, 2014
49
+
50
+ - Fixed a few vestiges of Apartment::Database
51
+
52
+ # 0.25.0
53
+ * July 3, 2014
54
+
55
+ - [BREAKING CHANGE] - `Apartment::Database` is not deprecated in favour of
56
+ `Apartment::Tenant`
57
+ - ActiveRecord (and Rails) 4.1 now supported
58
+ - A new sql based adapter that dumps the schema using sql
59
+
60
+ # 0.24.3
61
+ * March 5, 2014
62
+
63
+ - Rake enhancements weren't removed from the generator template
64
+
65
+ # 0.24.2
66
+ * February 24, 2014
67
+
68
+ - Better warnings if `apartment:migrate` is run
69
+
70
+ # 0.24.1
71
+ * February 21, 2014
72
+
73
+ - requiring `apartment/tasks/enhancements` in an initializer doesn't work
74
+ - One can disable tenant migrations using `Apartment.db_migrate_tenants = false` in the Rakefile
75
+
76
+ # 0.24
77
+ * February 21, 2014 (In honour of the Women's Gold Medal in Hockey at Sochi)
78
+
79
+ - [BREAKING CHANGE] `apartment:migrate` task no longer depends on `db:migrate`
80
+ - Instead, you can `require 'apartment/tasks/enhancements'` in your Apartment initializer
81
+ - This will enhance `rake db:migrate` to also run `apartment:migrate`
82
+ - You can now forget about ever running `apartment:migrate` again
83
+ - Numerous deprecations for things referencing the word 'database'
84
+ - This is an ongoing effort to completely replace 'database' with 'tenant' as a better abstraction
85
+ - Note the obvious `Apartment::Database` still exists but will hopefully become `Apartment::Tenant` soon
86
+
87
+ # 0.23.2
88
+ * January 9, 2014
89
+
90
+ - Increased visibility of #parse_database_name warning
91
+
92
+ # 0.23.1
93
+ * January 8, 2014
94
+
95
+ - Schema adapters now initialize with default and persistent schemas
96
+ - Deprecated Apartment::Elevators#parse_database_name
97
+
98
+ # 0.23.0
99
+ * August 21, 2013
100
+
101
+ - Subdomain Elevator now allows for exclusions
102
+ - Delayed::Job has been completely removed
103
+
104
+ # 0.22.1
105
+ * August 21, 2013
106
+
107
+ - Fix bug where if your ruby process importing the database schema is run
108
+ from a directory other than the app root, Apartment wouldn't know what
109
+ schema_migrations to insert into the database (Rails only)
110
+
111
+ # 0.22.0
112
+ * June 9, 2013
113
+
114
+ - Numerous bug fixes:
115
+ - Mysql reset could connect to wrong database [eric88]
116
+ - Postgresql schema names weren't quoted properly [gdott9]
117
+ - Fixed error message on SchemaNotFound in `process`
118
+ - HostHash elevator allows mapping host based on hash contents [gdott9]
119
+ - Official Sidekiq support with the [apartment-sidekiq gem](https://github.com/influitive/apartment-sidekiq)
120
+
121
+
122
+ # 0.21.1
123
+ * May 31, 2013
124
+
125
+ - Clearing the AR::QueryCache after switching databases.
126
+ - Fixes issue with stale model being loaded for schema adapters
127
+
128
+ # 0.21.0
129
+ * April 24, 2013
130
+
131
+ - JDBC support!! [PetrolMan]
132
+
133
+ # 0.20.0
134
+ * Feb 6, 2013
135
+
136
+ - Mysql now has a 'schema like' option to perform like Postgresql (default)
137
+ - This should be significantly more performant than using connections
138
+ - Psych is now supported for Delayed::Job yaml parsing
139
+
140
+ # 0.19.2
141
+ * Jan 30, 2013
142
+
143
+ - Database schema file can now be set manually or skipped altogether
144
+
145
+ # 0.19.1
146
+ * Jan 30, 2013
147
+
148
+ - Allow schema.rb import file to be specified in config or skip schema.rb import altogether
149
+
150
+ # 0.19.0
151
+ * Dec 29, 2012
152
+
153
+ - Apartment is now threadsafe
154
+ - New postgis adapter [zonpantli]
155
+ - Removed ActionDispatch dependency for use with Rack apps (regression)
156
+
157
+ # 0.18.0
158
+ * Nov 27, 2012
159
+
160
+ - Added `append_environment` config option [virtualstaticvoid]
161
+ - Cleaned up the readme and generator documentation
162
+ - Added `connection_class` config option [smashtank]
163
+ - Fixed a [bug](https://github.com/influitive/apartment/issues/17#issuecomment-10758327) in pg adapter when missing schema
164
+
165
+ # 0.17.1
166
+ * Oct 30, 2012
167
+
168
+ - Fixed a bug where switching to an unknown db in mysql2 would crash the app [Frodotus]
169
+
170
+ # 0.17.0
171
+ * Sept 26, 2012
172
+
173
+ - Apartment has [a new home!](https://github.com/influitive/apartment)
174
+ - Support Sidekiq hooks to switch dbs [maedhr]
175
+ - Allow VERSION to be used on apartment:migrate [Bhavin Kamani]
176
+
177
+ # 0.16.0
178
+ * June 1, 2012
179
+
180
+ - Apartment now supports a default_schema to be set, rather than relying on ActiveRecord's default schema_search_path
181
+ - Additional schemas can always be maintained in the schema_search_path by configuring persistent_schemas [ryanbrunner]
182
+ - This means Hstore is officially supported!!
183
+ - There is now a full domain based elevator to switch dbs based on the whole domain [lcowell]
184
+ - There is now a generic elevator that takes a Proc to switch dbs based on the return value of that proc.
185
+
186
+ # 0.15.0
187
+ * March 18, 2012
188
+
189
+ - Remove Rails dependency, Apartment can now be used with any Rack based framework using ActiveRecord
190
+
191
+ # 0.14.4
192
+ * March 8, 2012
193
+
194
+ - Delayed::Job Hooks now return to the previous database, rather than resetting
195
+
196
+ # 0.14.3
197
+ * Feb 21, 2012
198
+
199
+ - Fix yaml serialization of non DJ models
200
+
201
+ # 0.14.2
202
+ * Feb 21, 2012
203
+
204
+ - Fix Delayed::Job yaml encoding with Rails > 3.0.x
205
+
206
+ # 0.14.1
207
+ * Dec 13, 2011
208
+
209
+ - Fix ActionDispatch::Callbacks deprecation warnings
210
+
211
+ # 0.14.0
212
+ * Dec 13, 2011
213
+
214
+ - Rails 3.1 Support
215
+
216
+ # 0.13.1
217
+ * Nov 8, 2011
218
+
219
+ - Reset prepared statement cache for rails 3.1.1 before switching dbs when using postgresql schemas
220
+ - Only necessary until the next release which will be more schema aware
221
+
222
+ # 0.13.0
223
+ * Oct 25, 2011
224
+
225
+ - `process` will now rescue with reset if the previous schema/db is no longer available
226
+ - `create` now takes an optional block which allows you to process within the newly created db
227
+ - 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
228
+
229
+ # 0.12.0
230
+ * Oct 4, 2011
231
+
232
+ - Added a `drop` method for removing databases/schemas
233
+ - Refactored abstract adapter to further remove duplication in concrete implementations
234
+ - Excluded models now take string references so they are properly reloaded in development
235
+ - Better silencing of `schema.rb` loading using `verbose` flag
236
+
237
+ # 0.11.1
238
+ * Sep 22, 2011
239
+
240
+ - Better use of Railties for initializing apartment
241
+ - The following changes were necessary as I haven't figured out how to properly hook into Rails reloading
242
+ - Added reloader middleware in development to init Apartment on each request
243
+ - Override `reload!` in console to also init Apartment
244
+
245
+ # 0.11.0
246
+ * Sep 20, 2011
247
+
248
+ - Excluded models no longer use a different connection when using postgresql schemas. Instead their table_name is prefixed with `public.`
249
+
250
+ # 0.10.3
251
+ * Sep 20, 2011
252
+
253
+ - Fix improper raising of exceptions on create and reset
254
+
255
+ # 0.10.2
256
+ * Sep 15, 2011
257
+
258
+ - Remove all the annoying logging for loading db schema and seeding on create
259
+
260
+ # 0.10.1
261
+ * Aug 11, 2011
262
+
263
+ - Fixed bug in DJ where new objects (that hadn't been pulled from the db) didn't have the proper database assigned
264
+
265
+ # 0.10.0
266
+ * July 29, 2011
267
+
268
+ - Added better support for Delayed Job
269
+ - New config option that enables Delayed Job wrappers
270
+ - 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
271
+
272
+ # 0.9.2
273
+ * July 4, 2011
274
+
275
+ - Migrations now run associated rails migration fully, fixes schema.rb not being reloaded after migrations
276
+
277
+ # 0.9.1
278
+ * June 24, 2011
279
+
280
+ - Hooks now take the payload object as an argument to fetch the proper db for DJ hooks
281
+
282
+ # 0.9.0
283
+ * June 23, 2011
284
+
285
+ - Added module to provide delayed job hooks
286
+
287
+ # 0.8.0
288
+ * June 23, 2011
289
+
290
+ - Added #current_database which will return the current database (or schema) name
291
+
292
+ # 0.7.0
293
+ * June 22, 2011
294
+
295
+ - Added apartment:seed rake task for seeding all dbs
296
+
297
+ # 0.6.0
298
+ * June 21, 2011
299
+
300
+ - Added #process to connect to new db, perform operations, then ensure a reset
301
+
302
+ # 0.5.1
303
+ * June 21, 2011
304
+
305
+ - Fixed db migrate up/down/rollback
306
+ - added db:redo
307
+
308
+ # 0.5.0
309
+ * June 20, 2011
310
+
311
+ - Added the concept of an "Elevator", a rack based strategy for db switching
312
+ - Added the Subdomain Elevator middleware to enabled db switching based on subdomain
313
+
314
+ # 0.4.0
315
+ * June 14, 2011
316
+
317
+ - Added `configure` method on Apartment instead of using yml file, allows for dynamic setting of db names to migrate for rake task
318
+ - Added `seed_after_create` config option to import seed data to new db on create
319
+
320
+ # 0.3.0
321
+ * June 10, 2011
322
+
323
+ - Added full support for database migration
324
+ - Added in method to establish new connection for excluded models on startup rather than on each switch
325
+
326
+ # 0.2.0
327
+ * June 6, 2011 *
328
+
329
+ - Refactor to use more rails/active_support functionality
330
+ - Refactor config to lazily load apartment.yml if exists
331
+ - Remove OStruct and just use hashes for fetching methods
332
+ - Added schema load on create instead of migrating from scratch
333
+
334
+ # 0.1.3
335
+ * March 30, 2011 *
336
+
337
+ - Original pass from Ryan
@@ -0,0 +1,485 @@
1
+ # Innkeeper
2
+
3
+ [![Code Climate](https://codeclimate.com/github/cpoms/innkeeper.png)](https://codeclimate.com/github/cpoms/innkeeper)
4
+ [![Build Status](https://secure.travis-ci.org/cpoms/innkeeper.png?branch=development)](http://travis-ci.org/cpoms/innkeeper)
5
+
6
+ *Multitenancy for Rails and ActiveRecord*
7
+
8
+ Innkeeper provides tools to help you deal with multiple tenants in your Rails
9
+ application. If you need to have certain data sequestered based on account or
10
+ company, but still allow some data to exist in a common tenant, Innkeeper can
11
+ help.
12
+
13
+ ## Installation
14
+
15
+ ### Rails
16
+
17
+ Add the following to your Gemfile:
18
+
19
+ ```ruby
20
+ gem 'innkeeper'
21
+ ```
22
+
23
+ Then generate your `Innkeeper` config file using
24
+
25
+ ```ruby
26
+ bundle exec rails generate innkeeper:install
27
+ ```
28
+
29
+ This will create a `config/initializers/innkeeper.rb` initializer file.
30
+ Configure as needed using the docs below.
31
+
32
+ That's all you need to set up the Innkeeper libraries. If you want to switch
33
+ tenants on a per-user basis, look under "Usage - Switching tenants per request",
34
+ below.
35
+
36
+ ## Usage
37
+
38
+ ### Creating new Tenants
39
+
40
+ Before you can switch to a new innkeeper tenant, you will need to create it.
41
+ Whenever you need to create a new tenant, you can run the following command:
42
+
43
+ ```ruby
44
+ Innkeeper::Tenant.create('tenant_name')
45
+ ```
46
+
47
+ If you're using PostgreSQL, this will create the database and schema from the
48
+ derived tenant configuration. For example, if you're using the Schema resolver,
49
+ this will create a schema named 'tenant_name', assuming you're not using a
50
+ decorator. If you're customising the tenant name with a decorator, it is the
51
+ decorated name that will be used.
52
+
53
+ When you create a new tenant, the schema is loaded on to that tenant, so it will
54
+ be up to date when create returns.
55
+
56
+ ### Switching Tenants
57
+
58
+ To switch tenants using Innkeeper, use the following command:
59
+
60
+ ```ruby
61
+ Innkeeper::Tenant.switch!('tenant_name')
62
+ ```
63
+
64
+ When switch is called, all requests coming to ActiveRecord will be routed to the
65
+ tenant you specify (with the exception of excluded models, see below). To return
66
+ to the 'root' tenant, call switch with no arguments.
67
+
68
+ ### Switching Tenants per request
69
+
70
+ You can have Innkeeper route to the appropriate tenant by adding some Rack
71
+ middleware. Innkeeper can support many different "Elevators" that can take care
72
+ of this routing to your data.
73
+
74
+ **NOTE: when switching tenants per-request, keep in mind that the order of your
75
+ Rack middleware is important.** See the
76
+ [Middleware Considerations](#middleware-considerations) section for more.
77
+
78
+ The initializer above will generate the appropriate code for the Subdomain
79
+ elevator by default. You can see this in `config/initializers/innkeeper.rb`
80
+ after running that generator. If you're *not* using the generator, you can
81
+ specify your elevator below. Note that in this case you will **need** to require
82
+ the elevator manually in your `application.rb` like so:
83
+
84
+ ```ruby
85
+ # config/application.rb
86
+ require 'innkeeper/elevators/subdomain' # or 'domain' or 'generic'
87
+ ```
88
+
89
+ #### Switch on subdomain
90
+
91
+ In house, we use the subdomain elevator, which analyzes the subdomain of the
92
+ request and switches to a tenant schema of the same name. It can be used like
93
+ so:
94
+
95
+ ```ruby
96
+ # application.rb
97
+ module MyApplication
98
+ class Application < Rails::Application
99
+ config.middleware.use 'Innkeeper::Elevators::Subdomain'
100
+ end
101
+ end
102
+ ```
103
+
104
+ If you want to exclude a domain, for example if you don't want your application
105
+ to treat www like a subdomain, in an initializer in your application, you can
106
+ set the following:
107
+
108
+ ```ruby
109
+ # config/initializers/innkeeper/subdomain_exclusions.rb
110
+ Innkeeper::Elevators::Subdomain.excluded_subdomains = ['www']
111
+ ```
112
+
113
+ This functions much in the same way as Innkeeper.excluded_models. This example
114
+ will prevent switching your tenant when the subdomain is www. Handy for
115
+ subdomains like: "public", "www", and "admin" :)
116
+
117
+ #### Switch on domain
118
+
119
+ To switch based on full domain (excluding subdomains *ie 'www'* and top level
120
+ domains *ie '.com'* ) use the following:
121
+
122
+ ```ruby
123
+ # application.rb
124
+ module MyApplication
125
+ class Application < Rails::Application
126
+ config.middleware.use 'Innkeeper::Elevators::Domain'
127
+ end
128
+ end
129
+ ```
130
+
131
+ #### Switch on full host using a hash
132
+
133
+ To switch based on full host with a hash to find corresponding tenant name use
134
+ the following:
135
+
136
+ ```ruby
137
+ # application.rb
138
+ module MyApplication
139
+ class Application < Rails::Application
140
+ config.middleware.use 'Innkeeper::Elevators::HostHash', {'example.com' => 'example_tenant'}
141
+ end
142
+ end
143
+ ```
144
+
145
+ #### Custom Elevator
146
+
147
+ A Generic Elevator exists that allows you to pass a `Proc` (or anything that
148
+ responds to `call`) to the middleware. This Object will be passed in an
149
+ `ActionDispatch::Request` object when called for you to do your magic. Innkeeper
150
+ will use the return value of this proc to switch to the appropriate tenant. Use
151
+ like so:
152
+
153
+ ```ruby
154
+ # application.rb
155
+ module MyApplication
156
+ class Application < Rails::Application
157
+ # Obviously not a contrived example
158
+ config.middleware.use 'Innkeeper::Elevators::Generic', Proc.new { |request| request.host.reverse }
159
+ end
160
+ end
161
+ ```
162
+
163
+ Your other option is to subclass the Generic elevator and implement your own
164
+ switching mechanism. This is exactly how the other elevators work. Look at the
165
+ `subdomain.rb` elevator to get an idea of how this should work. Basically all
166
+ you need to do is subclass the generic elevator and implement your own
167
+ `parse_tenant_name` method that will ultimately return the name of the tenant
168
+ based on the request being made. It *could* look something like this:
169
+
170
+ ```ruby
171
+ # app/middleware/my_custom_elevator.rb
172
+ class MyCustomElevator < Innkeeper::Elevators::Generic
173
+
174
+ # @return {String} - The tenant to switch to
175
+ def parse_tenant_name(request)
176
+ # request is an instance of Rack::Request
177
+
178
+ # example: look up some tenant from the db based on this request
179
+ tenant_name = SomeModel.from_request(request)
180
+
181
+ return tenant_name
182
+ end
183
+ end
184
+ ```
185
+
186
+ #### Middleware Considerations
187
+
188
+ In the examples above, we show the Innkeeper middleware being appended to the
189
+ Rack stack with
190
+
191
+ ```ruby
192
+ Rails.application.config.middleware.use 'Innkeeper::Elevators::Subdomain'
193
+ ```
194
+
195
+ By default, the Subdomain middleware switches into a Tenant based on the
196
+ subdomain at the beginning of the request, and when the request is finished, it
197
+ switches back to the "public" Tenant. This happens in the
198
+ [Generic](https://github.com/cpoms/innkeeper/blob/development/lib/innkeeper/elevators/generic.rb#L22)
199
+ elevator, so all elevators that inherit from this elevator will operate as such.
200
+
201
+ It's also good to note that Innkeeper switches back to the "public" tenant any
202
+ time an error is raised in your application.
203
+
204
+ This works okay for simple applications, but it's important to consider that you
205
+ may want to maintain the "selected" tenant through different parts of the Rack
206
+ application stack. For example, the
207
+ [Devise](https://github.com/plataformatec/devise) gem adds the `Warden::Manager`
208
+ middleware at the end of the stack in the examples above, our
209
+ `Innkeeper::Elevators::Subdomain` middleware would come after it. Trouble is,
210
+ Innkeeper resets the selected tenant after the request is finish, so some
211
+ edirects (e.g. authentication) in Devise will be run in the context of the
212
+ "public" tenant. The same issue would also effect a gem such as the
213
+ [better_errors](https://github.com/charliesome/better_errors) gem which inserts
214
+ a middleware quite early in the Rails middleware stack.
215
+
216
+ To resolve this issue, consider adding the Innkeeper middleware at a location
217
+ in the Rack stack that makes sense for your needs, e.g.:
218
+
219
+ ```ruby
220
+ Rails.application.config.middleware.insert_before 'Warden::Manager', 'Innkeeper::Elevators::Subdomain'
221
+ ```
222
+
223
+ Now work done in the Warden middleware is wrapped in the
224
+ `Innkeeper::Tenant.switch` context started in the Generic elevator.
225
+
226
+ ### Dropping Tenants
227
+
228
+ To drop tenants using Innkeeper, use the following command:
229
+
230
+ ```ruby
231
+ Innkeeper::Tenant.drop('tenant_name')
232
+ ```
233
+
234
+ When method is called, the schema is dropped and all data from itself will be
235
+ lost. Be careful with this method.
236
+
237
+ ## Config
238
+
239
+ The following config options should be set up in a Rails initializer such as:
240
+
241
+ config/initializers/innkeeper.rb
242
+
243
+ To set config options, add this to your initializer:
244
+
245
+ ```ruby
246
+ Innkeeper.configure do |config|
247
+ # set your options (described below) here
248
+ end
249
+ ```
250
+
251
+ ### Excluding models
252
+
253
+ If you have some models that should always access the 'public' tenant, you can
254
+ specify this by configuring Innkeeper using `Innkeeper.configure`. This will
255
+ yield a config object for you. You can set excluded models like so:
256
+
257
+ ```ruby
258
+ config.excluded_models = ["User", "Company"] # these models will not be multi-tenanted, but remain in the global (public) namespace
259
+ ```
260
+
261
+ Note that a string representation of the model name is now the standard so that
262
+ models are properly constantized when reloaded in development.
263
+
264
+ Rails will always access the 'public' tenant when accessing these models, but
265
+ note that tables will be created in all schemas. This may not be ideal, but its
266
+ done this way because otherwise rails wouldn't be able to properly generate the
267
+ schema.rb file.
268
+
269
+ > **NOTE - Many-To-Many Excluded Models:**
270
+ > Since model exclusions must come from referencing a real ActiveRecord model,
271
+ `has_and_belongs_to_many` is NOT supported. In order to achieve a many-to-many
272
+ relationship for excluded models, you MUST use `has_many :through`. This way you
273
+ can reference the join model in the excluded models configuration.
274
+
275
+ ### Postgresql Schemas
276
+
277
+ ## Providing a Different default_schema
278
+
279
+ By default, ActiveRecord will use `"$user", public` as the default
280
+ `schema_search_path`. This can be modified if you wish to use a different
281
+ default schema be setting:
282
+
283
+ ```ruby
284
+ config.default_schema = "some_other_schema"
285
+ ```
286
+
287
+ With that set, all excluded models will use this schema as the table name prefix
288
+ instead of `public` and `reset` on `Innkeeper::Tenant` will return to this
289
+ schema as well.
290
+
291
+ ## Persistent Schemas
292
+
293
+ Innkeeper will normally just switch the `schema_search_path` whole hog to the
294
+ one passed in. This can lead to problems if you want other schemas to always be
295
+ searched as well. Enter `persistent_schemas`. You can configure a list of other
296
+ schemas that will always remain in the search path, while the default gets
297
+ swapped out:
298
+
299
+ ```ruby
300
+ config.persistent_schemas = ['some', 'other', 'schemas']
301
+ ```
302
+
303
+ ### Installing Extensions into Persistent Schemas
304
+
305
+ Persistent Schemas have numerous useful applications.
306
+ [Hstore](http://www.postgresql.org/docs/9.1/static/hstore.html), for instance,
307
+ is a popular storage engine for Postgresql. In order to use extensions such as
308
+ Hstore, you have to install it to a specific schema and have that always in the
309
+ `schema_search_path`.
310
+
311
+ When using extensions, keep in mind:
312
+
313
+ * Extensions can only be installed into one schema per database, so we will want
314
+ to install it into a schema that is always available in the
315
+ `schema_search_path`
316
+ * The schema and extension need to be created in the database *before* they are
317
+ referenced in migrations, database.yml or innkeeper.
318
+ * There does not seem to be a way to create the schema and extension using
319
+ standard rails migrations.
320
+ * Rails db:test:prepare deletes and recreates the database, so it needs to be
321
+ easy for the extension schema to be recreated here.
322
+
323
+ #### 1. Ensure the extensions schema is created when the database is created
324
+
325
+ ```ruby
326
+ # lib/tasks/db_enhancements.rake
327
+
328
+ ####### Important information ####################
329
+ # This file is used to setup a shared extensions #
330
+ # within a dedicated schema. This gives us the #
331
+ # advantage of only needing to enable extensions #
332
+ # in one place. #
333
+ # #
334
+ # This task should be run AFTER db:create but #
335
+ # BEFORE db:migrate. #
336
+ ##################################################
337
+
338
+ namespace :db do
339
+ desc 'Also create shared_extensions Schema'
340
+ task :extensions => :environment do
341
+ # Create Schema
342
+ ActiveRecord::Base.connection.execute 'CREATE SCHEMA IF NOT EXISTS shared_extensions;'
343
+ # Enable Hstore
344
+ ActiveRecord::Base.connection.execute 'CREATE EXTENSION IF NOT EXISTS HSTORE SCHEMA shared_extensions;'
345
+ # Enable UUID-OSSP
346
+ ActiveRecord::Base.connection.execute 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp" SCHEMA shared_extensions;'
347
+ end
348
+ end
349
+
350
+ Rake::Task["db:create"].enhance do
351
+ Rake::Task["db:extensions"].invoke
352
+ end
353
+
354
+ Rake::Task["db:test:purge"].enhance do
355
+ Rake::Task["db:extensions"].invoke
356
+ end
357
+ ```
358
+
359
+ #### 2. Ensure the schema is in Rails' default connection
360
+
361
+ Next, your `database.yml` file must mimic what you've set for your default and
362
+ persistent schemas in Innkeeper. When you run migrations with Rails, it won't
363
+ know about the extensions schema because Innkeeper isn't injected into the
364
+ default connection, it's done on a per-request basis, therefore Rails doesn't
365
+ know about `hstore` or `uuid-ossp` during migrations. To do so, add the
366
+ following to your `database.yml` for all environments
367
+
368
+ ```yaml
369
+ # database.yml
370
+ ...
371
+ adapter: postgresql
372
+ schema_search_path: "public,shared_extensions"
373
+ ...
374
+ ```
375
+
376
+ This would be for a config with `default_schema` set to `public` and
377
+ `persistent_schemas` set to `['shared_extensions']`.
378
+
379
+ #### 3. Ensure the schema is in the innkeeper config
380
+
381
+ ```ruby
382
+ # config/initializers/innkeeper.rb
383
+ ...
384
+ config.persistent_schemas = ['shared_extensions']
385
+ ...
386
+ ```
387
+
388
+ #### Alternative: Creating schema by default
389
+
390
+ Another way that we've successfully configured hstore for our applications is to
391
+ add it into the postgresql template1 database so that every tenant that gets
392
+ created has it by default.
393
+
394
+ One caveat with this approach is that it can interfere with other projects in
395
+ development using the same extensions and template, but not using innkeeper with
396
+ this approach.
397
+
398
+ You can do so using a command like so:
399
+
400
+ ```bash
401
+ psql -U postgres -d template1 -c "CREATE SCHEMA shared_extensions AUTHORIZATION some_username;"
402
+ psql -U postgres -d template1 -c "CREATE EXTENSION IF NOT EXISTS hstore SCHEMA shared_extensions;"
403
+ ```
404
+
405
+ The *ideal* setup would actually be to install `hstore` into the `public` schema
406
+ and leave the public schema in the `search_path` at all times. We won't be able
407
+ to do this though until public doesn't also contain the tenanted tables, which
408
+ is an open issue with no real milestone to be completed. Happy to accept PR's on
409
+ the matter.
410
+
411
+ ### Managing Migrations
412
+
413
+ In order to migrate all of your tenants (or postgresql schemas) you need to
414
+ provide a list of dbs to Innkeeper. You can make this dynamic by providing a
415
+ Proc object to be called on migrations. This object should yield an array of
416
+ string representing each tenant name. Example:
417
+
418
+ ```ruby
419
+ # Dynamically get tenant names to migrate
420
+ config.tenant_names = lambda{ Customer.pluck(:tenant_name) }
421
+
422
+ # Use a static list of tenant names for migrate
423
+ config.tenant_names = ['tenant1', 'tenant2']
424
+ ```
425
+
426
+ You can then migrate your tenants using the normal rake task:
427
+
428
+ ```ruby
429
+ rake db:migrate
430
+ ```
431
+
432
+ This just invokes `Innkeeper::Tenant.migrate(#{tenant_name})` for each tenant
433
+ name supplied from `Innkeeper.tenant_names`.
434
+
435
+ #### Parallel Migrations
436
+
437
+ Innkeeper supports parallelizing migrations into multiple threads when
438
+ you have a large number of tenants. By default, parallel migrations is
439
+ turned off. You can enable this by setting `parallel_migration_threads` to
440
+ the number of threads you want to use in your initializer.
441
+
442
+ Keep in mind that because migrations are going to access the database,
443
+ the number of threads indicated here should be less than the pool size
444
+ that Rails will use to connect to your database.
445
+
446
+ ## Tenants on different servers
447
+
448
+ Innkeeper supports tenant-based sharding at the application level. The `switch`,
449
+ `create`, and `drop` methods all support full database configurations (as a
450
+ hash) as well as tenant names. In fact, even when you pass a tenant name, it
451
+ gets resolved to a full configuration using the configured `tenant_resolver`. If
452
+ you wish to switch to a tenant on a different host, you can pass the full config
453
+ with the host key.
454
+
455
+ Innkeeper will compare the config to it's current one and work out whether it
456
+ needs to switch host, database, schema, etc, and only do the minimal switch. For
457
+ tenants (databases for mysql, schemas for pg) on the same host, the switch will
458
+ be a lightweight 'local' switch, which is one that occurs as a SQL query only,
459
+ rather than a re-establishment of the database connection.
460
+
461
+ You could make use of a custom resolver to do multi-host tenant switching by
462
+ name. You could map tenant names to a host (shard) IP via the hash of the host
463
+ name, or something similar, and divide the hash space across available hosts.
464
+
465
+ ## Sidekiq
466
+
467
+ See [innkeeper-sidekiq](https://github.com/cpoms/innkeeper-sidekiq)
468
+
469
+ ## Contributing
470
+
471
+ * In `test/`, you will see `databases.yml.sample` files
472
+ * Copy them into the same directory but with the name `databases.yml`
473
+ * Edit them to fit your own settings
474
+ * Rake tasks (see the Rakefile) will help you setup your dbs necessary to run
475
+ tests
476
+ * Please issue pull requests to the `development` branch. All development
477
+ happens here, master is used for releases.
478
+ * Ensure that your code is accompanied with tests. No code will be merged
479
+ without tests
480
+
481
+ ## License
482
+
483
+ Innkeeper is released under the [MIT License](http://www.opensource.org/licenses/MIT).
484
+
485
+ [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/cpoms/innkeeper/trend.png)](https://bitdeli.com/free "Bitdeli Badge")