ros-apartment 2.3.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE.md +21 -0
  3. data/.gitignore +15 -0
  4. data/.pryrc +3 -0
  5. data/.rspec +4 -0
  6. data/.travis.yml +65 -0
  7. data/Appraisals +71 -0
  8. data/Gemfile +10 -0
  9. data/Guardfile +24 -0
  10. data/HISTORY.md +398 -0
  11. data/README.md +576 -0
  12. data/Rakefile +128 -0
  13. data/TODO.md +51 -0
  14. data/apartment.gemspec +46 -0
  15. data/docker-compose.yml +33 -0
  16. data/gemfiles/rails_4_2.gemfile +23 -0
  17. data/gemfiles/rails_5_0.gemfile +22 -0
  18. data/gemfiles/rails_5_1.gemfile +22 -0
  19. data/gemfiles/rails_5_2.gemfile +18 -0
  20. data/gemfiles/rails_6_0.gemfile +22 -0
  21. data/gemfiles/rails_master.gemfile +22 -0
  22. data/lib/apartment.rb +118 -0
  23. data/lib/apartment/adapters/abstract_adapter.rb +269 -0
  24. data/lib/apartment/adapters/abstract_jdbc_adapter.rb +18 -0
  25. data/lib/apartment/adapters/jdbc_mysql_adapter.rb +19 -0
  26. data/lib/apartment/adapters/jdbc_postgresql_adapter.rb +56 -0
  27. data/lib/apartment/adapters/mysql2_adapter.rb +71 -0
  28. data/lib/apartment/adapters/postgis_adapter.rb +12 -0
  29. data/lib/apartment/adapters/postgresql_adapter.rb +236 -0
  30. data/lib/apartment/adapters/sqlite3_adapter.rb +56 -0
  31. data/lib/apartment/console.rb +12 -0
  32. data/lib/apartment/deprecation.rb +10 -0
  33. data/lib/apartment/elevators/domain.rb +22 -0
  34. data/lib/apartment/elevators/first_subdomain.rb +17 -0
  35. data/lib/apartment/elevators/generic.rb +32 -0
  36. data/lib/apartment/elevators/host.rb +30 -0
  37. data/lib/apartment/elevators/host_hash.rb +22 -0
  38. data/lib/apartment/elevators/subdomain.rb +62 -0
  39. data/lib/apartment/migrator.rb +51 -0
  40. data/lib/apartment/railtie.rb +67 -0
  41. data/lib/apartment/reloader.rb +21 -0
  42. data/lib/apartment/tasks/enhancements.rb +57 -0
  43. data/lib/apartment/tenant.rb +66 -0
  44. data/lib/apartment/version.rb +3 -0
  45. data/lib/generators/apartment/install/USAGE +5 -0
  46. data/lib/generators/apartment/install/install_generator.rb +10 -0
  47. data/lib/generators/apartment/install/templates/apartment.rb +109 -0
  48. data/lib/tasks/apartment.rake +145 -0
  49. data/spec/adapters/jdbc_mysql_adapter_spec.rb +19 -0
  50. data/spec/adapters/jdbc_postgresql_adapter_spec.rb +41 -0
  51. data/spec/adapters/mysql2_adapter_spec.rb +59 -0
  52. data/spec/adapters/postgresql_adapter_spec.rb +61 -0
  53. data/spec/adapters/sqlite3_adapter_spec.rb +83 -0
  54. data/spec/apartment_spec.rb +11 -0
  55. data/spec/config/database.yml.sample +49 -0
  56. data/spec/dummy/Rakefile +7 -0
  57. data/spec/dummy/app/controllers/application_controller.rb +6 -0
  58. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  59. data/spec/dummy/app/models/company.rb +3 -0
  60. data/spec/dummy/app/models/user.rb +3 -0
  61. data/spec/dummy/app/views/application/index.html.erb +1 -0
  62. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  63. data/spec/dummy/config.ru +4 -0
  64. data/spec/dummy/config/application.rb +49 -0
  65. data/spec/dummy/config/boot.rb +11 -0
  66. data/spec/dummy/config/database.yml.sample +44 -0
  67. data/spec/dummy/config/environment.rb +5 -0
  68. data/spec/dummy/config/environments/development.rb +28 -0
  69. data/spec/dummy/config/environments/production.rb +51 -0
  70. data/spec/dummy/config/environments/test.rb +34 -0
  71. data/spec/dummy/config/initializers/apartment.rb +4 -0
  72. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  73. data/spec/dummy/config/initializers/inflections.rb +10 -0
  74. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  75. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  76. data/spec/dummy/config/initializers/session_store.rb +8 -0
  77. data/spec/dummy/config/locales/en.yml +5 -0
  78. data/spec/dummy/config/routes.rb +3 -0
  79. data/spec/dummy/db/migrate/20110613152810_create_dummy_models.rb +39 -0
  80. data/spec/dummy/db/migrate/20111202022214_create_table_books.rb +14 -0
  81. data/spec/dummy/db/migrate/20180415260934_create_public_tokens.rb +13 -0
  82. data/spec/dummy/db/schema.rb +55 -0
  83. data/spec/dummy/db/seeds.rb +5 -0
  84. data/spec/dummy/db/seeds/import.rb +5 -0
  85. data/spec/dummy/public/404.html +26 -0
  86. data/spec/dummy/public/422.html +26 -0
  87. data/spec/dummy/public/500.html +26 -0
  88. data/spec/dummy/public/favicon.ico +0 -0
  89. data/spec/dummy/public/stylesheets/.gitkeep +0 -0
  90. data/spec/dummy/script/rails +6 -0
  91. data/spec/dummy_engine/.gitignore +8 -0
  92. data/spec/dummy_engine/Gemfile +15 -0
  93. data/spec/dummy_engine/Rakefile +34 -0
  94. data/spec/dummy_engine/bin/rails +12 -0
  95. data/spec/dummy_engine/config/initializers/apartment.rb +51 -0
  96. data/spec/dummy_engine/dummy_engine.gemspec +24 -0
  97. data/spec/dummy_engine/lib/dummy_engine.rb +4 -0
  98. data/spec/dummy_engine/lib/dummy_engine/engine.rb +4 -0
  99. data/spec/dummy_engine/lib/dummy_engine/version.rb +3 -0
  100. data/spec/dummy_engine/test/dummy/Rakefile +6 -0
  101. data/spec/dummy_engine/test/dummy/config.ru +4 -0
  102. data/spec/dummy_engine/test/dummy/config/application.rb +22 -0
  103. data/spec/dummy_engine/test/dummy/config/boot.rb +5 -0
  104. data/spec/dummy_engine/test/dummy/config/database.yml +25 -0
  105. data/spec/dummy_engine/test/dummy/config/environment.rb +5 -0
  106. data/spec/dummy_engine/test/dummy/config/environments/development.rb +37 -0
  107. data/spec/dummy_engine/test/dummy/config/environments/production.rb +78 -0
  108. data/spec/dummy_engine/test/dummy/config/environments/test.rb +39 -0
  109. data/spec/dummy_engine/test/dummy/config/initializers/assets.rb +8 -0
  110. data/spec/dummy_engine/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  111. data/spec/dummy_engine/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  112. data/spec/dummy_engine/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  113. data/spec/dummy_engine/test/dummy/config/initializers/inflections.rb +16 -0
  114. data/spec/dummy_engine/test/dummy/config/initializers/mime_types.rb +4 -0
  115. data/spec/dummy_engine/test/dummy/config/initializers/session_store.rb +3 -0
  116. data/spec/dummy_engine/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  117. data/spec/dummy_engine/test/dummy/config/locales/en.yml +23 -0
  118. data/spec/dummy_engine/test/dummy/config/routes.rb +56 -0
  119. data/spec/dummy_engine/test/dummy/config/secrets.yml +22 -0
  120. data/spec/examples/connection_adapter_examples.rb +42 -0
  121. data/spec/examples/generic_adapter_custom_configuration_example.rb +95 -0
  122. data/spec/examples/generic_adapter_examples.rb +163 -0
  123. data/spec/examples/schema_adapter_examples.rb +234 -0
  124. data/spec/integration/apartment_rake_integration_spec.rb +107 -0
  125. data/spec/integration/query_caching_spec.rb +81 -0
  126. data/spec/integration/use_within_an_engine_spec.rb +28 -0
  127. data/spec/schemas/v1.rb +16 -0
  128. data/spec/schemas/v2.rb +43 -0
  129. data/spec/schemas/v3.rb +49 -0
  130. data/spec/spec_helper.rb +61 -0
  131. data/spec/support/apartment_helpers.rb +43 -0
  132. data/spec/support/capybara_sessions.rb +15 -0
  133. data/spec/support/config.rb +10 -0
  134. data/spec/support/contexts.rb +52 -0
  135. data/spec/support/requirements.rb +35 -0
  136. data/spec/support/setup.rb +46 -0
  137. data/spec/tasks/apartment_rake_spec.rb +129 -0
  138. data/spec/tenant_spec.rb +190 -0
  139. data/spec/unit/config_spec.rb +112 -0
  140. data/spec/unit/elevators/domain_spec.rb +32 -0
  141. data/spec/unit/elevators/first_subdomain_spec.rb +24 -0
  142. data/spec/unit/elevators/generic_spec.rb +54 -0
  143. data/spec/unit/elevators/host_hash_spec.rb +32 -0
  144. data/spec/unit/elevators/host_spec.rb +89 -0
  145. data/spec/unit/elevators/subdomain_spec.rb +76 -0
  146. data/spec/unit/migrator_spec.rb +77 -0
  147. data/spec/unit/reloader_spec.rb +24 -0
  148. metadata +487 -0
@@ -0,0 +1,576 @@
1
+ # Apartment
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/apartment.svg)](https://badge.fury.io/rb/apartment)
4
+ [![Code Climate](https://codeclimate.com/github/influitive/apartment/badges/gpa.svg)](https://codeclimate.com/github/influitive/apartment)
5
+ [![Build Status](https://travis-ci.org/influitive/apartment.svg?branch=development)](https://travis-ci.org/influitive/apartment)
6
+
7
+ # NOT ACTIVELY MAINTAINED YET. SOLELY FOR TESTING PURPOSES ATM
8
+
9
+ *Multitenancy for Rails and ActiveRecord*
10
+
11
+ Apartment provides tools to help you deal with multiple tenants in your Rails
12
+ application. If you need to have certain data sequestered based on account or company,
13
+ but still allow some data to exist in a common tenant, Apartment can help.
14
+
15
+ ## HELP!
16
+
17
+ In order to help drive the direction of development and clean up the codebase, we'd like to take a poll
18
+ on how people are currently using Apartment. If you can take 5 seconds (1 question) to answer
19
+ this poll, we'd greatly appreciated it.
20
+
21
+ [View Poll](http://www.poll-maker.com/poll391552x4Bfb41a9-15)
22
+
23
+ ## Excessive Memory Issues on ActiveRecord 4.x
24
+
25
+ > If you're noticing ever growing memory issues (ie growing with each tenant you add)
26
+ > when using Apartment, that's because there's [an issue](https://github.com/rails/rails/issues/19578)
27
+ > with how ActiveRecord maps Postgresql data types into AR data types.
28
+ > This has been patched and will be released for AR 4.2.2. It's apparently hard
29
+ > to backport to 4.1 unfortunately.
30
+ > If you're noticing high memory usage from ActiveRecord with Apartment please upgrade.
31
+
32
+ ```ruby
33
+ gem 'rails', '4.2.1', github: 'influitive/rails', tag: 'v4.2.1.memfix'
34
+ ```
35
+
36
+ ## Installation
37
+
38
+ ### Rails
39
+
40
+ Add the following to your Gemfile:
41
+
42
+ ```ruby
43
+ gem 'apartment'
44
+ ```
45
+
46
+ Then generate your `Apartment` config file using
47
+
48
+ ```ruby
49
+ bundle exec rails generate apartment:install
50
+ ```
51
+
52
+ This will create a `config/initializers/apartment.rb` initializer file.
53
+ Configure as needed using the docs below.
54
+
55
+ That's all you need to set up the Apartment libraries. If you want to switch tenants
56
+ on a per-user basis, look under "Usage - Switching tenants per request", below.
57
+
58
+ > NOTE: If using [postgresql schemas](http://www.postgresql.org/docs/9.0/static/ddl-schemas.html) you must use:
59
+ >
60
+ > * 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
61
+
62
+ ## Usage
63
+
64
+ ### Video Tutorial
65
+
66
+ How to separate your application data into different accounts or companies.
67
+ [GoRails #47](https://gorails.com/episodes/multitenancy-with-apartment)
68
+
69
+ ### Creating new Tenants
70
+
71
+ Before you can switch to a new apartment tenant, you will need to create it. Whenever
72
+ you need to create a new tenant, you can run the following command:
73
+
74
+ ```ruby
75
+ Apartment::Tenant.create('tenant_name')
76
+ ```
77
+
78
+ If you're using the [prepend environment](https://github.com/influitive/apartment#handling-environments) config option or you AREN'T using Postgresql Schemas, this will create a tenant in the following format: "#{environment}\_tenant_name".
79
+ In the case of a sqlite database, this will be created in your 'db/' folder. With
80
+ other databases, the tenant will be created as a new DB within the system.
81
+
82
+ When you create a new tenant, all migrations will be run against that tenant, so it will be
83
+ up to date when create returns.
84
+
85
+ #### Notes on PostgreSQL
86
+
87
+ PostgreSQL works slightly differently than other databases when creating a new tenant. If you
88
+ are using PostgreSQL, Apartment by default will set up a new [schema](http://www.postgresql.org/docs/9.3/static/ddl-schemas.html)
89
+ and migrate into there. This provides better performance, and allows Apartment to work on systems like Heroku, which
90
+ would not allow a full new database to be created.
91
+
92
+ One can optionally use the full database creation instead if they want, though this is not recommended
93
+
94
+ ### Switching Tenants
95
+
96
+ To switch tenants using Apartment, use the following command:
97
+
98
+ ```ruby
99
+ Apartment::Tenant.switch('tenant_name') do
100
+ # ...
101
+ end
102
+ ```
103
+
104
+ When switch is called, all requests coming to ActiveRecord will be routed to the tenant
105
+ you specify (with the exception of excluded models, see below). The tenant is automatically
106
+ switched back at the end of the block to what it was before.
107
+
108
+ There is also `switch!` which doesn't take a block, but it's recommended to use `switch`.
109
+ To return to the default tenant, you can call `switch` with no arguments.
110
+
111
+ ### Switching Tenants per request
112
+
113
+ You can have Apartment route to the appropriate tenant by adding some Rack middleware.
114
+ Apartment can support many different "Elevators" that can take care of this routing to your data.
115
+
116
+ **NOTE: when switching tenants per-request, keep in mind that the order of your Rack middleware is important.**
117
+ See the [Middleware Considerations](#middleware-considerations) section for more.
118
+
119
+ The initializer above will generate the appropriate code for the Subdomain elevator
120
+ by default. You can see this in `config/initializers/apartment.rb` after running
121
+ that generator. If you're *not* using the generator, you can specify your
122
+ elevator below. Note that in this case you will **need** to require the elevator
123
+ manually in your `application.rb` like so
124
+
125
+ ```ruby
126
+ # config/application.rb
127
+ require 'apartment/elevators/subdomain' # or 'domain', 'first_subdomain', 'host'
128
+ ```
129
+
130
+ #### Switch on subdomain
131
+
132
+ In house, we use the subdomain elevator, which analyzes the subdomain of the request and switches to a tenant schema of the same name. It can be used like so:
133
+
134
+ ```ruby
135
+ # application.rb
136
+ module MyApplication
137
+ class Application < Rails::Application
138
+ config.middleware.use Apartment::Elevators::Subdomain
139
+ end
140
+ end
141
+ ```
142
+
143
+ If you want to exclude a domain, for example if you don't want your application to treat www like a subdomain, in an initializer in your application, you can set the following:
144
+
145
+ ```ruby
146
+ # config/initializers/apartment/subdomain_exclusions.rb
147
+ Apartment::Elevators::Subdomain.excluded_subdomains = ['www']
148
+ ```
149
+
150
+ This functions much in the same way as Apartment.excluded_models. This example will prevent switching your tenant when the subdomain is www. Handy for subdomains like: "public", "www", and "admin" :)
151
+
152
+ #### Switch on first subdomain
153
+
154
+ To switch on the first subdomain, which analyzes the chain of subdomains of the request and switches to a tenant schema of the first name in the chain (e.g. owls.birds.animals.com would switch to "owls"). It can be used like so:
155
+
156
+ ```ruby
157
+ # application.rb
158
+ module MyApplication
159
+ class Application < Rails::Application
160
+ config.middleware.use Apartment::Elevators::FirstSubdomain
161
+ end
162
+ end
163
+ ```
164
+
165
+ If you want to exclude a domain, for example if you don't want your application to treat www like a subdomain, in an initializer in your application, you can set the following:
166
+
167
+ ```ruby
168
+ # config/initializers/apartment/subdomain_exclusions.rb
169
+ Apartment::Elevators::FirstSubdomain.excluded_subdomains = ['www']
170
+ ```
171
+
172
+ This functions much in the same way as the Subdomain elevator. **NOTE:** in fact, at the time of this writing, the `Subdomain` and `FirstSubdomain` elevators both use the first subdomain ([#339](https://github.com/influitive/apartment/issues/339#issuecomment-235578610)). If you need to switch on larger parts of a Subdomain, consider using a Custom Elevator.
173
+
174
+ #### Switch on domain
175
+
176
+ To switch based on full domain (excluding the 'www' subdomains and top level domains *ie '.com'* ) use the following:
177
+
178
+ ```ruby
179
+ # application.rb
180
+ module MyApplication
181
+ class Application < Rails::Application
182
+ config.middleware.use Apartment::Elevators::Domain
183
+ end
184
+ end
185
+ ```
186
+
187
+ Note that if you have several subdomains, then it will match on the first *non-www* subdomain:
188
+ - example.com => example
189
+ - www.example.com => example
190
+ - a.example.com => a
191
+
192
+ #### Switch on full host using a hash
193
+
194
+ To switch based on full host with a hash to find corresponding tenant name use the following:
195
+
196
+ ```ruby
197
+ # application.rb
198
+ module MyApplication
199
+ class Application < Rails::Application
200
+ config.middleware.use Apartment::Elevators::HostHash, {'example.com' => 'example_tenant'}
201
+ end
202
+ end
203
+ ```
204
+
205
+ #### Switch on full host, ignoring given first subdomains
206
+
207
+ To switch based on full host to find corresponding tenant name use the following:
208
+
209
+ ```ruby
210
+ # application.rb
211
+ module MyApplication
212
+ class Application < Rails::Application
213
+ config.middleware.use Apartment::Elevators::Host
214
+ end
215
+ end
216
+ ```
217
+
218
+ If you want to exclude a first-subdomain, for example if you don't want your application to include www in the matching, in an initializer in your application, you can set the following:
219
+
220
+ ```ruby
221
+ Apartment::Elevators::Host.ignored_first_subdomains = ['www']
222
+ ```
223
+
224
+ With the above set, these would be the results:
225
+ - example.com => example.com
226
+ - www.example.com => example.com
227
+ - a.example.com => a.example.com
228
+ - www.a.example.com => a.example.com
229
+
230
+ #### Custom Elevator
231
+
232
+ 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 tenant. Use like so:
233
+
234
+ ```ruby
235
+ # application.rb
236
+ module MyApplication
237
+ class Application < Rails::Application
238
+ # Obviously not a contrived example
239
+ config.middleware.use Apartment::Elevators::Generic, Proc.new { |request| request.host.reverse }
240
+ end
241
+ end
242
+ ```
243
+
244
+ Your other option is to subclass the Generic elevator and implement your own
245
+ switching mechanism. This is exactly how the other elevators work. Look at
246
+ the `subdomain.rb` elevator to get an idea of how this should work. Basically
247
+ all you need to do is subclass the generic elevator and implement your own
248
+ `parse_tenant_name` method that will ultimately return the name of the tenant
249
+ based on the request being made. It *could* look something like this:
250
+
251
+ ```ruby
252
+ # app/middleware/my_custom_elevator.rb
253
+ class MyCustomElevator < Apartment::Elevators::Generic
254
+
255
+ # @return {String} - The tenant to switch to
256
+ def parse_tenant_name(request)
257
+ # request is an instance of Rack::Request
258
+
259
+ # example: look up some tenant from the db based on this request
260
+ tenant_name = SomeModel.from_request(request)
261
+
262
+ return tenant_name
263
+ end
264
+ end
265
+ ```
266
+
267
+ #### Middleware Considerations
268
+
269
+ In the examples above, we show the Apartment middleware being appended to the Rack stack with
270
+
271
+ ```ruby
272
+ Rails.application.config.middleware.use Apartment::Elevators::Subdomain
273
+ ```
274
+
275
+ By default, the Subdomain middleware switches into a Tenant based on the subdomain at the beginning of the request, and when the request is finished, it switches back to the "public" Tenant. This happens in the [Generic](https://github.com/influitive/apartment/blob/development/lib/apartment/elevators/generic.rb#L22) elevator, so all elevators that inherit from this elevator will operate as such.
276
+
277
+ It's also good to note that Apartment switches back to the "public" tenant any time an error is raised in your application.
278
+
279
+ This works okay for simple applications, but it's important to consider that you may want to maintain the "selected" tenant through different parts of the Rack application stack. For example, the [Devise](https://github.com/plataformatec/devise) gem adds the `Warden::Manager` middleware at the end of the stack in the examples above, our `Apartment::Elevators::Subdomain` middleware would come after it. Trouble is, Apartment resets the selected tenant after the request is finish, so some redirects (e.g. authentication) in Devise will be run in the context of the "public" tenant. The same issue would also effect a gem such as the [better_errors](https://github.com/charliesome/better_errors) gem which inserts a middleware quite early in the Rails middleware stack.
280
+
281
+ To resolve this issue, consider adding the Apartment middleware at a location in the Rack stack that makes sense for your needs, e.g.:
282
+
283
+ ```ruby
284
+ Rails.application.config.middleware.insert_before Warden::Manager, Apartment::Elevators::Subdomain
285
+ ```
286
+
287
+ Now work done in the Warden middleware is wrapped in the `Apartment::Tenant.switch` context started in the Generic elevator.
288
+
289
+ ### Dropping Tenants
290
+
291
+ To drop tenants using Apartment, use the following command:
292
+
293
+ ```ruby
294
+ Apartment::Tenant.drop('tenant_name')
295
+ ```
296
+
297
+ When method is called, the schema is dropped and all data from itself will be lost. Be careful with this method.
298
+
299
+ ## Config
300
+
301
+ The following config options should be set up in a Rails initializer such as:
302
+
303
+ config/initializers/apartment.rb
304
+
305
+ To set config options, add this to your initializer:
306
+
307
+ ```ruby
308
+ Apartment.configure do |config|
309
+ # set your options (described below) here
310
+ end
311
+ ```
312
+
313
+ ### Excluding models
314
+
315
+ If you have some models that should always access the 'public' tenant, 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:
316
+
317
+ ```ruby
318
+ config.excluded_models = ["User", "Company"] # these models will not be multi-tenanted, but remain in the global (public) namespace
319
+ ```
320
+
321
+ Note that a string representation of the model name is now the standard so that models are properly constantized when reloaded in development
322
+
323
+ Rails will always access the 'public' tenant when accessing these models, but note that tables will be created in all schemas. This may not be ideal, but its done this way because otherwise rails wouldn't be able to properly generate the schema.rb file.
324
+
325
+ > **NOTE - Many-To-Many Excluded Models:**
326
+ > Since model exclusions must come from referencing a real ActiveRecord model, `has_and_belongs_to_many` is NOT supported. In order to achieve a many-to-many relationship for excluded models, you MUST use `has_many :through`. This way you can reference the join model in the excluded models configuration.
327
+
328
+ ### Postgresql Schemas
329
+
330
+ ## Providing a Different default_schema
331
+
332
+ 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:
333
+
334
+ ```ruby
335
+ config.default_schema = "some_other_schema"
336
+ ```
337
+
338
+ With that set, all excluded models will use this schema as the table name prefix instead of `public` and `reset` on `Apartment::Tenant` will return to this schema as well.
339
+
340
+ ## Persistent Schemas
341
+
342
+ 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:
343
+
344
+ ```ruby
345
+ config.persistent_schemas = ['some', 'other', 'schemas']
346
+ ```
347
+
348
+ ### Installing Extensions into Persistent Schemas
349
+
350
+ Persistent Schemas have 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 extensions such as Hstore, you have to install it to a specific schema and have that always in the `schema_search_path`.
351
+
352
+ When using extensions, keep in mind:
353
+ * Extensions can only be installed into one schema per database, so we will want to install it into a schema that is always available in the `schema_search_path`
354
+ * The schema and extension need to be created in the database *before* they are referenced in migrations, database.yml or apartment.
355
+ * There does not seem to be a way to create the schema and extension using standard rails migrations.
356
+ * Rails db:test:prepare deletes and recreates the database, so it needs to be easy for the extension schema to be recreated here.
357
+
358
+ #### 1. Ensure the extensions schema is created when the database is created
359
+
360
+ ```ruby
361
+ # lib/tasks/db_enhancements.rake
362
+
363
+ ####### Important information ####################
364
+ # This file is used to setup a shared extensions #
365
+ # within a dedicated schema. This gives us the #
366
+ # advantage of only needing to enable extensions #
367
+ # in one place. #
368
+ # #
369
+ # This task should be run AFTER db:create but #
370
+ # BEFORE db:migrate. #
371
+ ##################################################
372
+
373
+ namespace :db do
374
+ desc 'Also create shared_extensions Schema'
375
+ task :extensions => :environment do
376
+ # Create Schema
377
+ ActiveRecord::Base.connection.execute 'CREATE SCHEMA IF NOT EXISTS shared_extensions;'
378
+ # Enable Hstore
379
+ ActiveRecord::Base.connection.execute 'CREATE EXTENSION IF NOT EXISTS HSTORE SCHEMA shared_extensions;'
380
+ # Enable UUID-OSSP
381
+ ActiveRecord::Base.connection.execute 'CREATE EXTENSION IF NOT EXISTS "uuid-ossp" SCHEMA shared_extensions;'
382
+ # Grant usage to public
383
+ ActiveRecord::Base.connection.execute 'GRANT usage ON SCHEMA shared_extensions to public;'
384
+ end
385
+ end
386
+
387
+ Rake::Task["db:create"].enhance do
388
+ Rake::Task["db:extensions"].invoke
389
+ end
390
+
391
+ Rake::Task["db:test:purge"].enhance do
392
+ Rake::Task["db:extensions"].invoke
393
+ end
394
+ ```
395
+
396
+ #### 2. Ensure the schema is in Rails' default connection
397
+
398
+ Next, your `database.yml` file must mimic what you've set for your default and persistent schemas in Apartment. When you run migrations with Rails, it won't know about the extensions schema because Apartment isn't injected into the default connection, it's done on a per-request basis, therefore Rails doesn't know about `hstore` or `uuid-ossp` during migrations. To do so, add the following to your `database.yml` for all environments
399
+
400
+ ```yaml
401
+ # database.yml
402
+ ...
403
+ adapter: postgresql
404
+ schema_search_path: "public,shared_extensions"
405
+ ...
406
+ ```
407
+
408
+ This would be for a config with `default_schema` set to `public` and `persistent_schemas` set to `['shared_extensions']`. **Note**: This only works on Heroku with [Rails 4.1+](https://devcenter.heroku.com/changelog-items/426). For apps that use older Rails versions hosted on Heroku, the only way to properly setup is to start with a fresh PostgreSQL instance:
409
+
410
+ 1. Append `?schema_search_path=public,hstore` to your `DATABASE_URL` environment variable, by this you don't have to revise the `database.yml` file (which is impossible since Heroku regenerates a completely different and immutable `database.yml` of its own on each deploy)
411
+ 2. Run `heroku pg:psql` from your command line
412
+ 3. And then `DROP EXTENSION hstore;` (**Note:** This will drop all columns that use `hstore` type, so proceed with caution; only do this with a fresh PostgreSQL instance)
413
+ 4. Next: `CREATE SCHEMA IF NOT EXISTS hstore;`
414
+ 5. Finally: `CREATE EXTENSION IF NOT EXISTS hstore SCHEMA hstore;` and hit enter (`\q` to exit)
415
+
416
+ To double check, login to the console of your Heroku app and see if `Apartment.connection.schema_search_path` is `public,hstore`
417
+
418
+ #### 3. Ensure the schema is in the apartment config
419
+
420
+ ```ruby
421
+ # config/initializers/apartment.rb
422
+ ...
423
+ config.persistent_schemas = ['shared_extensions']
424
+ ...
425
+ ```
426
+
427
+ #### Alternative: Creating schema by default
428
+
429
+ Another way that we've successfully configured hstore for our applications is to add it into the
430
+ postgresql template1 database so that every tenant that gets created has it by default.
431
+
432
+ One caveat with this approach is that it can interfere with other projects in development using the same extensions and template, but not using apartment with this approach.
433
+
434
+ You can do so using a command like so
435
+
436
+ ```bash
437
+ psql -U postgres -d template1 -c "CREATE SCHEMA shared_extensions AUTHORIZATION some_username;"
438
+ psql -U postgres -d template1 -c "CREATE EXTENSION IF NOT EXISTS hstore SCHEMA shared_extensions;"
439
+ ```
440
+
441
+ The *ideal* setup would actually be to install `hstore` into the `public` schema and leave the public
442
+ schema in the `search_path` at all times. We won't be able to do this though until public doesn't
443
+ also contain the tenanted tables, which is an open issue with no real milestone to be completed.
444
+ Happy to accept PR's on the matter.
445
+
446
+ #### Alternative: Creating new schemas by using raw SQL dumps
447
+
448
+ Apartment can be forced to use raw SQL dumps insted of `schema.rb` for creating new schemas. Use this when you are using some extra features in postgres that can't be represented in `schema.rb`, like materialized views etc.
449
+
450
+ This only applies while using postgres adapter and `config.use_schemas` is set to `true`.
451
+ (Note: this option doesn't use `db/structure.sql`, it creates SQL dump by executing `pg_dump`)
452
+
453
+ Enable this option with:
454
+ ```ruby
455
+ config.use_sql = true
456
+ ```
457
+
458
+ ### Managing Migrations
459
+
460
+ In order to migrate all of your tenants (or postgresql schemas) you need to provide a list
461
+ of dbs to Apartment. You can make this dynamic by providing a Proc object to be called on migrations.
462
+ This object should yield an array of string representing each tenant name. Example:
463
+
464
+ ```ruby
465
+ # Dynamically get tenant names to migrate
466
+ config.tenant_names = lambda{ Customer.pluck(:tenant_name) }
467
+
468
+ # Use a static list of tenant names for migrate
469
+ config.tenant_names = ['tenant1', 'tenant2']
470
+ ```
471
+
472
+ You can then migrate your tenants using the normal rake task:
473
+
474
+ ```ruby
475
+ rake db:migrate
476
+ ```
477
+
478
+ This just invokes `Apartment::Tenant.migrate(#{tenant_name})` for each tenant name supplied
479
+ from `Apartment.tenant_names`
480
+
481
+ Note that you can disable the default migrating of all tenants with `db:migrate` by setting
482
+ `Apartment.db_migrate_tenants = false` in your `Rakefile`. Note this must be done
483
+ *before* the rake tasks are loaded. ie. before `YourApp::Application.load_tasks` is called
484
+
485
+ #### Parallel Migrations
486
+
487
+ Apartment supports parallelizing migrations into multiple threads when
488
+ you have a large number of tenants. By default, parallel migrations is
489
+ turned off. You can enable this by setting `parallel_migration_threads` to
490
+ the number of threads you want to use in your initializer.
491
+
492
+ Keep in mind that because migrations are going to access the database,
493
+ the number of threads indicated here should be less than the pool size
494
+ that Rails will use to connect to your database.
495
+
496
+ ### Handling Environments
497
+
498
+ By default, when not using postgresql schemas, Apartment will prepend the environment to the tenant name
499
+ to ensure there is no conflict between your environments. This is mainly for the benefit of your development
500
+ and test environments. If you wish to turn this option off in production, you could do something like:
501
+
502
+ ```ruby
503
+ config.prepend_environment = !Rails.env.production?
504
+ ```
505
+
506
+ ## Tenants on different servers
507
+
508
+ You can store your tenants in different databases on one or more servers.
509
+ To do it, specify your `tenant_names` as a hash, keys being the actual tenant names,
510
+ values being a hash with the database configuration to use.
511
+
512
+ Example:
513
+
514
+ ```ruby
515
+ config.with_multi_server_setup = true
516
+ config.tenant_names = {
517
+ 'tenant1' => {
518
+ adapter: 'postgresql',
519
+ host: 'some_server',
520
+ port: 5555,
521
+ database: 'postgres' # this is not the name of the tenant's db
522
+ # but the name of the database to connect to, before creating the tenant's db
523
+ # mandatory in postgresql
524
+ }
525
+ }
526
+ # or using a lambda:
527
+ config.tenant_names = lambda do
528
+ Tenant.all.each_with_object({}) do |tenant, hash|
529
+ hash[tenant.name] = tenant.db_configuration
530
+ end
531
+ end
532
+ ```
533
+
534
+ ## Background workers
535
+
536
+ See [apartment-sidekiq](https://github.com/influitive/apartment-sidekiq) or [apartment-activejob](https://github.com/influitive/apartment-activejob).
537
+
538
+ ## Callbacks
539
+
540
+ You can execute callbacks when switching between tenants or creating a new one, Apartment provides the following callbacks:
541
+
542
+ - before_create
543
+ - after_create
544
+ - before_switch
545
+ - after_switch
546
+
547
+ You can register a callback using [ActiveSupport::Callbacks](https://api.rubyonrails.org/classes/ActiveSupport/Callbacks.html) the following way:
548
+
549
+ ```ruby
550
+ require 'apartment/adapters/abstract_adapter'
551
+
552
+ module Apartment
553
+ module Adapters
554
+ class AbstractAdapter
555
+ set_callback :switch, :before do |object|
556
+ ...
557
+ end
558
+ end
559
+ end
560
+ end
561
+ ```
562
+
563
+ ## Contributing
564
+
565
+ * In both `spec/dummy/config` and `spec/config`, you will see `database.yml.sample` files
566
+ * Copy them into the same directory but with the name `database.yml`
567
+ * Edit them to fit your own settings
568
+ * Rake tasks (see the Rakefile) will help you setup your dbs necessary to run tests
569
+ * Please issue pull requests to the `development` branch. All development happens here, master is used for releases.
570
+ * Ensure that your code is accompanied with tests. No code will be merged without tests
571
+
572
+ * If you're looking to help, check out the TODO file for some upcoming changes I'd like to implement in Apartment.
573
+
574
+ ## License
575
+
576
+ Apartment is released under the [MIT License](http://www.opensource.org/licenses/MIT).