ros-apartment 2.3.0.alpha1 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (160) hide show
  1. checksums.yaml +4 -4
  2. data/.pryrc +5 -3
  3. data/.rubocop.yml +36 -0
  4. data/.rubocop_todo.yml +439 -0
  5. data/.ruby-version +1 -0
  6. data/Appraisals +31 -52
  7. data/CHANGELOG.md +965 -0
  8. data/Gemfile +2 -7
  9. data/Guardfile +3 -16
  10. data/README.md +121 -44
  11. data/Rakefile +42 -31
  12. data/lib/apartment/active_record/connection_handling.rb +31 -0
  13. data/lib/apartment/active_record/internal_metadata.rb +9 -0
  14. data/lib/apartment/active_record/postgresql_adapter.rb +43 -0
  15. data/lib/apartment/active_record/schema_migration.rb +11 -0
  16. data/lib/apartment/adapters/abstract_adapter.rb +52 -46
  17. data/lib/apartment/adapters/abstract_jdbc_adapter.rb +5 -3
  18. data/lib/apartment/adapters/jdbc_mysql_adapter.rb +3 -3
  19. data/lib/apartment/adapters/jdbc_postgresql_adapter.rb +19 -13
  20. data/lib/apartment/adapters/mysql2_adapter.rb +15 -9
  21. data/lib/apartment/adapters/postgis_adapter.rb +3 -2
  22. data/lib/apartment/adapters/postgresql_adapter.rb +79 -31
  23. data/lib/apartment/adapters/sqlite3_adapter.rb +18 -8
  24. data/lib/apartment/adapters/trilogy_adapter.rb +29 -0
  25. data/lib/apartment/console.rb +23 -11
  26. data/lib/apartment/custom_console.rb +42 -0
  27. data/lib/apartment/deprecation.rb +2 -1
  28. data/lib/apartment/elevators/domain.rb +4 -3
  29. data/lib/apartment/elevators/first_subdomain.rb +3 -2
  30. data/lib/apartment/elevators/generic.rb +4 -3
  31. data/lib/apartment/elevators/host.rb +6 -1
  32. data/lib/apartment/elevators/host_hash.rb +6 -2
  33. data/lib/apartment/elevators/subdomain.rb +9 -5
  34. data/lib/apartment/log_subscriber.rb +45 -0
  35. data/lib/apartment/migrator.rb +7 -24
  36. data/lib/apartment/model.rb +29 -0
  37. data/lib/apartment/railtie.rb +21 -20
  38. data/lib/apartment/tasks/enhancements.rb +4 -6
  39. data/lib/apartment/tasks/task_helper.rb +52 -0
  40. data/lib/apartment/tenant.rb +7 -10
  41. data/lib/apartment/version.rb +3 -1
  42. data/lib/apartment.rb +46 -15
  43. data/lib/generators/apartment/install/install_generator.rb +4 -3
  44. data/lib/generators/apartment/install/templates/apartment.rb +10 -3
  45. data/lib/tasks/apartment.rake +48 -87
  46. data/ros-apartment.gemspec +65 -0
  47. metadata +181 -264
  48. data/.github/ISSUE_TEMPLATE.md +0 -21
  49. data/.travis.yml +0 -65
  50. data/HISTORY.md +0 -398
  51. data/TODO.md +0 -51
  52. data/apartment.gemspec +0 -46
  53. data/docker-compose.yml +0 -33
  54. data/gemfiles/rails_4_2.gemfile +0 -23
  55. data/gemfiles/rails_5_0.gemfile +0 -22
  56. data/gemfiles/rails_5_1.gemfile +0 -22
  57. data/gemfiles/rails_5_2.gemfile +0 -18
  58. data/gemfiles/rails_6_0.gemfile +0 -22
  59. data/gemfiles/rails_master.gemfile +0 -22
  60. data/lib/apartment/reloader.rb +0 -21
  61. data/spec/adapters/jdbc_mysql_adapter_spec.rb +0 -19
  62. data/spec/adapters/jdbc_postgresql_adapter_spec.rb +0 -41
  63. data/spec/adapters/mysql2_adapter_spec.rb +0 -59
  64. data/spec/adapters/postgresql_adapter_spec.rb +0 -61
  65. data/spec/adapters/sqlite3_adapter_spec.rb +0 -83
  66. data/spec/apartment_spec.rb +0 -11
  67. data/spec/config/database.yml.sample +0 -49
  68. data/spec/dummy/Rakefile +0 -7
  69. data/spec/dummy/app/controllers/application_controller.rb +0 -6
  70. data/spec/dummy/app/helpers/application_helper.rb +0 -2
  71. data/spec/dummy/app/models/company.rb +0 -3
  72. data/spec/dummy/app/models/user.rb +0 -3
  73. data/spec/dummy/app/views/application/index.html.erb +0 -1
  74. data/spec/dummy/app/views/layouts/application.html.erb +0 -14
  75. data/spec/dummy/config/application.rb +0 -49
  76. data/spec/dummy/config/boot.rb +0 -11
  77. data/spec/dummy/config/database.yml.sample +0 -44
  78. data/spec/dummy/config/environment.rb +0 -5
  79. data/spec/dummy/config/environments/development.rb +0 -28
  80. data/spec/dummy/config/environments/production.rb +0 -51
  81. data/spec/dummy/config/environments/test.rb +0 -34
  82. data/spec/dummy/config/initializers/apartment.rb +0 -4
  83. data/spec/dummy/config/initializers/backtrace_silencers.rb +0 -7
  84. data/spec/dummy/config/initializers/inflections.rb +0 -10
  85. data/spec/dummy/config/initializers/mime_types.rb +0 -5
  86. data/spec/dummy/config/initializers/secret_token.rb +0 -7
  87. data/spec/dummy/config/initializers/session_store.rb +0 -8
  88. data/spec/dummy/config/locales/en.yml +0 -5
  89. data/spec/dummy/config/routes.rb +0 -3
  90. data/spec/dummy/config.ru +0 -4
  91. data/spec/dummy/db/migrate/20110613152810_create_dummy_models.rb +0 -39
  92. data/spec/dummy/db/migrate/20111202022214_create_table_books.rb +0 -14
  93. data/spec/dummy/db/migrate/20180415260934_create_public_tokens.rb +0 -13
  94. data/spec/dummy/db/schema.rb +0 -55
  95. data/spec/dummy/db/seeds/import.rb +0 -5
  96. data/spec/dummy/db/seeds.rb +0 -5
  97. data/spec/dummy/db/test.sqlite3 +0 -0
  98. data/spec/dummy/public/404.html +0 -26
  99. data/spec/dummy/public/422.html +0 -26
  100. data/spec/dummy/public/500.html +0 -26
  101. data/spec/dummy/public/favicon.ico +0 -0
  102. data/spec/dummy/public/stylesheets/.gitkeep +0 -0
  103. data/spec/dummy/script/rails +0 -6
  104. data/spec/dummy_engine/.gitignore +0 -8
  105. data/spec/dummy_engine/Gemfile +0 -15
  106. data/spec/dummy_engine/Rakefile +0 -34
  107. data/spec/dummy_engine/bin/rails +0 -12
  108. data/spec/dummy_engine/config/initializers/apartment.rb +0 -51
  109. data/spec/dummy_engine/dummy_engine.gemspec +0 -24
  110. data/spec/dummy_engine/lib/dummy_engine/engine.rb +0 -4
  111. data/spec/dummy_engine/lib/dummy_engine/version.rb +0 -3
  112. data/spec/dummy_engine/lib/dummy_engine.rb +0 -4
  113. data/spec/dummy_engine/test/dummy/Rakefile +0 -6
  114. data/spec/dummy_engine/test/dummy/config/application.rb +0 -22
  115. data/spec/dummy_engine/test/dummy/config/boot.rb +0 -5
  116. data/spec/dummy_engine/test/dummy/config/database.yml +0 -25
  117. data/spec/dummy_engine/test/dummy/config/environment.rb +0 -5
  118. data/spec/dummy_engine/test/dummy/config/environments/development.rb +0 -37
  119. data/spec/dummy_engine/test/dummy/config/environments/production.rb +0 -78
  120. data/spec/dummy_engine/test/dummy/config/environments/test.rb +0 -39
  121. data/spec/dummy_engine/test/dummy/config/initializers/assets.rb +0 -8
  122. data/spec/dummy_engine/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
  123. data/spec/dummy_engine/test/dummy/config/initializers/cookies_serializer.rb +0 -3
  124. data/spec/dummy_engine/test/dummy/config/initializers/filter_parameter_logging.rb +0 -4
  125. data/spec/dummy_engine/test/dummy/config/initializers/inflections.rb +0 -16
  126. data/spec/dummy_engine/test/dummy/config/initializers/mime_types.rb +0 -4
  127. data/spec/dummy_engine/test/dummy/config/initializers/session_store.rb +0 -3
  128. data/spec/dummy_engine/test/dummy/config/initializers/wrap_parameters.rb +0 -14
  129. data/spec/dummy_engine/test/dummy/config/locales/en.yml +0 -23
  130. data/spec/dummy_engine/test/dummy/config/routes.rb +0 -56
  131. data/spec/dummy_engine/test/dummy/config/secrets.yml +0 -22
  132. data/spec/dummy_engine/test/dummy/config.ru +0 -4
  133. data/spec/examples/connection_adapter_examples.rb +0 -42
  134. data/spec/examples/generic_adapter_custom_configuration_example.rb +0 -95
  135. data/spec/examples/generic_adapter_examples.rb +0 -163
  136. data/spec/examples/schema_adapter_examples.rb +0 -234
  137. data/spec/integration/apartment_rake_integration_spec.rb +0 -107
  138. data/spec/integration/query_caching_spec.rb +0 -81
  139. data/spec/integration/use_within_an_engine_spec.rb +0 -28
  140. data/spec/schemas/v1.rb +0 -16
  141. data/spec/schemas/v2.rb +0 -43
  142. data/spec/schemas/v3.rb +0 -49
  143. data/spec/spec_helper.rb +0 -61
  144. data/spec/support/apartment_helpers.rb +0 -43
  145. data/spec/support/capybara_sessions.rb +0 -15
  146. data/spec/support/config.rb +0 -10
  147. data/spec/support/contexts.rb +0 -52
  148. data/spec/support/requirements.rb +0 -35
  149. data/spec/support/setup.rb +0 -46
  150. data/spec/tasks/apartment_rake_spec.rb +0 -129
  151. data/spec/tenant_spec.rb +0 -190
  152. data/spec/unit/config_spec.rb +0 -112
  153. data/spec/unit/elevators/domain_spec.rb +0 -32
  154. data/spec/unit/elevators/first_subdomain_spec.rb +0 -24
  155. data/spec/unit/elevators/generic_spec.rb +0 -54
  156. data/spec/unit/elevators/host_hash_spec.rb +0 -32
  157. data/spec/unit/elevators/host_spec.rb +0 -89
  158. data/spec/unit/elevators/subdomain_spec.rb +0 -76
  159. data/spec/unit/migrator_spec.rb +0 -77
  160. data/spec/unit/reloader_spec.rb +0 -24
data/Gemfile CHANGED
@@ -1,10 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'http://rubygems.org'
2
4
 
3
5
  gemspec
4
-
5
- gem 'rails', '>= 3.1.2'
6
-
7
- group :local do
8
- gem 'pry'
9
- gem 'guard-rspec', '~> 4.2'
10
- end
data/Guardfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # A sample Guardfile
2
4
  # More info at https://github.com/guard/guard#readme
3
5
 
@@ -5,20 +7,5 @@ guard :rspec do
5
7
  watch(%r{^spec/.+_spec\.rb$})
6
8
  watch(%r{^lib/apartment/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" }
7
9
  watch(%r{^lib/apartment/(.+)\.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' }
10
+ watch('spec/spec_helper.rb') { 'spec' }
24
11
  end
data/README.md CHANGED
@@ -1,10 +1,7 @@
1
1
  # Apartment
2
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
3
+ [![Gem Version](https://badge.fury.io/rb/ros-apartment.svg)](https://badge.fury.io/rb/ros-apartment)
4
+ [![Code Climate](https://api.codeclimate.com/v1/badges/b0dc327380bb8438f991/maintainability)](https://codeclimate.com/github/rails-on-services/apartment/maintainability)
8
5
 
9
6
  *Multitenancy for Rails and ActiveRecord*
10
7
 
@@ -12,26 +9,21 @@ Apartment provides tools to help you deal with multiple tenants in your Rails
12
9
  application. If you need to have certain data sequestered based on account or company,
13
10
  but still allow some data to exist in a common tenant, Apartment can help.
14
11
 
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)
12
+ ## Apartment drop in replacement gem
22
13
 
23
- ## Excessive Memory Issues on ActiveRecord 4.x
14
+ After having reached out via github issues and email directly, no replies ever
15
+ came. Since we wanted to upgrade our application to Rails 6 we decided to fork
16
+ and start some development to support Rails 6. Because we don't have access
17
+ to the apartment gem itself, the solution was to release it under a different
18
+ name but providing the exact same API as it was before.
24
19
 
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.
20
+ ## Help wanted
31
21
 
32
- ```ruby
33
- gem 'rails', '4.2.1', github: 'influitive/rails', tag: 'v4.2.1.memfix'
34
- ```
22
+ We were never involved with the development of Apartment gem in the first place
23
+ and this project started out of our own needs. We will be more than happy
24
+ to collaborate to maintain the gem alive and supporting the latest versions
25
+ of ruby and rails, but your help is appreciated. Either by reporting bugs you
26
+ may find or proposing improvements to the gem itself. Feel free to reach out.
35
27
 
36
28
  ## Installation
37
29
 
@@ -40,7 +32,7 @@ gem 'rails', '4.2.1', github: 'influitive/rails', tag: 'v4.2.1.memfix'
40
32
  Add the following to your Gemfile:
41
33
 
42
34
  ```ruby
43
- gem 'apartment'
35
+ gem 'ros-apartment', require: 'apartment'
44
36
  ```
45
37
 
46
38
  Then generate your `Apartment` config file using
@@ -75,7 +67,7 @@ you need to create a new tenant, you can run the following command:
75
67
  Apartment::Tenant.create('tenant_name')
76
68
  ```
77
69
 
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".
70
+ If you're using the [prepend environment](https://github.com/rails-on-services/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
71
  In the case of a sqlite database, this will be created in your 'db/' folder. With
80
72
  other databases, the tenant will be created as a new DB within the system.
81
73
 
@@ -108,6 +100,16 @@ switched back at the end of the block to what it was before.
108
100
  There is also `switch!` which doesn't take a block, but it's recommended to use `switch`.
109
101
  To return to the default tenant, you can call `switch` with no arguments.
110
102
 
103
+ #### Multiple Tenants
104
+
105
+ When using schemas, you can also pass in a list of schemas if desired. Any tables defined in a schema earlier in the chain will be referenced first, so this is only useful if you have a schema with only some of the tables defined:
106
+
107
+ ```ruby
108
+ Apartment::Tenant.switch(['tenant_1', 'tenant_2']) do
109
+ # ...
110
+ end
111
+ ```
112
+
111
113
  ### Switching Tenants per request
112
114
 
113
115
  You can have Apartment route to the appropriate tenant by adding some Rack middleware.
@@ -236,7 +238,7 @@ A Generic Elevator exists that allows you to pass a `Proc` (or anything that res
236
238
  module MyApplication
237
239
  class Application < Rails::Application
238
240
  # Obviously not a contrived example
239
- config.middleware.use Apartment::Elevators::Generic, Proc.new { |request| request.host.reverse }
241
+ config.middleware.use Apartment::Elevators::Generic, proc { |request| request.host.reverse }
240
242
  end
241
243
  end
242
244
  ```
@@ -272,7 +274,7 @@ In the examples above, we show the Apartment middleware being appended to the Ra
272
274
  Rails.application.config.middleware.use Apartment::Elevators::Subdomain
273
275
  ```
274
276
 
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.
277
+ 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/rails-on-services/apartment/blob/development/lib/apartment/elevators/generic.rb#L22) elevator, so all elevators that inherit from this elevator will operate as such.
276
278
 
277
279
  It's also good to note that Apartment switches back to the "public" tenant any time an error is raised in your application.
278
280
 
@@ -296,6 +298,27 @@ Apartment::Tenant.drop('tenant_name')
296
298
 
297
299
  When method is called, the schema is dropped and all data from itself will be lost. Be careful with this method.
298
300
 
301
+ ### Custom Prompt
302
+
303
+ #### Console methods
304
+
305
+ `ros-apartment` console configures two helper methods:
306
+ 1. `tenant_list` - list available tenants while using the console
307
+ 2. `st(tenant_name:String)` - Switches the context to the tenant name passed, if
308
+ it exists.
309
+
310
+ #### Custom printed prompt
311
+
312
+ `ros-apartment` also has a custom prompt that gives a bit more information about
313
+ the context in which you're running. It shows the environment as well as the tenant
314
+ that is currently switched to. In order for you to enable this, you need to require
315
+ the custom console in your application.
316
+
317
+ In `application.rb` add `require 'apartment/custom_console'`.
318
+ Please note that we rely on `pry-rails` to edit the prompt, thus your project needs
319
+ to install it as well. In order to do so, you need to add `gem 'pry-rails'` to your
320
+ project's gemfile.
321
+
299
322
  ## Config
300
323
 
301
324
  The following config options should be set up in a Rails initializer such as:
@@ -310,6 +333,37 @@ Apartment.configure do |config|
310
333
  end
311
334
  ```
312
335
 
336
+ ### Skip tenant schema check
337
+
338
+ This is configurable by setting: `tenant_presence_check`. It defaults to true
339
+ in order to maintain the original gem behavior. This is only checked when using one of the PostgreSQL adapters.
340
+ The original gem behavior, when running `switch` would look for the existence of the schema before switching. This adds an extra query on every context switch. While in the default simple scenarios this is a valid check, in high volume platforms this adds some unnecessary overhead which can be detected in some other ways on the application level.
341
+
342
+ Setting this configuration value to `false` will disable the schema presence check before trying to switch the context.
343
+
344
+ ```ruby
345
+ Apartment.configure do |config|
346
+ config.tenant_presence_check = false
347
+ end
348
+ ```
349
+
350
+ ### Additional logging information
351
+
352
+ Enabling this configuration will output the database that the process is currently connected to as well as which
353
+ schemas are in the search path. This can be enabled by setting to true the `active_record_log` configuration.
354
+
355
+ Please note that our custom logger inherits from `ActiveRecord::LogSubscriber` so this will be required for the configuration to work.
356
+
357
+ **Example log output:**
358
+
359
+ <img src="documentation/images/log_example.png">
360
+
361
+ ```ruby
362
+ Apartment.configure do |config|
363
+ config.active_record_log = true
364
+ end
365
+ ```
366
+
313
367
  ### Excluding models
314
368
 
315
369
  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:
@@ -327,17 +381,30 @@ Rails will always access the 'public' tenant when accessing these models, but no
327
381
 
328
382
  ### Postgresql Schemas
329
383
 
330
- ## Providing a Different default_schema
384
+ #### Alternative: Creating new schemas by using raw SQL dumps
385
+
386
+ 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.
387
+
388
+ This only applies while using postgres adapter and `config.use_schemas` is set to `true`.
389
+ (Note: this option doesn't use `db/structure.sql`, it creates SQL dump by executing `pg_dump`)
390
+
391
+ Enable this option with:
392
+
393
+ ```ruby
394
+ config.use_sql = true
395
+ ```
396
+
397
+ ### Providing a Different default_tenant
331
398
 
332
399
  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
400
 
334
401
  ```ruby
335
- config.default_schema = "some_other_schema"
402
+ config.default_tenant = "some_other_schema"
336
403
  ```
337
404
 
338
405
  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
406
 
340
- ## Persistent Schemas
407
+ ### Persistent Schemas
341
408
 
342
409
  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
410
 
@@ -405,7 +472,7 @@ schema_search_path: "public,shared_extensions"
405
472
  ...
406
473
  ```
407
474
 
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:
475
+ This would be for a config with `default_tenant` 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
476
 
410
477
  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
478
  2. Run `heroku pg:psql` from your command line
@@ -443,18 +510,6 @@ schema in the `search_path` at all times. We won't be able to do this though unt
443
510
  also contain the tenanted tables, which is an open issue with no real milestone to be completed.
444
511
  Happy to accept PR's on the matter.
445
512
 
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
513
  ### Managing Migrations
459
514
 
460
515
  In order to migrate all of your tenants (or postgresql schemas) you need to provide a list
@@ -475,7 +530,7 @@ You can then migrate your tenants using the normal rake task:
475
530
  rake db:migrate
476
531
  ```
477
532
 
478
- This just invokes `Apartment::Tenant.migrate(#{tenant_name})` for each tenant name supplied
533
+ This just invokes `Apartment::Migrator.migrate(#{tenant_name})` for each tenant name supplied
479
534
  from `Apartment.tenant_names`
480
535
 
481
536
  Note that you can disable the default migrating of all tenants with `db:migrate` by setting
@@ -533,7 +588,12 @@ end
533
588
 
534
589
  ## Background workers
535
590
 
536
- See [apartment-sidekiq](https://github.com/influitive/apartment-sidekiq) or [apartment-activejob](https://github.com/influitive/apartment-activejob).
591
+ Both these gems have been forked as a side consequence of having a new gem name.
592
+ You can use them exactly as you were using before. They are, just like this one
593
+ a drop-in replacement.
594
+
595
+ See [apartment-sidekiq](https://github.com/rails-on-services/apartment-sidekiq)
596
+ or [apartment-activejob](https://github.com/rails-on-services/apartment-activejob).
537
597
 
538
598
  ## Callbacks
539
599
 
@@ -560,6 +620,16 @@ module Apartment
560
620
  end
561
621
  ```
562
622
 
623
+ ## Running rails console without a connection to the database
624
+
625
+ By default, once apartment starts, it establishes a connection to the database. It is possible to
626
+ disable this initial connection, by running with `APARTMENT_DISABLE_INIT` set to something:
627
+
628
+ ```shell
629
+ $ APARTMENT_DISABLE_INIT=true DATABASE_URL=postgresql://localhost:1234/buk_development bin/rails runner 'puts 1'
630
+ # 1
631
+ ```
632
+
563
633
  ## Contributing
564
634
 
565
635
  * In both `spec/dummy/config` and `spec/config`, you will see `database.yml.sample` files
@@ -571,6 +641,13 @@ end
571
641
 
572
642
  * If you're looking to help, check out the TODO file for some upcoming changes I'd like to implement in Apartment.
573
643
 
644
+ ### Running bundle install
645
+
646
+ mysql2 gem in some cases fails to install.
647
+ If you face problems running bundle install in OSX, try installing the gem running:
648
+
649
+ `gem install mysql2 -v '0.5.3' -- --with-ldflags=-L/usr/local/opt/openssl/lib --with-cppflags=-I/usr/local/opt/openssl/include`
650
+
574
651
  ## License
575
652
 
576
653
  Apartment is released under the [MIT License](http://www.opensource.org/licenses/MIT).
data/Rakefile CHANGED
@@ -1,19 +1,25 @@
1
- require 'bundler' rescue 'You must `gem install bundler` and `bundle install` to run rake tasks'
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'bundler'
5
+ rescue StandardError
6
+ 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
2
8
  Bundler.setup
3
9
  Bundler::GemHelper.install_tasks
4
10
 
5
11
  require 'appraisal'
6
12
 
7
- require "rspec"
8
- require "rspec/core/rake_task"
13
+ require 'rspec'
14
+ require 'rspec/core/rake_task'
9
15
 
10
- RSpec::Core::RakeTask.new(:spec => %w{ db:copy_credentials db:test:prepare }) do |spec|
11
- spec.pattern = "spec/**/*_spec.rb"
16
+ RSpec::Core::RakeTask.new(spec: %w[db:copy_credentials db:test:prepare]) do |spec|
17
+ spec.pattern = 'spec/**/*_spec.rb'
12
18
  # spec.rspec_opts = '--order rand:47078'
13
19
  end
14
20
 
15
21
  namespace :spec do
16
- [:tasks, :unit, :adapters, :integration].each do |type|
22
+ %i[tasks unit adapters integration].each do |type|
17
23
  RSpec::Core::RakeTask.new(type => :spec) do |spec|
18
24
  spec.pattern = "spec/#{type}/**/*_spec.rb"
19
25
  end
@@ -27,11 +33,11 @@ task :console do
27
33
  Pry.start
28
34
  end
29
35
 
30
- task :default => :spec
36
+ task default: :spec
31
37
 
32
38
  namespace :db do
33
39
  namespace :test do
34
- task :prepare => %w{postgres:drop_db postgres:build_db mysql:drop_db mysql:build_db}
40
+ task prepare: %w[postgres:drop_db postgres:build_db mysql:drop_db mysql:build_db]
35
41
  end
36
42
 
37
43
  desc "copy sample database credential files over if real files don't exist"
@@ -40,29 +46,36 @@ namespace :db do
40
46
  apartment_db_file = 'spec/config/database.yml'
41
47
  rails_db_file = 'spec/dummy/config/database.yml'
42
48
 
43
- FileUtils.copy(apartment_db_file + '.sample', apartment_db_file, :verbose => true) unless File.exists?(apartment_db_file)
44
- FileUtils.copy(rails_db_file + '.sample', rails_db_file, :verbose => true) unless File.exists?(rails_db_file)
49
+ unless File.exist?(apartment_db_file)
50
+ FileUtils.copy("#{apartment_db_file}.sample", apartment_db_file, verbose: true)
51
+ end
52
+ FileUtils.copy("#{rails_db_file}.sample", rails_db_file, verbose: true) unless File.exist?(rails_db_file)
45
53
  end
46
54
  end
47
55
 
48
56
  namespace :postgres do
49
57
  require 'active_record'
50
- require "#{File.join(File.dirname(__FILE__), 'spec', 'support', 'config')}"
58
+ require File.join(File.dirname(__FILE__), 'spec', 'support', 'config').to_s
51
59
 
52
60
  desc 'Build the PostgreSQL test databases'
53
61
  task :build_db do
54
62
  params = []
55
- params << "-E UTF8"
63
+ params << '-E UTF8'
56
64
  params << pg_config['database']
57
65
  params << "-U#{pg_config['username']}"
58
66
  params << "-h#{pg_config['host']}" if pg_config['host']
59
67
  params << "-p#{pg_config['port']}" if pg_config['port']
60
- %x{ createdb #{params.join(' ')} } rescue "test db already exists"
68
+
69
+ begin
70
+ `createdb #{params.join(' ')}`
71
+ rescue StandardError
72
+ 'test db already exists'
73
+ end
61
74
  ActiveRecord::Base.establish_connection pg_config
62
75
  migrate
63
76
  end
64
77
 
65
- desc "drop the PostgreSQL test database"
78
+ desc 'drop the PostgreSQL test database'
66
79
  task :drop_db do
67
80
  puts "dropping database #{pg_config['database']}"
68
81
  params = []
@@ -70,14 +83,13 @@ namespace :postgres do
70
83
  params << "-U#{pg_config['username']}"
71
84
  params << "-h#{pg_config['host']}" if pg_config['host']
72
85
  params << "-p#{pg_config['port']}" if pg_config['port']
73
- %x{ dropdb #{params.join(' ')} }
86
+ `dropdb #{params.join(' ')}`
74
87
  end
75
-
76
88
  end
77
89
 
78
90
  namespace :mysql do
79
91
  require 'active_record'
80
- require "#{File.join(File.dirname(__FILE__), 'spec', 'support', 'config')}"
92
+ require File.join(File.dirname(__FILE__), 'spec', 'support', 'config').to_s
81
93
 
82
94
  desc 'Build the MySQL test databases'
83
95
  task :build_db do
@@ -85,24 +97,29 @@ namespace :mysql do
85
97
  params << "-h #{my_config['host']}" if my_config['host']
86
98
  params << "-u #{my_config['username']}" if my_config['username']
87
99
  params << "-p#{my_config['password']}" if my_config['password']
88
- %x{ mysqladmin #{params.join(' ')} create #{my_config['database']} } rescue "test db already exists"
100
+ params << "--port #{my_config['port']}" if my_config['port']
101
+ begin
102
+ `mysqladmin #{params.join(' ')} create #{my_config['database']}`
103
+ rescue StandardError
104
+ 'test db already exists'
105
+ end
89
106
  ActiveRecord::Base.establish_connection my_config
90
107
  migrate
91
108
  end
92
109
 
93
- desc "drop the MySQL test database"
110
+ desc 'drop the MySQL test database'
94
111
  task :drop_db do
95
112
  puts "dropping database #{my_config['database']}"
96
113
  params = []
97
114
  params << "-h #{my_config['host']}" if my_config['host']
98
115
  params << "-u #{my_config['username']}" if my_config['username']
99
116
  params << "-p#{my_config['password']}" if my_config['password']
100
- %x{ mysqladmin #{params.join(' ')} drop #{my_config['database']} --force}
117
+ params << "--port #{my_config['port']}" if my_config['port']
118
+ `mysqladmin #{params.join(' ')} drop #{my_config['database']} --force`
101
119
  end
102
-
103
120
  end
104
121
 
105
- # TODO clean this up
122
+ # TODO: clean this up
106
123
  def config
107
124
  Apartment::Test.config['connections']
108
125
  end
@@ -115,14 +132,8 @@ def my_config
115
132
  config['mysql']
116
133
  end
117
134
 
118
- def activerecord_below_5_2?
119
- ActiveRecord.version.release < Gem::Version.new('5.2.0')
120
- end
121
-
122
135
  def migrate
123
- if activerecord_below_5_2?
124
- ActiveRecord::Migrator.migrate('spec/dummy/db/migrate')
125
- else
126
- ActiveRecord::MigrationContext.new('spec/dummy/db/migrate').migrate
127
- end
136
+ # TODO: Figure out if there is any other possibility that can/should be
137
+ # passed here as the second argument for the migration context
138
+ ActiveRecord::MigrationContext.new('spec/dummy/db/migrate', ActiveRecord::SchemaMigration).migrate
128
139
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord # :nodoc:
4
+ # This is monkeypatching Active Record to ensure that whenever a new connection is established it
5
+ # switches to the same tenant as before the connection switching. This problem is more evident when
6
+ # using read replica in Rails 6
7
+ module ConnectionHandling
8
+ if ActiveRecord.version.release <= Gem::Version.new('6.2')
9
+ def connected_to_with_tenant(database: nil, role: nil, prevent_writes: false, &blk)
10
+ current_tenant = Apartment::Tenant.current
11
+
12
+ connected_to_without_tenant(database: database, role: role, prevent_writes: prevent_writes) do
13
+ Apartment::Tenant.switch!(current_tenant)
14
+ yield(blk)
15
+ end
16
+ end
17
+ else
18
+ def connected_to_with_tenant(role: nil, prevent_writes: false, &blk)
19
+ current_tenant = Apartment::Tenant.current
20
+
21
+ connected_to_without_tenant(role: role, prevent_writes: prevent_writes) do
22
+ Apartment::Tenant.switch!(current_tenant)
23
+ yield(blk)
24
+ end
25
+ end
26
+ end
27
+
28
+ alias connected_to_without_tenant connected_to
29
+ alias connected_to connected_to_with_tenant
30
+ end
31
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ class InternalMetadata < ActiveRecord::Base # :nodoc:
4
+ class << self
5
+ def table_exists?
6
+ connection.table_exists?(table_name)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Style/ClassAndModuleChildren
4
+
5
+ # NOTE: This patch is meant to remove any schema_prefix appart from the ones for
6
+ # excluded models. The schema_prefix would be resolved by apartment's setting
7
+ # of search path
8
+ module Apartment::PostgreSqlAdapterPatch
9
+ def default_sequence_name(table, _column)
10
+ res = super
11
+
12
+ # for JDBC driver, if rescued in super_method, trim leading and trailing quotes
13
+ res.delete!('"') if defined?(JRUBY_VERSION)
14
+
15
+ schema_prefix = "#{Apartment::Tenant.current}."
16
+ default_tenant_prefix = "#{Apartment::Tenant.default_tenant}."
17
+
18
+ # NOTE: Excluded models should always access the sequence from the default
19
+ # tenant schema
20
+ if excluded_model?(table)
21
+ res.sub!(schema_prefix, default_tenant_prefix) if schema_prefix != default_tenant_prefix
22
+ return res
23
+ end
24
+
25
+ res.delete_prefix!(schema_prefix) if res&.starts_with?(schema_prefix)
26
+
27
+ res
28
+ end
29
+
30
+ private
31
+
32
+ def excluded_model?(table)
33
+ Apartment.excluded_models.any? { |m| m.constantize.table_name == table }
34
+ end
35
+ end
36
+
37
+ require 'active_record/connection_adapters/postgresql_adapter'
38
+
39
+ # NOTE: inject this into postgresql adapters
40
+ class ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
41
+ include Apartment::PostgreSqlAdapterPatch
42
+ end
43
+ # rubocop:enable Style/ClassAndModuleChildren
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class SchemaMigration # :nodoc:
5
+ class << self
6
+ def table_exists?
7
+ connection.table_exists?(table_name)
8
+ end
9
+ end
10
+ end
11
+ end