diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..2f3e3c8b --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,78 @@ +version: 2.1 + +orbs: + rubocop: hanachin/rubocop@0.0.6 + +jobs: + build: + docker: + - image: circleci/<< parameters.ruby_version >> + - image: circleci/postgres:9.6.2-alpine + - image: circleci/mysql:5.7 + environment: + MYSQL_ALLOW_EMPTY_PASSWORD: "yes" + parameters: + ruby_version: + type: string + gemfile: + type: string + environment: + BUNDLE_GEMFILE: << parameters.gemfile >> + steps: + - checkout + # Restore Cached Dependencies + # - restore_cache: + # keys: + # - gem-cache-v1-{{ arch }}-{{ .Branch }}-{{ checksum "<< parameters.gemfile >>.lock" }} + # - gem-cache-v1-{{ arch }}-{{ .Branch }} + # - gem-cache-v1 + + - run: bundle install --path vendor/bundle + + # - save_cache: + # key: gem-cache-v1-{{ arch }}-{{ .Branch }}-{{ checksum "<< parameters.gemfile >>.lock" }} + # paths: + # - vendor/bundle + + - run: + name: Install postgres client + command: sudo apt install -y postgresql-client + + - run: + name: Install mysql client + command: sudo apt install -y default-mysql-client + + - run: + name: Configure config database.yml + command: bundle exec rake db:copy_credentials + + - run: + name: wait for postgresql + command: dockerize -wait tcp://localhost:5432 -timeout 1m + + - run: + name: wait for mysql + command: dockerize -wait tcp://localhost:3306 -timeout 1m + + - run: + name: Database Setup + command: | + bundle exec rake db:test:prepare + + - run: + name: Run tests + command: bundle exec rspec + +workflows: + tests: + jobs: + - build: + matrix: + parameters: + ruby_version: ["ruby:2.6-buster", "ruby:2.7-buster"] + gemfile: ["gemfiles/rails_5_2.gemfile", "gemfiles/rails_6_0.gemfile", "gemfiles/rails_6_1.gemfile"] + + rubocop: + jobs: + - rubocop/rubocop: + version: 0.88.0 diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml new file mode 100644 index 00000000..ecf5c1bf --- /dev/null +++ b/.github/workflows/changelog.yml @@ -0,0 +1,63 @@ +name: Changelog + +on: + pull_request: + types: [closed] + + release: + types: [published] + + issues: + types: [closed, edited] + +jobs: + generate_changelog: + runs-on: ubuntu-latest + name: Generate changelog for master branch + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 # otherwise, you will failed to push refs to dest repo + + - name: Generate changelog + uses: charmixer/auto-changelog-action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Commit files + env: + ACTION_EMAIL: action@github.com + ACTION_USERNAME: GitHub Action + run: | + git config --local user.email "$ACTION_EMAIL" + git config --local user.name "$ACTION_USERNAME" + git add CHANGELOG.md && git commit -m 'Updated CHANGELOG.md' && echo ::set-env name=push::1 || echo "No changes to CHANGELOG.md" + + - name: Push changes + if: env.push == 1 + env: + # CI_USER: ${{ secrets.YOUR_GITHUB_USER }} + CI_TOKEN: ${{ secrets.CHANGELOG_GITHUB_TOKEN }} + run: | + git push "https://$GITHUB_ACTOR:$CI_TOKEN@github.com/$GITHUB_REPOSITORY.git" HEAD:master + + # - name: Push changelog to master + # if: env.push == 1 + # uses: ad-m/github-push-action@master + # with: + # github_token: ${{ secrets.CHANGELOG_GITHUB_TOKEN }} + # branch: master + + # - name: Cherry-pick changelog to development + # if: env.push == 1 + # env: + # ACTION_EMAIL: action@github.com + # ACTION_USERNAME: GitHub Action + # run: | + # git config --local user.email "$ACTION_EMAIL" + # git config --local user.name "$ACTION_USERNAME" + # commit_hash=`git show HEAD | egrep commit\ .+$ | cut -d' ' -f2` + # git checkout development + # git pull + # git cherry-pick $commit_hash + # git push diff --git a/.pryrc b/.pryrc index 22e16cde..33d98970 100644 --- a/.pryrc +++ b/.pryrc @@ -1,3 +1,5 @@ -if defined?(Rails) && Rails.env - extend Rails::ConsoleMethods -end +# frozen_string_literal: true + +# rubocop:disable Style/MixinUsage +extend Rails::ConsoleMethods if defined?(Rails) && Rails.env +# rubocop:enable Style/MixinUsage diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 00000000..b7c5c8b6 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,96 @@ +inherit_from: .rubocop_todo.yml + +AllCops: + Exclude: + - 'gemfiles/**/*.gemfile' + - 'gemfiles/vendor/**/*' + - 'spec/dummy_engine/dummy_engine.gemspec' + +Gemspec/RequiredRubyVersion: + Exclude: + - 'ros-apartment.gemspec' + +Style/WordArray: + Exclude: + - spec/schemas/**/*.rb + +Style/NumericLiterals: + Exclude: + - spec/schemas/**/*.rb + +Layout/EmptyLineAfterMagicComment: + Exclude: + - spec/schemas/**/*.rb + +Metrics/BlockLength: + Exclude: + - spec/**/*.rb + +Layout/EmptyLinesAroundAttributeAccessor: + Enabled: true + +Layout/SpaceAroundMethodCallOperator: + Enabled: true + +Lint/DeprecatedOpenSSLConstant: + Enabled: true + +Lint/DuplicateElsifCondition: + Enabled: true + +Lint/MixedRegexpCaptureTypes: + Enabled: true + +Lint/RaiseException: + Enabled: true + +Lint/StructNewOverride: + Enabled: true + +Style/AccessorGrouping: + Enabled: true + +Style/ArrayCoercion: + Enabled: true + +Style/BisectedAttrAccessor: + Enabled: true + +Style/CaseLikeIf: + Enabled: true + +Style/ExponentialNotation: + Enabled: true + +Style/HashAsLastArrayItem: + Enabled: true + +Style/HashEachMethods: + Enabled: true + +Style/HashLikeCase: + Enabled: true + +Style/HashTransformKeys: + Enabled: true + +Style/HashTransformValues: + Enabled: true + +Style/RedundantAssignment: + Enabled: true + +Style/RedundantFetchBlock: + Enabled: true + +Style/RedundantFileExtensionInRequire: + Enabled: true + +Style/RedundantRegexpCharacterClass: + Enabled: true + +Style/RedundantRegexpEscape: + Enabled: true + +Style/SlicingWithRange: + Enabled: true diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 00000000..c72ac883 --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,66 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2020-07-16 04:15:41 UTC using RuboCop version 0.88.0. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 1 +Lint/MixedRegexpCaptureTypes: + Exclude: + - 'lib/apartment/elevators/domain.rb' + +# Offense count: 2 +# Cop supports --auto-correct. +Lint/NonDeterministicRequireOrder: + Exclude: + - 'spec/spec_helper.rb' + +# Offense count: 7 +# Configuration parameters: IgnoredMethods. +Metrics/AbcSize: + Max: 33 + +# Offense count: 3 +# Configuration parameters: CountComments, CountAsOne, ExcludedMethods. +# ExcludedMethods: refine +Metrics/BlockLength: + Max: 102 + +# Offense count: 1 +# Configuration parameters: CountComments, CountAsOne. +Metrics/ClassLength: + Max: 151 + +# Offense count: 6 +# Configuration parameters: CountComments, CountAsOne, ExcludedMethods. +Metrics/MethodLength: + Max: 24 + +# Offense count: 17 +Style/Documentation: + Exclude: + - 'spec/**/*' + - 'test/**/*' + - 'lib/apartment/adapters/jdbc_mysql_adapter.rb' + - 'lib/apartment/adapters/postgis_adapter.rb' + - 'lib/apartment/adapters/postgresql_adapter.rb' + - 'lib/apartment/adapters/sqlite3_adapter.rb' + - 'lib/apartment/custom_console.rb' + - 'lib/apartment/deprecation.rb' + - 'lib/apartment/migrator.rb' + - 'lib/apartment/model.rb' + - 'lib/apartment/railtie.rb' + - 'lib/apartment/reloader.rb' + - 'lib/apartment/tasks/enhancements.rb' + - 'lib/apartment/tasks/task_helper.rb' + - 'lib/generators/apartment/install/install_generator.rb' + +# Offense count: 3 +# Cop supports --auto-correct. +Style/IfUnlessModifier: + Exclude: + - 'Rakefile' + - 'lib/apartment.rb' + - 'lib/apartment/tenant.rb' diff --git a/.story_branch.yml b/.story_branch.yml new file mode 100644 index 00000000..4143ff44 --- /dev/null +++ b/.story_branch.yml @@ -0,0 +1,5 @@ +--- +tracker: github +issue_placement: beginning +project_id: +- rails-on-services/apartment diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 78067b58..00000000 --- a/.travis.yml +++ /dev/null @@ -1,64 +0,0 @@ -sudo: required -language: ruby -services: - - docker -rvm: - - jruby-9.1.15.0 - - 2.1.9 - - 2.2.9 - - 2.3.6 - - 2.4.3 - - 2.5.0 - - 2.6.2 - - ruby-head -gemfile: - - gemfiles/rails_4_2.gemfile - - gemfiles/rails_5_0.gemfile - - gemfiles/rails_5_1.gemfile - - gemfiles/rails_5_2.gemfile - - gemfiles/rails_6_0.gemfile - - gemfiles/rails_master.gemfile -bundler_args: --without local -before_install: - - sudo /etc/init.d/mysql stop - - sudo /etc/init.d/postgresql stop - - docker-compose up -d - - gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true - - gem uninstall bundler -v '>= 2' -x || true - - gem install bundler -v '< 2' -env: - RUBY_GC_MALLOC_LIMIT: 90000000 - RUBY_GC_HEAP_FREE_SLOTS: 200000 -matrix: - allow_failures: - - rvm: ruby-head - - gemfile: gemfiles/rails_master.gemfile - - rvm: jruby-9.1.15.0 - gemfile: gemfiles/rails_5_0.gemfile - exclude: - - rvm: 2.1.9 - gemfile: gemfiles/rails_5_0.gemfile - - rvm: 2.1.9 - gemfile: gemfiles/rails_5_1.gemfile - - rvm: 2.1.9 - gemfile: gemfiles/rails_5_2.gemfile - - rvm: 2.1.9 - gemfile: gemfiles/rails_6_0.gemfile - - rvm: 2.1.9 - gemfile: gemfiles/rails_master.gemfile - - rvm: 2.2.9 - gemfile: gemfiles/rails_6_0.gemfile - - rvm: 2.3.6 - gemfile: gemfiles/rails_6_0.gemfile - - rvm: 2.4.3 - gemfile: gemfiles/rails_6_0.gemfile - - rvm: jruby-9.1.15.0 - gemfile: gemfiles/rails_5_1.gemfile - - rvm: jruby-9.1.15.0 - gemfile: gemfiles/rails_5_2.gemfile - - rvm: jruby-9.1.15.0 - gemfile: gemfiles/rails_6_0.gemfile - - rvm: jruby-9.1.15.0 - gemfile: gemfiles/rails_master.gemfile - fast_finish: true -cache: bundler diff --git a/Appraisals b/Appraisals index f6904095..5f55c5ac 100644 --- a/Appraisals +++ b/Appraisals @@ -1,20 +1,9 @@ -appraise "rails-4-2" do - gem "rails", "~> 4.2.0" - platforms :ruby do - gem "pg", "< 1.0.0" - gem "mysql2", "~> 0.4.0" - end - platforms :jruby do - gem 'activerecord-jdbc-adapter', '~> 1.3' - gem 'activerecord-jdbcpostgresql-adapter', '~> 1.3' - gem 'activerecord-jdbcmysql-adapter', '~> 1.3' - end -end +# frozen_string_literal: true -appraise "rails-5-0" do - gem "rails", "~> 5.0.0" +appraise 'rails-5-0' do + gem 'rails', '~> 5.0.0' platforms :ruby do - gem "pg", "< 1.0.0" + gem 'pg', '< 1.0.0' end platforms :jruby do gem 'activerecord-jdbc-adapter', '~> 50.0' @@ -23,10 +12,10 @@ appraise "rails-5-0" do end end -appraise "rails-5-1" do - gem "rails", "~> 5.1.0" +appraise 'rails-5-1' do + gem 'rails', '~> 5.1.0' platforms :ruby do - gem "pg", "< 1.0.0" + gem 'pg', '< 1.0.0' end platforms :jruby do gem 'activerecord-jdbc-adapter', '~> 51.0' @@ -35,8 +24,8 @@ appraise "rails-5-1" do end end -appraise "rails-5-2" do - gem "rails", "~> 5.2.0" +appraise 'rails-5-2' do + gem 'rails', '~> 5.2.0' platforms :jruby do gem 'activerecord-jdbc-adapter', '~> 52.0' gem 'activerecord-jdbcpostgresql-adapter', '~> 52.0' @@ -44,28 +33,38 @@ appraise "rails-5-2" do end end - -appraise "rails-6-0" do - gem "rails", "~> 6.0.0.rc1" +appraise 'rails-6-0' do + gem 'rails', '~> 6.0.0' platforms :ruby do gem 'sqlite3', '~> 1.4' end platforms :jruby do - gem 'activerecord-jdbc-adapter', '~> 60.0.rc1' - gem 'activerecord-jdbcpostgresql-adapter', '~> 60.0.rc1' - gem 'activerecord-jdbcmysql-adapter', '~> 60.0.rc1' + gem 'activerecord-jdbc-adapter', '~> 60.0' + gem 'activerecord-jdbcpostgresql-adapter', '~> 60.0' + gem 'activerecord-jdbcmysql-adapter', '~> 60.0' end end +appraise 'rails-6-1' do + gem 'rails', '~> 6.1.0' + platforms :ruby do + gem 'sqlite3', '~> 1.4' + end + platforms :jruby do + gem 'activerecord-jdbc-adapter', '~> 61.0' + gem 'activerecord-jdbcpostgresql-adapter', '~> 61.0' + gem 'activerecord-jdbcmysql-adapter', '~> 61.0' + end +end -appraise "rails-master" do - gem "rails", git: 'https://github.com/rails/rails.git' +appraise 'rails-master' do + gem 'rails', git: 'https://github.com/rails/rails.git' platforms :ruby do gem 'sqlite3', '~> 1.4' end platforms :jruby do - gem 'activerecord-jdbc-adapter', '~> 52.0' - gem 'activerecord-jdbcpostgresql-adapter', '~> 52.0' - gem 'activerecord-jdbcmysql-adapter', '~> 52.0' + gem 'activerecord-jdbc-adapter', '~> 61.0' + gem 'activerecord-jdbcpostgresql-adapter', '~> 61.0' + gem 'activerecord-jdbcmysql-adapter', '~> 61.0' end end diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..4ce60fbe --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,963 @@ +# Changelog + +## [Unreleased](https://github.com/rails-on-services/apartment/tree/HEAD) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v2.8.0...HEAD) + +**Implemented enhancements:** + +**Fixed bugs:** + +- New version raises an error with ActiveSupport::LogSubscriber [#128](https://github.com/rails-on-services/apartment/issues/128) +- Weird logs when tenant fails to create [#127]() + +**Closed issues:** + +## [v2.8.0](https://github.com/rails-on-services/apartment/tree/v2.8.0) (2020-12-16) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v2.7.2...v2.8.0) + +**Implemented enhancements:** + +- Uses a transaction to create a tenant [#66](https://github.com/rails-on-services/apartment/issues/66) + +**Fixed bugs:** + +- Fix seeding errors [#86](https://github.com/rails-on-services/apartment/issues/86) +- When tests run in a transaction, new tenants in tests fail to create [#123](https://github.com/rails-on-services/apartment/issues/123) +- Reverted unsafe initializer - introduces the possibility of disabling the initial connection to the database via + environment variable. Relates to the following tickets/PRs: + - [#113](https://github.com/rails-on-services/apartment/issues/113) + - [#39](https://github.com/rails-on-services/apartment/pull/39) + - [#53](https://github.com/rails-on-services/apartment/pull/53) + - [#118](https://github.com/rails-on-services/apartment/pull/118) + +**Closed issues:** + +- Improve changelog automatic generation [#98](https://github.com/rails-on-services/apartment/issues/98) +- Relaxes dependencies to allow rails 6.1 [#121](https://github.com/rails-on-services/apartment/issues/121) + + +## [v2.7.2](https://github.com/rails-on-services/apartment/tree/v2.7.2) (2020-07-17) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v2.7.1...v2.7.2) + +**Implemented enhancements:** + +- Deprecate History.md [\#80](https://github.com/rails-on-services/apartment/issues/80) + +**Fixed bugs:** + +- Tenant.switch! raises exception on first call / Postgresql [\#92](https://github.com/rails-on-services/apartment/issues/92) +- NameError: instance variable @sequence\_name not defined [\#81](https://github.com/rails-on-services/apartment/issues/81) + +**Closed issues:** + +- Error creating tenant with uuid column [\#85](https://github.com/rails-on-services/apartment/issues/85) +- enhanced db:create task breaks plugins compatibility [\#82](https://github.com/rails-on-services/apartment/issues/82) +- Support disabling of full\_migration\_on\_create [\#30](https://github.com/rails-on-services/apartment/issues/30) + +**Merged pull requests:** + +- \[Chore\] Fix Changelog github action [\#97](https://github.com/rails-on-services/apartment/pull/97) ([rpbaltazar](https://github.com/rpbaltazar)) +- Prepare release - 2.7.2 [\#96](https://github.com/rails-on-services/apartment/pull/96) ([rpbaltazar](https://github.com/rpbaltazar)) +- \[Resolves \#92\] tenant switch raises exception on first call [\#95](https://github.com/rails-on-services/apartment/pull/95) ([rpbaltazar](https://github.com/rpbaltazar)) +- Dont use custom rubocop [\#94](https://github.com/rails-on-services/apartment/pull/94) ([rpbaltazar](https://github.com/rpbaltazar)) +- \[Resolves \#80\] added changelog action [\#90](https://github.com/rails-on-services/apartment/pull/90) ([rpbaltazar](https://github.com/rpbaltazar)) +- \[Resolves \#81\] check for var existence before [\#89](https://github.com/rails-on-services/apartment/pull/89) ([rpbaltazar](https://github.com/rpbaltazar)) + +## [v2.7.1](https://github.com/rails-on-services/apartment/tree/v2.7.1) (2020-06-27) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v2.7.0...v2.7.1) + +**Merged pull requests:** + +- Prepare Release 2.7.1 [\#84](https://github.com/rails-on-services/apartment/pull/84) ([rpbaltazar](https://github.com/rpbaltazar)) +- \[Resolves \#82\] Enhanced db create task breaks plugins compatibility [\#83](https://github.com/rails-on-services/apartment/pull/83) ([rpbaltazar](https://github.com/rpbaltazar)) +- \[ci\] update rake [\#79](https://github.com/rails-on-services/apartment/pull/79) ([ahorek](https://github.com/ahorek)) + +## [v2.7.0](https://github.com/rails-on-services/apartment/tree/v2.7.0) (2020-06-26) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v2.6.1...v2.7.0) + +**Implemented enhancements:** + +- Rake tasks define methods on main [\#70](https://github.com/rails-on-services/apartment/issues/70) + +**Fixed bugs:** + +- Undefined method devise with 2.6.1 [\#65](https://github.com/rails-on-services/apartment/issues/65) +- db:create is failed [\#61](https://github.com/rails-on-services/apartment/issues/61) + +**Closed issues:** + +- configure story branch [\#68](https://github.com/rails-on-services/apartment/issues/68) +- HISTORY.md has not been updated for latest releases [\#62](https://github.com/rails-on-services/apartment/issues/62) +- \[Postgresql users\] Help testing development branch in your environment [\#34](https://github.com/rails-on-services/apartment/issues/34) + +**Merged pull requests:** + +- Prepare Release - 2.7.0 [\#77](https://github.com/rails-on-services/apartment/pull/77) ([rpbaltazar](https://github.com/rpbaltazar)) +- \[Fixes \#61\] db create is failed [\#76](https://github.com/rails-on-services/apartment/pull/76) ([rpbaltazar](https://github.com/rpbaltazar)) +- \[Resolves \#70\] rake tasks define methods on main [\#75](https://github.com/rails-on-services/apartment/pull/75) ([rpbaltazar](https://github.com/rpbaltazar)) +- \[Chore\] Update travis config to run rubocop [\#74](https://github.com/rails-on-services/apartment/pull/74) ([rpbaltazar](https://github.com/rpbaltazar)) +- Remove and warn depracated config `tld\_length` [\#72](https://github.com/rails-on-services/apartment/pull/72) ([choznerol](https://github.com/choznerol)) +- \[Resolves \#62\] added Missing notes in history.md [\#63](https://github.com/rails-on-services/apartment/pull/63) ([rpbaltazar](https://github.com/rpbaltazar)) +- Add database and schema to active record log [\#55](https://github.com/rails-on-services/apartment/pull/55) ([woohoou](https://github.com/woohoou)) + +## [v2.6.1](https://github.com/rails-on-services/apartment/tree/v2.6.1) (2020-06-02) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v2.6.0...v2.6.1) + +**Closed issues:** + +- db:rollback uses second latest migration for tenants [\#56](https://github.com/rails-on-services/apartment/issues/56) +- rake db:setup tries to seed non existing tenant [\#52](https://github.com/rails-on-services/apartment/issues/52) +- Custom Console deprecation warning [\#37](https://github.com/rails-on-services/apartment/issues/37) + +**Merged pull requests:** + +- Version bump - 2.6.1 [\#60](https://github.com/rails-on-services/apartment/pull/60) ([rpbaltazar](https://github.com/rpbaltazar)) +- Prepare Release - 2.6.1 [\#59](https://github.com/rails-on-services/apartment/pull/59) ([rpbaltazar](https://github.com/rpbaltazar)) +- \[\#56\] Db rollback uses second latest migration [\#57](https://github.com/rails-on-services/apartment/pull/57) ([rpbaltazar](https://github.com/rpbaltazar)) +- \[\#52\] enhance after db create [\#54](https://github.com/rails-on-services/apartment/pull/54) ([rpbaltazar](https://github.com/rpbaltazar)) +- fix init after reload on development [\#53](https://github.com/rails-on-services/apartment/pull/53) ([fsateler](https://github.com/fsateler)) +- fix: reset sequence\_name after tenant switch [\#51](https://github.com/rails-on-services/apartment/pull/51) ([fsateler](https://github.com/fsateler)) +- Avoid early connection [\#39](https://github.com/rails-on-services/apartment/pull/39) ([fsateler](https://github.com/fsateler)) + +## [v2.6.0](https://github.com/rails-on-services/apartment/tree/v2.6.0) (2020-05-14) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v2.5.0...v2.6.0) + +**Closed issues:** + +- Error Dropping Tenant [\#46](https://github.com/rails-on-services/apartment/issues/46) +- After switch callback not working with nil argument [\#42](https://github.com/rails-on-services/apartment/issues/42) +- Add tenant info to console boot? [\#41](https://github.com/rails-on-services/apartment/issues/41) +- Support configuration for skip checking of schema existence before switching [\#26](https://github.com/rails-on-services/apartment/issues/26) + +**Merged pull requests:** + +- \[Resolves \#37\] Custom console deprecation warning [\#49](https://github.com/rails-on-services/apartment/pull/49) ([rpbaltazar](https://github.com/rpbaltazar)) +- Prepare Release 2.6.0 [\#48](https://github.com/rails-on-services/apartment/pull/48) ([rpbaltazar](https://github.com/rpbaltazar)) +- Add console welcome message [\#47](https://github.com/rails-on-services/apartment/pull/47) ([JeremiahChurch](https://github.com/JeremiahChurch)) +- \[Resolves \#26\] Support configuration for skip checking of schema existence before switching [\#45](https://github.com/rails-on-services/apartment/pull/45) ([rpbaltazar](https://github.com/rpbaltazar)) +- \[Resolves \#42\] After switch callback not working with nil argument [\#43](https://github.com/rails-on-services/apartment/pull/43) ([rpbaltazar](https://github.com/rpbaltazar)) + +## [v2.5.0](https://github.com/rails-on-services/apartment/tree/v2.5.0) (2020-05-05) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/2.4.0...v2.5.0) + +**Implemented enhancements:** + +- Add latest ruby verisons to test matrix [\#31](https://github.com/rails-on-services/apartment/issues/31) +- Deprecate EOL ruby and rails versions [\#11](https://github.com/rails-on-services/apartment/issues/11) + +**Fixed bugs:** + +- When manually switching the connection it resets the search path [\#27](https://github.com/rails-on-services/apartment/issues/27) + +**Closed issues:** + +- Cached statement breaks in find [\#35](https://github.com/rails-on-services/apartment/issues/35) +- rails 6.1.alpha support [\#6](https://github.com/rails-on-services/apartment/issues/6) +- How to exclude all models from engine? [\#4](https://github.com/rails-on-services/apartment/issues/4) + +**Merged pull requests:** + +- Prepare Release 2.5.0 [\#44](https://github.com/rails-on-services/apartment/pull/44) ([rpbaltazar](https://github.com/rpbaltazar)) +- \[Resolves \#27\] Added before hook to connected to to try to set the tenant [\#40](https://github.com/rails-on-services/apartment/pull/40) ([rpbaltazar](https://github.com/rpbaltazar)) +- \[Resolves \#35\] update cache key to use a string or an array [\#36](https://github.com/rails-on-services/apartment/pull/36) ([rpbaltazar](https://github.com/rpbaltazar)) +- \[Hotfix \#27\] Some errors were being thrown due to caching issues [\#33](https://github.com/rails-on-services/apartment/pull/33) ([rpbaltazar](https://github.com/rpbaltazar)) +- \[Resolves \#31\] Add latest ruby verisons to test matrix [\#32](https://github.com/rails-on-services/apartment/pull/32) ([rpbaltazar](https://github.com/rpbaltazar)) +- \[Chore\] refactored files to their names [\#29](https://github.com/rails-on-services/apartment/pull/29) ([rpbaltazar](https://github.com/rpbaltazar)) +- \[Resolves \#27\] When manually switching the connection it resets the search path [\#28](https://github.com/rails-on-services/apartment/pull/28) ([rpbaltazar](https://github.com/rpbaltazar)) +- \[Resolves \#11\] Remove old ruby and rails versions from the supported versions [\#20](https://github.com/rails-on-services/apartment/pull/20) ([rpbaltazar](https://github.com/rpbaltazar)) +- Support rails 6.1 [\#7](https://github.com/rails-on-services/apartment/pull/7) ([jean-francois-labbe](https://github.com/jean-francois-labbe)) + +## [2.4.0](https://github.com/rails-on-services/apartment/tree/2.4.0) (2020-04-01) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v2.3.0...2.4.0) + +**Implemented enhancements:** + +- Add console info about tenants and fast switches [\#14](https://github.com/rails-on-services/apartment/issues/14) +- Update travis config to only run PR's instead of running all commits [\#12](https://github.com/rails-on-services/apartment/issues/12) + +**Closed issues:** + +- Rubocop cleanup [\#9](https://github.com/rails-on-services/apartment/issues/9) +- Apartment.configure throws NoMethodError [\#3](https://github.com/rails-on-services/apartment/issues/3) +- upcoming Rails 6 multi-database [\#2](https://github.com/rails-on-services/apartment/issues/2) + +**Merged pull requests:** + +- Fix gemspec open versions and updated version [\#25](https://github.com/rails-on-services/apartment/pull/25) ([rpbaltazar](https://github.com/rpbaltazar)) +- Fix gemspec open versions and updated version [\#24](https://github.com/rails-on-services/apartment/pull/24) ([rpbaltazar](https://github.com/rpbaltazar)) +- Cleanup travis matrix [\#23](https://github.com/rails-on-services/apartment/pull/23) ([rpbaltazar](https://github.com/rpbaltazar)) +- Prepare v2.4.0 Release [\#22](https://github.com/rails-on-services/apartment/pull/22) ([rpbaltazar](https://github.com/rpbaltazar)) +- Updated readme badges [\#21](https://github.com/rails-on-services/apartment/pull/21) ([rpbaltazar](https://github.com/rpbaltazar)) +- Rescuing ActiveRecord::NoDatabaseError when dropping tenants [\#19](https://github.com/rails-on-services/apartment/pull/19) ([rpbaltazar](https://github.com/rpbaltazar)) +- Skip init if we're running webpacker:compile [\#18](https://github.com/rails-on-services/apartment/pull/18) ([rpbaltazar](https://github.com/rpbaltazar)) +- \[Resolves \#14\] Add console info about tenants and fast switches [\#17](https://github.com/rails-on-services/apartment/pull/17) ([rpbaltazar](https://github.com/rpbaltazar)) +- Don't crash when no database connection is present [\#16](https://github.com/rails-on-services/apartment/pull/16) ([ArthurWD](https://github.com/ArthurWD)) +- \[Resolves \#12\] Update travis config to only run PRs instead of all commits [\#13](https://github.com/rails-on-services/apartment/pull/13) ([rpbaltazar](https://github.com/rpbaltazar)) +- \[Chore\] Fix rubocop usage [\#10](https://github.com/rails-on-services/apartment/pull/10) ([rpbaltazar](https://github.com/rpbaltazar)) +- \[Resolves \#9\] Cleanup rubocop todo [\#8](https://github.com/rails-on-services/apartment/pull/8) ([rpbaltazar](https://github.com/rpbaltazar)) +- Rakefile should use mysql port from configuration [\#5](https://github.com/rails-on-services/apartment/pull/5) ([jean-francois-labbe](https://github.com/jean-francois-labbe)) + +## [v2.3.0](https://github.com/rails-on-services/apartment/tree/v2.3.0) (2020-01-03) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v2.2.1...v2.3.0) + +**Merged pull requests:** + +- \[Resolves\] Basic support for Rails 6 [\#1](https://github.com/rails-on-services/apartment/pull/1) ([rpbaltazar](https://github.com/rpbaltazar)) + +## [v2.2.1](https://github.com/rails-on-services/apartment/tree/v2.2.1) (2019-06-19) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v2.2.0...v2.2.1) + +## [v2.2.0](https://github.com/rails-on-services/apartment/tree/v2.2.0) (2018-04-13) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v2.1.0...v2.2.0) + +## [v2.1.0](https://github.com/rails-on-services/apartment/tree/v2.1.0) (2017-12-15) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v2.0.0...v2.1.0) + +## [v2.0.0](https://github.com/rails-on-services/apartment/tree/v2.0.0) (2017-07-26) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v1.2.0...v2.0.0) + +## [v1.2.0](https://github.com/rails-on-services/apartment/tree/v1.2.0) (2016-07-28) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v1.1.0...v1.2.0) + +## [v1.1.0](https://github.com/rails-on-services/apartment/tree/v1.1.0) (2016-05-26) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v1.0.2...v1.1.0) + +## [v1.0.2](https://github.com/rails-on-services/apartment/tree/v1.0.2) (2015-07-02) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v1.0.1...v1.0.2) + +## [v1.0.1](https://github.com/rails-on-services/apartment/tree/v1.0.1) (2015-04-28) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v1.0.0...v1.0.1) + +## [v1.0.0](https://github.com/rails-on-services/apartment/tree/v1.0.0) (2015-02-03) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.26.1...v1.0.0) + +## [v0.26.1](https://github.com/rails-on-services/apartment/tree/v0.26.1) (2015-01-13) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.26.0...v0.26.1) + +## [v0.26.0](https://github.com/rails-on-services/apartment/tree/v0.26.0) (2015-01-05) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.25.2...v0.26.0) + +## [v0.25.2](https://github.com/rails-on-services/apartment/tree/v0.25.2) (2014-09-08) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.25.1...v0.25.2) + +## [v0.25.1](https://github.com/rails-on-services/apartment/tree/v0.25.1) (2014-07-17) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.25.0...v0.25.1) + +## [v0.25.0](https://github.com/rails-on-services/apartment/tree/v0.25.0) (2014-07-03) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.24.3...v0.25.0) + +## [v0.24.3](https://github.com/rails-on-services/apartment/tree/v0.24.3) (2014-03-05) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.24.2...v0.24.3) + +## [v0.24.2](https://github.com/rails-on-services/apartment/tree/v0.24.2) (2014-02-24) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.24.1...v0.24.2) + +## [v0.24.1](https://github.com/rails-on-services/apartment/tree/v0.24.1) (2014-02-21) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.24.0...v0.24.1) + +## [v0.24.0](https://github.com/rails-on-services/apartment/tree/v0.24.0) (2014-02-21) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.23.2...v0.24.0) + +## [v0.23.2](https://github.com/rails-on-services/apartment/tree/v0.23.2) (2014-01-09) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.23.1...v0.23.2) + +## [v0.23.1](https://github.com/rails-on-services/apartment/tree/v0.23.1) (2014-01-08) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.23.0...v0.23.1) + +## [v0.23.0](https://github.com/rails-on-services/apartment/tree/v0.23.0) (2013-12-15) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.22.1...v0.23.0) + +## [v0.22.1](https://github.com/rails-on-services/apartment/tree/v0.22.1) (2013-08-21) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.22.0...v0.22.1) + +## [v0.22.0](https://github.com/rails-on-services/apartment/tree/v0.22.0) (2013-07-09) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.21.1...v0.22.0) + +## [v0.21.1](https://github.com/rails-on-services/apartment/tree/v0.21.1) (2013-05-31) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.21.0...v0.21.1) + +## [v0.21.0](https://github.com/rails-on-services/apartment/tree/v0.21.0) (2013-04-25) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.20.0...v0.21.0) + +## [v0.20.0](https://github.com/rails-on-services/apartment/tree/v0.20.0) (2013-02-06) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/rm...v0.20.0) + +## [rm](https://github.com/rails-on-services/apartment/tree/rm) (2013-01-30) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.19.2...rm) + +## [v0.19.2](https://github.com/rails-on-services/apartment/tree/v0.19.2) (2013-01-30) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.19.0...v0.19.2) + +## [v0.19.0](https://github.com/rails-on-services/apartment/tree/v0.19.0) (2012-12-30) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.18.0...v0.19.0) + +## [v0.18.0](https://github.com/rails-on-services/apartment/tree/v0.18.0) (2012-11-28) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.17.3...v0.18.0) + +## [v0.17.3](https://github.com/rails-on-services/apartment/tree/v0.17.3) (2012-11-20) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.17.2...v0.17.3) + +## [v0.17.2](https://github.com/rails-on-services/apartment/tree/v0.17.2) (2012-11-15) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.17.1...v0.17.2) + +## [v0.17.1](https://github.com/rails-on-services/apartment/tree/v0.17.1) (2012-10-30) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.17.0...v0.17.1) + +## [v0.17.0](https://github.com/rails-on-services/apartment/tree/v0.17.0) (2012-09-26) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.16.0...v0.17.0) + +## [v0.16.0](https://github.com/rails-on-services/apartment/tree/v0.16.0) (2012-06-01) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.15.0...v0.16.0) + +## [v0.15.0](https://github.com/rails-on-services/apartment/tree/v0.15.0) (2012-03-18) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.14.4...v0.15.0) + +## [v0.14.4](https://github.com/rails-on-services/apartment/tree/v0.14.4) (2012-03-08) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.14.3...v0.14.4) + +## [v0.14.3](https://github.com/rails-on-services/apartment/tree/v0.14.3) (2012-02-21) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.14.2...v0.14.3) + +## [v0.14.2](https://github.com/rails-on-services/apartment/tree/v0.14.2) (2012-02-21) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.13.0.1...v0.14.2) + +## [v0.13.0.1](https://github.com/rails-on-services/apartment/tree/v0.13.0.1) (2012-02-09) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.14.1...v0.13.0.1) + +## [v0.14.1](https://github.com/rails-on-services/apartment/tree/v0.14.1) (2011-12-13) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.14.0...v0.14.1) + +## [v0.14.0](https://github.com/rails-on-services/apartment/tree/v0.14.0) (2011-12-13) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.13.1...v0.14.0) + +## [v0.13.1](https://github.com/rails-on-services/apartment/tree/v0.13.1) (2011-11-08) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.13.0...v0.13.1) + +## [v0.13.0](https://github.com/rails-on-services/apartment/tree/v0.13.0) (2011-10-25) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.12.0...v0.13.0) + +## [v0.12.0](https://github.com/rails-on-services/apartment/tree/v0.12.0) (2011-10-04) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.11.1...v0.12.0) + +## [v0.11.1](https://github.com/rails-on-services/apartment/tree/v0.11.1) (2011-09-22) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.11.0...v0.11.1) + +## [v0.11.0](https://github.com/rails-on-services/apartment/tree/v0.11.0) (2011-09-20) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.10.3...v0.11.0) + +## [v0.10.3](https://github.com/rails-on-services/apartment/tree/v0.10.3) (2011-09-20) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.10.2...v0.10.3) + +## [v0.10.2](https://github.com/rails-on-services/apartment/tree/v0.10.2) (2011-09-15) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.10.1...v0.10.2) + +## [v0.10.1](https://github.com/rails-on-services/apartment/tree/v0.10.1) (2011-08-11) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.10.0...v0.10.1) + +## [v0.10.0](https://github.com/rails-on-services/apartment/tree/v0.10.0) (2011-07-29) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.9.2...v0.10.0) + +## [v0.9.2](https://github.com/rails-on-services/apartment/tree/v0.9.2) (2011-07-04) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.9.1...v0.9.2) + +## [v0.9.1](https://github.com/rails-on-services/apartment/tree/v0.9.1) (2011-06-24) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.9.0...v0.9.1) + +## [v0.9.0](https://github.com/rails-on-services/apartment/tree/v0.9.0) (2011-06-23) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.8.0...v0.9.0) + +## [v0.8.0](https://github.com/rails-on-services/apartment/tree/v0.8.0) (2011-06-23) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.7.0...v0.8.0) + +## [v0.7.0](https://github.com/rails-on-services/apartment/tree/v0.7.0) (2011-06-22) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.6.0...v0.7.0) + +## [v0.6.0](https://github.com/rails-on-services/apartment/tree/v0.6.0) (2011-06-21) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/0.6.0...v0.6.0) + +## [0.6.0](https://github.com/rails-on-services/apartment/tree/0.6.0) (2011-06-21) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.5.1...0.6.0) + +## [v0.5.1](https://github.com/rails-on-services/apartment/tree/v0.5.1) (2011-06-21) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/0.5.0...v0.5.1) + +## [0.5.0](https://github.com/rails-on-services/apartment/tree/0.5.0) (2011-06-20) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.5.0...0.5.0) + +## [v0.5.0](https://github.com/rails-on-services/apartment/tree/v0.5.0) (2011-06-20) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/v0.1.3...v0.5.0) + +## [v0.1.3](https://github.com/rails-on-services/apartment/tree/v0.1.3) (2011-04-18) + +[Full Changelog](https://github.com/rails-on-services/apartment/compare/7100f34a185ab7d48947f06aa8c14f0cf0a68bb7...v0.1.3) + +# v2.7.1 + +**Implemented enhancements:** + +- N/a + +**Fixed bugs:** + +- [Resolves #82] Enhanced db:create breaks plugin compatibility - + +**Closed issues:** + +- Update rake version in development +- Renamed gemspec to match gem name + +# v2.7.0 + +**Implemented enhancements:** + +- [Resolves #70] Rake tasks define methods on main - +- Add database and schema to active record log. Configurable, defaults to false to keep current behavior - + +**Fixed bugs:** + +- [Fixes #61] Fix database create in mysql - + +**Closed issues:** + +- Remove deprecated tld_length config option: tld_length was removed in influitive#309, this configuration option doesn't have any effect now. - +- Using [diffend.io proxy](https://diffend.io) to safely check required gems +- Added [story branch](https://github.com/story-branch/story_branch) to the configuration +- Using travis-ci to run rubocop as well, replacing github actions: github actions do not work in fork's PRs + +# v2.6.1 + +**Implemented enhancements:** +- N/a + +**Fixed bugs:** +- [Resolves influitive#607] Avoid early connection + - + - + - +- [Resolves #52] Rake db:setup tries to seed non existent tenant - +- [Resolves #56] DB rollback uses second last migration - + +#**Closed issues:** +- N/a + +# v2.6.0 + +**Implemented enhancements:** +- [Resolves #26] Support configuration for skip checking of schema existence before switching +- [Resolves #41] Add tenant info to console boot + +**Fixed bugs:** +- [Resolves #37] Custom Console deprecation warning +- [Resolves #42] After switch callback not working with nil argument + +#**Closed issues:** +- Updated github actions configuration to run on PRs as well + +# v2.5.0 + +**Implemented enhancements:** +- [Resolves #6] Adds support for rails 6.1 +- [Resolves #27] Adds support to not rely on set search path, but instead prepends the schema name to the table name when using postgresql with schemas. +- [Resolves #35] Cache keys are now tenant dependent + +**Fixed bugs:** +- [Resolves #27] Manually switching connection between read and write forgets the schema + +#**Closed issues:** +- [Resolves #31] Add latest ruby versions to test matrix + +# v2.4.0 + +**Implemented enhancements:** +- [Resolves #14] Add console info about tenants and fast switches #17 +- Skip init if we're running webpacker:compile #18 + +**Fixed bugs:** +- Don't crash when no database connection is present #16 +- Rescuing ActiveRecord::NoDatabaseError when dropping tenants #19 + +#**Closed issues:** +- Rakefile should use mysql port from configuration #5 +- [Resolves #9] Cleanup rubocop todo #8 +- Cleanup travis matrix #23 + +# v2.3.0 + * January 3, 2020 + +**Implemented enhancements:** + - Basic support for rails 6 + - Released different gem name, with same API as apartment + +# v2.2.1 + * June 19, 2019 + +**Implemented enhancements:** + - #566: IGNORE_EMPTY_TENANTS environment variable to ignore empty tenants + warning. [Pysis868] + +**Fixed bugs:** + - #586: Ignore `CREATE SCHEMA public` statement in pg dump [artemave] + - #549: Fix Postgres schema creation with dump SQL [ancorcruz] + +# v2.2.0 + * April 14, 2018 + +**Implemented enhancements:** + - #523: Add Rails 5.2 support [IngusSkaistkalns] + - #504: Test against Ruby 2.5.0 [ahorek] + - #528: Test against Rails 5.2 [meganemura] + +**Removed:** + - #504: Remove Rails 4.0/4.1 support [ahorek] + - #545: Stop supporting for JRuby + Rails 5.0 [meganemura] + +**Fixed bugs:** + - #537: Fix PostgresqlSchemaFromSqlAdapter for newer PostgreSQL [shterrett] + - #532: Issue is reported by [aldrinmartoq] + - #519: Fix exception when main database doesn't exist [mayeco] + +**Closed issues:** + + - #514: Fix typo [menorval] + +# v2.1.0 + * December 15, 2017 + + - Add `parallel_migration_threads` configuration option for running migrations + in parallel [ryanbrunner] + - Drop Ruby 2.0.0 support [meganemura] + - ignore_private when parsing subdomains with PublicSuffix [michiomochi] + - Ignore row_security statements in psql dumps for backward compatibility + [meganemura] + - "Host" elevator [shrmnk] + - Enhance db:drop task to act on all tenants [kuzukuzu] + +# v2.0.0 + * July 26, 2017 + + - Raise FileNotFound rather than abort when loading files [meganemura] + - Add 5.1 support with fixes for deprecations [meganemura] + - Fix tests for 5.x and a host of dev-friendly improvements [meganemura] + - Keep query cache config after switching databases [fernandomm] + - Pass constants not strings to middleware stack (Rails 5) [tzabaman] + - Remove deprecations from 1.0.0 [caironoleto] + - Replace `tld_length` configuration option with PublicSuffix gem for the + subdomain elevator [humancopy] + - Pass full config to create_database to allow :encoding/:collation/etc + [kakipo] + - Don't retain a connection during initialization [mikecmpbll] + - Fix database name escaping in drop_command [mikecmpbll] + - Skip initialization for assets:clean and assets:precompile tasks + [frank-west-iii] + +# v1.2.0 + * July 28, 2016 + + - Official Rails 5 support + +# v1.1.0 + * May 26, 2016 + + - Reset tenant after each request + - [Support callbacks](https://github.com/influitive/apartment/commit/ff9c9d092a781026502f5997c0bbedcb5748bc83) on switch [cbeer] + - Preliminary support for [separate database hosts](https://github.com/influitive/apartment/commit/abdffbf8cd9fba87243f16c86390da13e318ee1f) [apneadiving] + +# v1.0.2 + * July 2, 2015 + + - Fix pg_dump env vars - pull/208 [MitinPavel] + - Allow custom seed data file - pull/234 [typeoneerror] + +# v1.0.1 + * April 28, 2015 + + - Fix `Apartment::Deprecation` which was rescuing all exceptions + +# v1.0.0 + * Feb 3, 2015 + + - [BREAKING CHANGE] `Apartment::Tenant.process` is deprecated in favour of `Apartment::Tenant.switch` + - [BREAKING CHANGE] `Apartment::Tenant.switch` without a block is deprecated in favour of `Apartment::Tenant.switch!` + - Raise proper `TenantNotFound`, `TenantExists` exceptions + - Deprecate old `SchemaNotFound`, `DatabaseNotFound` exceptions + +# v0.26.1 + * Jan 13, 2015 + + - Fixed [schema quoting bug](https://github.com/influitive/apartment/issues/198#issuecomment-69782651) [jonsgreen] + +# v0.26.0 + * Jan 5, 2015 + + - Rails 4.2 support + +# v0.25.2 + * Sept 8, 2014 + + - Heroku fix on `assets:precompile` - pull/169 [rabbitt] + +# v0.25.1 + * July 17, 2014 + + - Fixed a few vestiges of Apartment::Database + +# v0.25.0 + * July 3, 2014 + + - [BREAKING CHANGE] - `Apartment::Database` is not deprecated in favour of + `Apartment::Tenant` + - ActiveRecord (and Rails) 4.1 now supported + - A new sql based adapter that dumps the schema using sql + +# v0.24.3 + * March 5, 2014 + + - Rake enhancements weren't removed from the generator template + +# v0.24.2 + * February 24, 2014 + + - Better warnings if `apartment:migrate` is run + +# v0.24.1 + * February 21, 2014 + + - requiring `apartment/tasks/enhancements` in an initializer doesn't work + - One can disable tenant migrations using `Apartment.db_migrate_tenants = false` in the Rakefile + +# v0.24 + * February 21, 2014 (In honour of the Women's Gold Medal in Hockey at Sochi) + + - [BREAKING CHANGE] `apartment:migrate` task no longer depends on `db:migrate` + - Instead, you can `require 'apartment/tasks/enhancements'` in your Apartment initializer + - This will enhance `rake db:migrate` to also run `apartment:migrate` + - You can now forget about ever running `apartment:migrate` again + - Numerous deprecations for things referencing the word 'database' + - This is an ongoing effort to completely replace 'database' with 'tenant' as a better abstraction + - Note the obvious `Apartment::Database` still exists but will hopefully become `Apartment::Tenant` soon + +# v0.23.2 + * January 9, 2014 + + - Increased visibility of #parse_database_name warning + +# v0.23.1 + * January 8, 2014 + + - Schema adapters now initialize with default and persistent schemas + - Deprecated Apartment::Elevators#parse_database_name + +# v0.23.0 + * August 21, 2013 + + - Subdomain Elevator now allows for exclusions + - Delayed::Job has been completely removed + +# v0.22.1 + * August 21, 2013 + + - Fix bug where if your ruby process importing the database schema is run + from a directory other than the app root, Apartment wouldn't know what + schema_migrations to insert into the database (Rails only) + +# v0.22.0 + * June 9, 2013 + + - Numerous bug fixes: + - Mysql reset could connect to wrong database [eric88] + - Postgresql schema names weren't quoted properly [gdott9] + - Fixed error message on SchemaNotFound in `process` + - HostHash elevator allows mapping host based on hash contents [gdott9] + - Official Sidekiq support with the [apartment-sidekiq gem](https://github.com/influitive/apartment-sidekiq) + + +# v0.21.1 + * May 31, 2013 + + - Clearing the AR::QueryCache after switching databases. + - Fixes issue with stale model being loaded for schema adapters + +# v0.21.0 + * April 24, 2013 + + - JDBC support!! [PetrolMan] + +# v0.20.0 + * Feb 6, 2013 + + - Mysql now has a 'schema like' option to perform like Postgresql (default) + - This should be significantly more performant than using connections + - Psych is now supported for Delayed::Job yaml parsing + +# v0.19.2 + * Jan 30, 2013 + + - Database schema file can now be set manually or skipped altogether + +# v0.19.1 + * Jan 30, 2013 + + - Allow schema.rb import file to be specified in config or skip schema.rb import altogether + +# v0.19.0 + * Dec 29, 2012 + + - Apartment is now threadsafe + - New postgis adapter [zonpantli] + - Removed ActionDispatch dependency for use with Rack apps (regression) + +# v0.18.0 + * Nov 27, 2012 + + - Added `append_environment` config option [virtualstaticvoid] + - Cleaned up the readme and generator documentation + - Added `connection_class` config option [smashtank] + - Fixed a [bug](https://github.com/influitive/apartment/issues/17#issuecomment-10758327) in pg adapter when missing schema + +# v0.17.1 + * Oct 30, 2012 + + - Fixed a bug where switching to an unknown db in mysql2 would crash the app [Frodotus] + +# v0.17.0 + * Sept 26, 2012 + + - Apartment has [a new home!](https://github.com/influitive/apartment) + - Support Sidekiq hooks to switch dbs [maedhr] + - Allow VERSION to be used on apartment:migrate [Bhavin Kamani] + +# v0.16.0 + * June 1, 2012 + + - Apartment now supports a default_schema to be set, rather than relying on ActiveRecord's default schema_search_path + - Additional schemas can always be maintained in the schema_search_path by configuring persistent_schemas [ryanbrunner] + - This means Hstore is officially supported!! + - There is now a full domain based elevator to switch dbs based on the whole domain [lcowell] + - There is now a generic elevator that takes a Proc to switch dbs based on the return value of that proc. + +# v0.15.0 + * March 18, 2012 + + - Remove Rails dependency, Apartment can now be used with any Rack based framework using ActiveRecord + +# v0.14.4 + * March 8, 2012 + + - Delayed::Job Hooks now return to the previous database, rather than resetting + +# v0.14.3 + * Feb 21, 2012 + + - Fix yaml serialization of non DJ models + +# v0.14.2 + * Feb 21, 2012 + + - Fix Delayed::Job yaml encoding with Rails > 3.0.x + +# v0.14.1 + * Dec 13, 2011 + + - Fix ActionDispatch::Callbacks deprecation warnings + +# v0.14.0 + * Dec 13, 2011 + + - Rails 3.1 Support + +# v0.13.1 + * Nov 8, 2011 + + - Reset prepared statement cache for rails 3.1.1 before switching dbs when using postgresql schemas + - Only necessary until the next release which will be more schema aware + +# v0.13.0 + * Oct 25, 2011 + + - `process` will now rescue with reset if the previous schema/db is no longer available + - `create` now takes an optional block which allows you to process within the newly created db + - 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 + +# v0.12.0 + * Oct 4, 2011 + + - Added a `drop` method for removing databases/schemas + - Refactored abstract adapter to further remove duplication in concrete implementations + - Excluded models now take string references so they are properly reloaded in development + - Better silencing of `schema.rb` loading using `verbose` flag + +# v0.11.1 + * Sep 22, 2011 + + - Better use of Railties for initializing apartment + - The following changes were necessary as I haven't figured out how to properly hook into Rails reloading + - Added reloader middleware in development to init Apartment on each request + - Override `reload!` in console to also init Apartment + +# v0.11.0 + * Sep 20, 2011 + + - Excluded models no longer use a different connection when using postgresql schemas. Instead their table_name is prefixed with `public.` + +# v0.10.3 + * Sep 20, 2011 + + - Fix improper raising of exceptions on create and reset + +# v0.10.2 + * Sep 15, 2011 + + - Remove all the annoying logging for loading db schema and seeding on create + +# v0.10.1 + * Aug 11, 2011 + + - Fixed bug in DJ where new objects (that hadn't been pulled from the db) didn't have the proper database assigned + +# v0.10.0 + * July 29, 2011 + + - Added better support for Delayed Job + - New config option that enables Delayed Job wrappers + - 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 + +# v0.9.2 + * July 4, 2011 + + - Migrations now run associated rails migration fully, fixes schema.rb not being reloaded after migrations + +# v0.9.1 + * June 24, 2011 + + - Hooks now take the payload object as an argument to fetch the proper db for DJ hooks + +# v0.9.0 + * June 23, 2011 + + - Added module to provide delayed job hooks + +# v0.8.0 + * June 23, 2011 + + - Added #current_database which will return the current database (or schema) name + +# v0.7.0 + * June 22, 2011 + + - Added apartment:seed rake task for seeding all dbs + +# v0.6.0 + * June 21, 2011 + + - Added #process to connect to new db, perform operations, then ensure a reset + +# v0.5.1 + * June 21, 2011 + + - Fixed db migrate up/down/rollback + - added db:redo + +# v0.5.0 + * June 20, 2011 + + - Added the concept of an "Elevator", a rack based strategy for db switching + - Added the Subdomain Elevator middleware to enabled db switching based on subdomain + +# v0.4.0 + * June 14, 2011 + + - Added `configure` method on Apartment instead of using yml file, allows for dynamic setting of db names to migrate for rake task + - Added `seed_after_create` config option to import seed data to new db on create + +# v0.3.0 + * June 10, 2011 + + - Added full support for database migration + - Added in method to establish new connection for excluded models on startup rather than on each switch + +# v0.2.0 + * June 6, 2011 * + + - Refactor to use more rails/active_support functionality + - Refactor config to lazily load apartment.yml if exists + - Remove OStruct and just use hashes for fetching methods + - Added schema load on create instead of migrating from scratch + +# v0.1.3 + * March 30, 2011 * + + - Original pass from Ryan + + +\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* diff --git a/Gemfile b/Gemfile index 6c13b34a..0949319d 100644 --- a/Gemfile +++ b/Gemfile @@ -1,10 +1,13 @@ +# frozen_string_literal: true + source 'http://rubygems.org' gemspec -gem 'rails', '>= 3.1.2' +gem 'rails', '>= 3.1.2' +gem 'rubocop' group :local do - gem 'pry' gem 'guard-rspec', '~> 4.2' + gem 'pry' end diff --git a/Guardfile b/Guardfile index 5d3fa72d..335bab54 100644 --- a/Guardfile +++ b/Guardfile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # A sample Guardfile # More info at https://github.com/guard/guard#readme @@ -5,20 +7,5 @@ guard :rspec do watch(%r{^spec/.+_spec\.rb$}) watch(%r{^lib/apartment/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" } watch(%r{^lib/apartment/(.+)\.rb$}) { |m| "spec/integration/#{m[1]}_spec.rb" } - watch('spec/spec_helper.rb') { "spec" } - - # # Rails example - # watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } - # watch(%r{^app/(.*)(\.erb|\.haml|\.slim)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" } - # 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"] } - # watch(%r{^spec/support/(.+)\.rb$}) { "spec" } - # watch('config/routes.rb') { "spec/routing" } - # watch('app/controllers/application_controller.rb') { "spec/controllers" } - - # # Capybara features specs - # watch(%r{^app/views/(.+)/.*\.(erb|haml|slim)$}) { |m| "spec/features/#{m[1]}_spec.rb" } - - # # Turnip features and steps - # watch(%r{^spec/acceptance/(.+)\.feature$}) - # watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' } + watch('spec/spec_helper.rb') { 'spec' } end diff --git a/HISTORY.md b/HISTORY.md index c6e1398f..0167efa5 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,35 +1,133 @@ -# 2.2.1 +# v2.7.1 + +**Implemented enhancements:** + +- N/a + +**Fixed bugs:** + +- [Resolves #82] Enhanced db:create breaks plugin compatibility - + +**Closed issues:** + +- Update rake version in development +- Renamed gemspec to match gem name + +# v2.7.0 + +**Implemented enhancements:** + +- [Resolves #70] Rake tasks define methods on main - +- Add database and schema to active record log. Configurable, defaults to false to keep current behavior - + +**Fixed bugs:** + +- [Fixes #61] Fix database create in mysql - + +**Closed issues:** + +- Remove deprecated tld_length config option: tld_length was removed in influitive#309, this configuration option doesn't have any effect now. - +- Using [diffend.io proxy](https://diffend.io) to safely check required gems +- Added [story branch](https://github.com/story-branch/story_branch) to the configuration +- Using travis-ci to run rubocop as well, replacing github actions: github actions do not work in fork's PRs + +# v2.6.1 + +**Implemented enhancements:** +- N/a + +**Fixed bugs:** +- [Resolves influitive#607] Avoid early connection + - + - + - +- [Resolves #52] Rake db:setup tries to seed non existent tenant - +- [Resolves #56] DB rollback uses second last migration - + +#**Closed issues:** +- N/a + +# v2.6.0 + +**Implemented enhancements:** +- [Resolves #26] Support configuration for skip checking of schema existence before switching +- [Resolves #41] Add tenant info to console boot + +**Fixed bugs:** +- [Resolves #37] Custom Console deprecation warning +- [Resolves #42] After switch callback not working with nil argument + +#**Closed issues:** +- Updated github actions configuration to run on PRs as well + +# v2.5.0 + +**Implemented enhancements:** +- [Resolves #6] Adds support for rails 6.1 +- [Resolves #27] Adds support to not rely on set search path, but instead prepends the schema name to the table name when using postgresql with schemas. +- [Resolves #35] Cache keys are now tenant dependent + +**Fixed bugs:** +- [Resolves #27] Manually switching connection between read and write forgets the schema + +#**Closed issues:** +- [Resolves #31] Add latest ruby versions to test matrix + +# v2.4.0 + +**Implemented enhancements:** +- [Resolves #14] Add console info about tenants and fast switches #17 +- Skip init if we're running webpacker:compile #18 + +**Fixed bugs:** +- Don't crash when no database connection is present #16 +- Rescuing ActiveRecord::NoDatabaseError when dropping tenants #19 + +#**Closed issues:** +- Rakefile should use mysql port from configuration #5 +- [Resolves #9] Cleanup rubocop todo #8 +- Cleanup travis matrix #23 + +# v2.3.0 + * January 3, 2020 + +**Implemented enhancements:** + - Basic support for rails 6 + - Released different gem name, with same API as apartment + +# v2.2.1 * June 19, 2019 -## Added +**Implemented enhancements:** - #566: IGNORE_EMPTY_TENANTS environment variable to ignore empty tenants warning. [Pysis868] -## Fixed +**Fixed bugs:** - #586: Ignore `CREATE SCHEMA public` statement in pg dump [artemave] - #549: Fix Postgres schema creation with dump SQL [ancorcruz] -# 2.2.0 +# v2.2.0 * April 14, 2018 -## Added +**Implemented enhancements:** - #523: Add Rails 5.2 support [IngusSkaistkalns] - #504: Test against Ruby 2.5.0 [ahorek] - #528: Test against Rails 5.2 [meganemura] -## Removed +**Removed:** - #504: Remove Rails 4.0/4.1 support [ahorek] - #545: Stop supporting for JRuby + Rails 5.0 [meganemura] -## Fixed +**Fixed bugs:** - #537: Fix PostgresqlSchemaFromSqlAdapter for newer PostgreSQL [shterrett] - #532: Issue is reported by [aldrinmartoq] - #519: Fix exception when main database doesn't exist [mayeco] -## Changed +**Closed issues:** + - #514: Fix typo [menorval] -# 2.1.0 +# v2.1.0 * December 15, 2017 - Add `parallel_migration_threads` configuration option for running migrations @@ -41,7 +139,7 @@ - "Host" elevator [shrmnk] - Enhance db:drop task to act on all tenants [kuzukuzu] -# 2.0.0 +# v2.0.0 * July 26, 2017 - Raise FileNotFound rather than abort when loading files [meganemura] @@ -59,30 +157,30 @@ - Skip initialization for assets:clean and assets:precompile tasks [frank-west-iii] -# 1.2.0 +# v1.2.0 * July 28, 2016 - Official Rails 5 support -# 1.1.0 +# v1.1.0 * May 26, 2016 - Reset tenant after each request - [Support callbacks](https://github.com/influitive/apartment/commit/ff9c9d092a781026502f5997c0bbedcb5748bc83) on switch [cbeer] - Preliminary support for [separate database hosts](https://github.com/influitive/apartment/commit/abdffbf8cd9fba87243f16c86390da13e318ee1f) [apneadiving] -# 1.0.2 +# v1.0.2 * July 2, 2015 - Fix pg_dump env vars - pull/208 [MitinPavel] - Allow custom seed data file - pull/234 [typeoneerror] -# 1.0.1 +# v1.0.1 * April 28, 2015 - Fix `Apartment::Deprecation` which was rescuing all exceptions -# 1.0.0 +# v1.0.0 * Feb 3, 2015 - [BREAKING CHANGE] `Apartment::Tenant.process` is deprecated in favour of `Apartment::Tenant.switch` @@ -90,27 +188,27 @@ - Raise proper `TenantNotFound`, `TenantExists` exceptions - Deprecate old `SchemaNotFound`, `DatabaseNotFound` exceptions -# 0.26.1 +# v0.26.1 * Jan 13, 2015 - Fixed [schema quoting bug](https://github.com/influitive/apartment/issues/198#issuecomment-69782651) [jonsgreen] -# 0.26.0 +# v0.26.0 * Jan 5, 2015 - Rails 4.2 support -# 0.25.2 +# v0.25.2 * Sept 8, 2014 - Heroku fix on `assets:precompile` - pull/169 [rabbitt] -# 0.25.1 +# v0.25.1 * July 17, 2014 - Fixed a few vestiges of Apartment::Database -# 0.25.0 +# v0.25.0 * July 3, 2014 - [BREAKING CHANGE] - `Apartment::Database` is not deprecated in favour of @@ -118,23 +216,23 @@ - ActiveRecord (and Rails) 4.1 now supported - A new sql based adapter that dumps the schema using sql -# 0.24.3 +# v0.24.3 * March 5, 2014 - Rake enhancements weren't removed from the generator template -# 0.24.2 +# v0.24.2 * February 24, 2014 - Better warnings if `apartment:migrate` is run -# 0.24.1 +# v0.24.1 * February 21, 2014 - requiring `apartment/tasks/enhancements` in an initializer doesn't work - One can disable tenant migrations using `Apartment.db_migrate_tenants = false` in the Rakefile -# 0.24 +# v0.24 * February 21, 2014 (In honour of the Women's Gold Medal in Hockey at Sochi) - [BREAKING CHANGE] `apartment:migrate` task no longer depends on `db:migrate` @@ -145,31 +243,31 @@ - This is an ongoing effort to completely replace 'database' with 'tenant' as a better abstraction - Note the obvious `Apartment::Database` still exists but will hopefully become `Apartment::Tenant` soon -# 0.23.2 +# v0.23.2 * January 9, 2014 - Increased visibility of #parse_database_name warning -# 0.23.1 +# v0.23.1 * January 8, 2014 - Schema adapters now initialize with default and persistent schemas - Deprecated Apartment::Elevators#parse_database_name -# 0.23.0 +# v0.23.0 * August 21, 2013 - Subdomain Elevator now allows for exclusions - Delayed::Job has been completely removed -# 0.22.1 +# v0.22.1 * August 21, 2013 - Fix bug where if your ruby process importing the database schema is run from a directory other than the app root, Apartment wouldn't know what schema_migrations to insert into the database (Rails only) -# 0.22.0 +# v0.22.0 * June 9, 2013 - Numerous bug fixes: @@ -180,42 +278,42 @@ - Official Sidekiq support with the [apartment-sidekiq gem](https://github.com/influitive/apartment-sidekiq) -# 0.21.1 +# v0.21.1 * May 31, 2013 - Clearing the AR::QueryCache after switching databases. - Fixes issue with stale model being loaded for schema adapters -# 0.21.0 +# v0.21.0 * April 24, 2013 - JDBC support!! [PetrolMan] -# 0.20.0 +# v0.20.0 * Feb 6, 2013 - Mysql now has a 'schema like' option to perform like Postgresql (default) - This should be significantly more performant than using connections - Psych is now supported for Delayed::Job yaml parsing -# 0.19.2 +# v0.19.2 * Jan 30, 2013 - Database schema file can now be set manually or skipped altogether -# 0.19.1 +# v0.19.1 * Jan 30, 2013 - Allow schema.rb import file to be specified in config or skip schema.rb import altogether -# 0.19.0 +# v0.19.0 * Dec 29, 2012 - Apartment is now threadsafe - New postgis adapter [zonpantli] - Removed ActionDispatch dependency for use with Rack apps (regression) -# 0.18.0 +# v0.18.0 * Nov 27, 2012 - Added `append_environment` config option [virtualstaticvoid] @@ -223,19 +321,19 @@ - Added `connection_class` config option [smashtank] - Fixed a [bug](https://github.com/influitive/apartment/issues/17#issuecomment-10758327) in pg adapter when missing schema -# 0.17.1 +# v0.17.1 * Oct 30, 2012 - Fixed a bug where switching to an unknown db in mysql2 would crash the app [Frodotus] -# 0.17.0 +# v0.17.0 * Sept 26, 2012 - Apartment has [a new home!](https://github.com/influitive/apartment) - Support Sidekiq hooks to switch dbs [maedhr] - Allow VERSION to be used on apartment:migrate [Bhavin Kamani] -# 0.16.0 +# v0.16.0 * June 1, 2012 - Apartment now supports a default_schema to be set, rather than relying on ActiveRecord's default schema_search_path @@ -244,50 +342,50 @@ - There is now a full domain based elevator to switch dbs based on the whole domain [lcowell] - There is now a generic elevator that takes a Proc to switch dbs based on the return value of that proc. -# 0.15.0 +# v0.15.0 * March 18, 2012 - Remove Rails dependency, Apartment can now be used with any Rack based framework using ActiveRecord -# 0.14.4 +# v0.14.4 * March 8, 2012 - Delayed::Job Hooks now return to the previous database, rather than resetting -# 0.14.3 +# v0.14.3 * Feb 21, 2012 - Fix yaml serialization of non DJ models -# 0.14.2 +# v0.14.2 * Feb 21, 2012 - Fix Delayed::Job yaml encoding with Rails > 3.0.x -# 0.14.1 +# v0.14.1 * Dec 13, 2011 - Fix ActionDispatch::Callbacks deprecation warnings -# 0.14.0 +# v0.14.0 * Dec 13, 2011 - Rails 3.1 Support -# 0.13.1 +# v0.13.1 * Nov 8, 2011 - Reset prepared statement cache for rails 3.1.1 before switching dbs when using postgresql schemas - Only necessary until the next release which will be more schema aware -# 0.13.0 +# v0.13.0 * Oct 25, 2011 - `process` will now rescue with reset if the previous schema/db is no longer available - `create` now takes an optional block which allows you to process within the newly created db - 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 -# 0.12.0 +# v0.12.0 * Oct 4, 2011 - Added a `drop` method for removing databases/schemas @@ -295,7 +393,7 @@ - Excluded models now take string references so they are properly reloaded in development - Better silencing of `schema.rb` loading using `verbose` flag -# 0.11.1 +# v0.11.1 * Sep 22, 2011 - Better use of Railties for initializing apartment @@ -303,88 +401,88 @@ - Added reloader middleware in development to init Apartment on each request - Override `reload!` in console to also init Apartment -# 0.11.0 +# v0.11.0 * Sep 20, 2011 - Excluded models no longer use a different connection when using postgresql schemas. Instead their table_name is prefixed with `public.` -# 0.10.3 +# v0.10.3 * Sep 20, 2011 - Fix improper raising of exceptions on create and reset -# 0.10.2 +# v0.10.2 * Sep 15, 2011 - Remove all the annoying logging for loading db schema and seeding on create -# 0.10.1 +# v0.10.1 * Aug 11, 2011 - Fixed bug in DJ where new objects (that hadn't been pulled from the db) didn't have the proper database assigned -# 0.10.0 +# v0.10.0 * July 29, 2011 - Added better support for Delayed Job - New config option that enables Delayed Job wrappers - 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 -# 0.9.2 +# v0.9.2 * July 4, 2011 - Migrations now run associated rails migration fully, fixes schema.rb not being reloaded after migrations -# 0.9.1 +# v0.9.1 * June 24, 2011 - Hooks now take the payload object as an argument to fetch the proper db for DJ hooks -# 0.9.0 +# v0.9.0 * June 23, 2011 - Added module to provide delayed job hooks -# 0.8.0 +# v0.8.0 * June 23, 2011 - Added #current_database which will return the current database (or schema) name -# 0.7.0 +# v0.7.0 * June 22, 2011 - Added apartment:seed rake task for seeding all dbs -# 0.6.0 +# v0.6.0 * June 21, 2011 - Added #process to connect to new db, perform operations, then ensure a reset -# 0.5.1 +# v0.5.1 * June 21, 2011 - Fixed db migrate up/down/rollback - added db:redo -# 0.5.0 +# v0.5.0 * June 20, 2011 - Added the concept of an "Elevator", a rack based strategy for db switching - Added the Subdomain Elevator middleware to enabled db switching based on subdomain -# 0.4.0 +# v0.4.0 * June 14, 2011 - Added `configure` method on Apartment instead of using yml file, allows for dynamic setting of db names to migrate for rake task - Added `seed_after_create` config option to import seed data to new db on create -# 0.3.0 +# v0.3.0 * June 10, 2011 - Added full support for database migration - Added in method to establish new connection for excluded models on startup rather than on each switch -# 0.2.0 +# v0.2.0 * June 6, 2011 * - Refactor to use more rails/active_support functionality @@ -392,7 +490,7 @@ - Remove OStruct and just use hashes for fetching methods - Added schema load on create instead of migrating from scratch -# 0.1.3 +# v0.1.3 * March 30, 2011 * - Original pass from Ryan diff --git a/README.md b/README.md index 6713baaa..5e58ffb0 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # Apartment -[![Gem Version](https://badge.fury.io/rb/apartment.svg)](https://badge.fury.io/rb/apartment) -[![Code Climate](https://codeclimate.com/github/influitive/apartment/badges/gpa.svg)](https://codeclimate.com/github/influitive/apartment) -[![Build Status](https://travis-ci.org/influitive/apartment.svg?branch=development)](https://travis-ci.org/influitive/apartment) +[![Gem Version](https://badge.fury.io/rb/ros-apartment.svg)](https://badge.fury.io/rb/apartment) +[![Code Climate](https://api.codeclimate.com/v1/badges/b0dc327380bb8438f991/maintainability)](https://codeclimate.com/github/rails-on-services/apartment/maintainability) +[![Build Status](https://travis-ci.org/rails-on-services/apartment.svg?branch=development)](https://travis-ci.org/rails-on-services/apartment) *Multitenancy for Rails and ActiveRecord* @@ -10,26 +10,21 @@ Apartment provides tools to help you deal with multiple tenants in your Rails application. If you need to have certain data sequestered based on account or company, but still allow some data to exist in a common tenant, Apartment can help. -## HELP! +## Apartment drop in replacement gem -In order to help drive the direction of development and clean up the codebase, we'd like to take a poll -on how people are currently using Apartment. If you can take 5 seconds (1 question) to answer -this poll, we'd greatly appreciated it. +After having reached out via github issues and email directly, no replies ever +came. Since we wanted to upgrade our application to Rails 6 we decided to fork +and start some development to support Rails 6. Because we don't have access +to the apartment gem itself, the solution was to release it under a different +name but providing the exact same API as it was before. -[View Poll](http://www.poll-maker.com/poll391552x4Bfb41a9-15) +## Help wanted -## Excessive Memory Issues on ActiveRecord 4.x - -> If you're noticing ever growing memory issues (ie growing with each tenant you add) -> when using Apartment, that's because there's [an issue](https://github.com/rails/rails/issues/19578) -> with how ActiveRecord maps Postgresql data types into AR data types. -> This has been patched and will be released for AR 4.2.2. It's apparently hard -> to backport to 4.1 unfortunately. -> If you're noticing high memory usage from ActiveRecord with Apartment please upgrade. - -```ruby -gem 'rails', '4.2.1', github: 'influitive/rails', tag: 'v4.2.1.memfix' -``` +We were never involved with the development of Apartment gem in the first place +and this project started out of our own needs. We will be more than happy +to collaborate to maintain the gem alive and supporting the latest versions +of ruby and rails, but your help is appreciated. Either by reporting bugs you +may find or proposing improvements to the gem itself. Feel free to reach out. ## Installation @@ -38,7 +33,7 @@ gem 'rails', '4.2.1', github: 'influitive/rails', tag: 'v4.2.1.memfix' Add the following to your Gemfile: ```ruby -gem 'apartment' +gem 'ros-apartment', require: 'apartment' ``` Then generate your `Apartment` config file using @@ -89,6 +84,14 @@ would not allow a full new database to be created. One can optionally use the full database creation instead if they want, though this is not recommended +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: + +```ruby +Apartment::Tenant.switch(['tenant_1', 'tenant_2']) do + # ... +end +``` + ### Switching Tenants To switch tenants using Apartment, use the following command: @@ -234,7 +237,7 @@ A Generic Elevator exists that allows you to pass a `Proc` (or anything that res module MyApplication class Application < Rails::Application # Obviously not a contrived example - config.middleware.use Apartment::Elevators::Generic, Proc.new { |request| request.host.reverse } + config.middleware.use Apartment::Elevators::Generic, proc { |request| request.host.reverse } end end ``` @@ -294,6 +297,27 @@ Apartment::Tenant.drop('tenant_name') When method is called, the schema is dropped and all data from itself will be lost. Be careful with this method. +### Custom Prompt + +#### Console methods + +`ros-apartment` console configures two helper methods: +1. `tenant_list` - list available tenants while using the console +2. `st(tenant_name:String)` - Switches the context to the tenant name passed, if +it exists. + +#### Custom printed prompt + +`ros-apartment` also has a custom prompt that gives a bit more information about +the context in which you're running. It shows the environment as well as the tenant +that is currently switched to. In order for you to enable this, you need to require +the custom console in your application. + +In `application.rb` add `require 'apartment/custom_console'`. +Please note that we rely on `pry-rails` to edit the prompt, thus your project needs +to install it as well. In order to do so, you need to add `gem 'pry-rails'` to your +project's gemfile. + ## Config The following config options should be set up in a Rails initializer such as: @@ -308,6 +332,37 @@ Apartment.configure do |config| end ``` +### Skip tenant schema check + +This is configurable by setting: `tenant_presence_check`. It defaults to true +in order to maintain the original gem behavior. This is only checked when using one of the PostgreSQL adapters. +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. + +Setting this configuration value to `false` will disable the schema presence check before trying to switch the context. + +```ruby +Apartment.configure do |config| + tenant_presence_check = false +end +``` + +### Additional logging information + +Enabling this configuration will output the database that the process is currently connected to as well as which +schemas are in the search path. This can be enabled by setting to true the `active_record_log` configuration. + +Please note that our custom logger inherits from `ActiveRecord::LogSubscriber` so this will be required for the configuration to work. + +**Example log output:** + + + +```ruby +Apartment.configure do |config| + config.active_record_log = true +end +``` + ### Excluding models 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: @@ -325,17 +380,30 @@ Rails will always access the 'public' tenant when accessing these models, but no ### Postgresql Schemas -## Providing a Different default_schema +#### Alternative: Creating new schemas by using raw SQL dumps + +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. + +This only applies while using postgres adapter and `config.use_schemas` is set to `true`. +(Note: this option doesn't use `db/structure.sql`, it creates SQL dump by executing `pg_dump`) + +Enable this option with: + +```ruby +config.use_sql = true +``` + +### Providing a Different default_tenant 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: ```ruby -config.default_schema = "some_other_schema" +config.default_tenant = "some_other_schema" ``` With that set, all excluded models will use this schema as the table name prefix instead of `public` and `reset` on `Apartment::Tenant` will return to this schema as well. -## Persistent Schemas +### Persistent Schemas Apartment will normally just switch the `schema_search_path` whole hog to the one passed in. This can lead to problems if you want other schemas to always be searched as well. Enter `persistent_schemas`. You can configure a list of other schemas that will always remain in the search path, while the default gets swapped out: @@ -403,7 +471,7 @@ schema_search_path: "public,shared_extensions" ... ``` -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: +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: 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) 2. Run `heroku pg:psql` from your command line @@ -441,18 +509,6 @@ schema in the `search_path` at all times. We won't be able to do this though unt also contain the tenanted tables, which is an open issue with no real milestone to be completed. Happy to accept PR's on the matter. -#### Alternative: Creating new schemas by using raw SQL dumps - -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. - -This only applies while using postgres adapter and `config.use_schemas` is set to `true`. -(Note: this option doesn't use `db/structure.sql`, it creates SQL dump by executing `pg_dump`) - -Enable this option with: -```ruby -config.use_sql = true -``` - ### Managing Migrations In order to migrate all of your tenants (or postgresql schemas) you need to provide a list @@ -484,7 +540,7 @@ Note that you can disable the default migrating of all tenants with `db:migrate` Apartment supports parallelizing migrations into multiple threads when you have a large number of tenants. By default, parallel migrations is -turned off. You can enable this by setting `parallel_migration_threads` to +turned off. You can enable this by setting `parallel_migration_threads` to the number of threads you want to use in your initializer. Keep in mind that because migrations are going to access the database, @@ -531,7 +587,12 @@ end ## Background workers -See [apartment-sidekiq](https://github.com/influitive/apartment-sidekiq) or [apartment-activejob](https://github.com/influitive/apartment-activejob). +Both these gems have been forked as a side consequence of having a new gem name. +You can use them exactly as you were using before. They are, just like this one +a drop-in replacement. + +See [apartment-sidekiq](https://github.com/rails-on-services/apartment-sidekiq) +or [apartment-activejob](https://github.com/rails-on-services/apartment-activejob). ## Callbacks @@ -558,6 +619,16 @@ module Apartment end ``` +## Running rails console without a connection to the database + +By default, once apartment starts, it establishes a connection to the database. It is possible to +disable this initial connection, by running with `APARTMENT_DISABLE_INIT` set to something: + +```shell +$ APARTMENT_DISABLE_INIT=true DATABASE_URL=postgresql://localhost:1234/buk_development bin/rails runner 'puts 1' +# 1 +``` + ## Contributing * In both `spec/dummy/config` and `spec/config`, you will see `database.yml.sample` files @@ -569,6 +640,13 @@ end * If you're looking to help, check out the TODO file for some upcoming changes I'd like to implement in Apartment. +### Running bundle install + +mysql2 gem in some cases fails to install. +If you face problems running bundle install in OSX, try installing the gem running: + +`gem install mysql2 -v '0.5.3' -- --with-ldflags=-L/usr/local/opt/openssl/lib --with-cppflags=-I/usr/local/opt/openssl/include` + ## License Apartment is released under the [MIT License](http://www.opensource.org/licenses/MIT). diff --git a/Rakefile b/Rakefile index df67edc6..1769f7d0 100644 --- a/Rakefile +++ b/Rakefile @@ -1,19 +1,25 @@ -require 'bundler' rescue 'You must `gem install bundler` and `bundle install` to run rake tasks' +# frozen_string_literal: true + +begin + require 'bundler' +rescue StandardError + 'You must `gem install bundler` and `bundle install` to run rake tasks' +end Bundler.setup Bundler::GemHelper.install_tasks require 'appraisal' -require "rspec" -require "rspec/core/rake_task" +require 'rspec' +require 'rspec/core/rake_task' -RSpec::Core::RakeTask.new(:spec => %w{ db:copy_credentials db:test:prepare }) do |spec| - spec.pattern = "spec/**/*_spec.rb" +RSpec::Core::RakeTask.new(spec: %w[db:copy_credentials db:test:prepare]) do |spec| + spec.pattern = 'spec/**/*_spec.rb' # spec.rspec_opts = '--order rand:47078' end namespace :spec do - [:tasks, :unit, :adapters, :integration].each do |type| + %i[tasks unit adapters integration].each do |type| RSpec::Core::RakeTask.new(type => :spec) do |spec| spec.pattern = "spec/#{type}/**/*_spec.rb" end @@ -27,11 +33,11 @@ task :console do Pry.start end -task :default => :spec +task default: :spec namespace :db do namespace :test do - task :prepare => %w{postgres:drop_db postgres:build_db mysql:drop_db mysql:build_db} + task prepare: %w[postgres:drop_db postgres:build_db mysql:drop_db mysql:build_db] end desc "copy sample database credential files over if real files don't exist" @@ -40,29 +46,36 @@ namespace :db do apartment_db_file = 'spec/config/database.yml' rails_db_file = 'spec/dummy/config/database.yml' - FileUtils.copy(apartment_db_file + '.sample', apartment_db_file, :verbose => true) unless File.exists?(apartment_db_file) - FileUtils.copy(rails_db_file + '.sample', rails_db_file, :verbose => true) unless File.exists?(rails_db_file) + unless File.exist?(apartment_db_file) + FileUtils.copy("#{apartment_db_file}.sample", apartment_db_file, verbose: true) + end + FileUtils.copy("#{rails_db_file}.sample", rails_db_file, verbose: true) unless File.exist?(rails_db_file) end end namespace :postgres do require 'active_record' - require "#{File.join(File.dirname(__FILE__), 'spec', 'support', 'config')}" + require File.join(File.dirname(__FILE__), 'spec', 'support', 'config').to_s desc 'Build the PostgreSQL test databases' task :build_db do params = [] - params << "-E UTF8" + params << '-E UTF8' params << pg_config['database'] params << "-U#{pg_config['username']}" params << "-h#{pg_config['host']}" if pg_config['host'] params << "-p#{pg_config['port']}" if pg_config['port'] - %x{ createdb #{params.join(' ')} } rescue "test db already exists" + + begin + `createdb #{params.join(' ')}` + rescue StandardError + 'test db already exists' + end ActiveRecord::Base.establish_connection pg_config migrate end - desc "drop the PostgreSQL test database" + desc 'drop the PostgreSQL test database' task :drop_db do puts "dropping database #{pg_config['database']}" params = [] @@ -70,14 +83,13 @@ namespace :postgres do params << "-U#{pg_config['username']}" params << "-h#{pg_config['host']}" if pg_config['host'] params << "-p#{pg_config['port']}" if pg_config['port'] - %x{ dropdb #{params.join(' ')} } + `dropdb #{params.join(' ')}` end - end namespace :mysql do require 'active_record' - require "#{File.join(File.dirname(__FILE__), 'spec', 'support', 'config')}" + require File.join(File.dirname(__FILE__), 'spec', 'support', 'config').to_s desc 'Build the MySQL test databases' task :build_db do @@ -85,24 +97,29 @@ namespace :mysql do params << "-h #{my_config['host']}" if my_config['host'] params << "-u #{my_config['username']}" if my_config['username'] params << "-p#{my_config['password']}" if my_config['password'] - %x{ mysqladmin #{params.join(' ')} create #{my_config['database']} } rescue "test db already exists" + params << "--port #{my_config['port']}" if my_config['port'] + begin + `mysqladmin #{params.join(' ')} create #{my_config['database']}` + rescue StandardError + 'test db already exists' + end ActiveRecord::Base.establish_connection my_config migrate end - desc "drop the MySQL test database" + desc 'drop the MySQL test database' task :drop_db do puts "dropping database #{my_config['database']}" params = [] params << "-h #{my_config['host']}" if my_config['host'] params << "-u #{my_config['username']}" if my_config['username'] params << "-p#{my_config['password']}" if my_config['password'] - %x{ mysqladmin #{params.join(' ')} drop #{my_config['database']} --force} + params << "--port #{my_config['port']}" if my_config['port'] + `mysqladmin #{params.join(' ')} drop #{my_config['database']} --force` end - end -# TODO clean this up +# TODO: clean this up def config Apartment::Test.config['connections'] end @@ -119,10 +136,18 @@ def activerecord_below_5_2? ActiveRecord.version.release < Gem::Version.new('5.2.0') end +def activerecord_below_6_0? + ActiveRecord.version.release < Gem::Version.new('6.0.0') +end + def migrate if activerecord_below_5_2? ActiveRecord::Migrator.migrate('spec/dummy/db/migrate') - else + elsif activerecord_below_6_0? ActiveRecord::MigrationContext.new('spec/dummy/db/migrate').migrate + else + # TODO: Figure out if there is any other possibility that can/should be + # passed here as the second argument for the migration context + ActiveRecord::MigrationContext.new('spec/dummy/db/migrate', ActiveRecord::SchemaMigration).migrate end end diff --git a/TODO.md b/TODO.md index 533b9ab2..a5226393 100644 --- a/TODO.md +++ b/TODO.md @@ -46,6 +46,5 @@ Quick TODOs -1. `default_tenant` should be up to the adapter, not the Apartment class, deprecate `default_schema` 2. deprecation.rb rescues everything, we have a hard dependency on ActiveSupport so this is unnecessary 3. diff --git a/apartment.gemspec b/apartment.gemspec deleted file mode 100644 index eb59070f..00000000 --- a/apartment.gemspec +++ /dev/null @@ -1,46 +0,0 @@ -# -*- encoding: utf-8 -*- -$: << File.expand_path("../lib", __FILE__) -require "apartment/version" - -Gem::Specification.new do |s| - s.name = %q{apartment} - s.version = Apartment::VERSION - - s.authors = ["Ryan Brunner", "Brad Robertson"] - s.summary = %q{A Ruby gem for managing database multitenancy} - s.description = %q{Apartment allows Rack applications to deal with database multitenancy through ActiveRecord} - s.email = ["ryan@influitive.com", "brad@influitive.com"] - s.files = `git ls-files`.split($/) - s.executables = s.files.grep(%r{^bin/}).map{ |f| File.basename(f) } - s.test_files = s.files.grep(%r{^(test|spec|features)/}) - s.require_paths = ["lib"] - - s.homepage = %q{https://github.com/influitive/apartment} - s.licenses = ["MIT"] - - # must be >= 3.1.2 due to bug in prepared_statements - s.add_dependency 'activerecord', '>= 3.1.2', '< 6.1' - s.add_dependency 'rack', '>= 1.3.6' - s.add_dependency 'public_suffix', '>= 2' - s.add_dependency 'parallel', '>= 0.7.1' - - s.add_development_dependency 'appraisal' - s.add_development_dependency 'rake', '~> 0.9' - s.add_development_dependency 'rspec', '~> 3.4' - s.add_development_dependency 'rspec-rails', '~> 3.4' - s.add_development_dependency 'capybara', '~> 2.0' - s.add_development_dependency 'bundler', '>= 1.3', '< 2.0' - - if defined?(JRUBY_VERSION) - s.add_development_dependency 'activerecord-jdbc-adapter' - s.add_development_dependency 'activerecord-jdbcpostgresql-adapter' - s.add_development_dependency 'activerecord-jdbcmysql-adapter' - s.add_development_dependency 'jdbc-postgres' - s.add_development_dependency 'jdbc-mysql' - s.add_development_dependency 'jruby-openssl' - else - s.add_development_dependency 'mysql2' - s.add_development_dependency 'pg' - s.add_development_dependency 'sqlite3', '~> 1.3.6' - end -end diff --git a/documentation/images/log_example.png b/documentation/images/log_example.png new file mode 100644 index 00000000..71b0e7c0 Binary files /dev/null and b/documentation/images/log_example.png differ diff --git a/gemfiles/rails_4_2.gemfile b/gemfiles/rails_4_2.gemfile index ef31a500..33048072 100644 --- a/gemfiles/rails_4_2.gemfile +++ b/gemfiles/rails_4_2.gemfile @@ -1,23 +1,25 @@ +# frozen_string_literal: true + # This file was generated by Appraisal -source "http://rubygems.org" +source 'http://rubygems.org' -gem "rails", "~> 4.2.0" +gem 'rails', '~> 4.2.0' group :local do - gem "pry" - gem "guard-rspec", "~> 4.2" + gem 'guard-rspec', '~> 4.2' + gem 'pry' end platforms :ruby do - gem "pg", "< 1.0.0" - gem "mysql2", "~> 0.4.0" + gem 'mysql2', '~> 0.4.0' + gem 'pg', '< 1.0.0' end platforms :jruby do - gem "activerecord-jdbc-adapter", "~> 1.3" - gem "activerecord-jdbcpostgresql-adapter", "~> 1.3" - gem "activerecord-jdbcmysql-adapter", "~> 1.3" + gem 'activerecord-jdbc-adapter', '~> 1.3' + gem 'activerecord-jdbcmysql-adapter', '~> 1.3' + gem 'activerecord-jdbcpostgresql-adapter', '~> 1.3' end -gemspec path: "../" +gemspec path: '../' diff --git a/gemfiles/rails_5_0.gemfile b/gemfiles/rails_5_0.gemfile index 07d99a14..37dc042b 100644 --- a/gemfiles/rails_5_0.gemfile +++ b/gemfiles/rails_5_0.gemfile @@ -3,10 +3,11 @@ source "http://rubygems.org" gem "rails", "~> 5.0.0" +gem "rubocop" group :local do - gem "pry" gem "guard-rspec", "~> 4.2" + gem "pry" end platforms :ruby do diff --git a/gemfiles/rails_5_1.gemfile b/gemfiles/rails_5_1.gemfile index 28353198..59af05f8 100644 --- a/gemfiles/rails_5_1.gemfile +++ b/gemfiles/rails_5_1.gemfile @@ -3,10 +3,11 @@ source "http://rubygems.org" gem "rails", "~> 5.1.0" +gem "rubocop" group :local do - gem "pry" gem "guard-rspec", "~> 4.2" + gem "pry" end platforms :ruby do diff --git a/gemfiles/rails_5_2.gemfile b/gemfiles/rails_5_2.gemfile index 5a18c669..18d8952a 100644 --- a/gemfiles/rails_5_2.gemfile +++ b/gemfiles/rails_5_2.gemfile @@ -3,10 +3,11 @@ source "http://rubygems.org" gem "rails", "~> 5.2.0" +gem "rubocop" group :local do - gem "pry" gem "guard-rspec", "~> 4.2" + gem "pry" end platforms :jruby do diff --git a/gemfiles/rails_6_0.gemfile b/gemfiles/rails_6_0.gemfile index 20ed8a37..6d23e4aa 100644 --- a/gemfiles/rails_6_0.gemfile +++ b/gemfiles/rails_6_0.gemfile @@ -2,11 +2,12 @@ source "http://rubygems.org" -gem "rails", "~> 6.0.0.rc1" +gem "rails", "~> 6.0.0" +gem "rubocop" group :local do - gem "pry" gem "guard-rspec", "~> 4.2" + gem "pry" end platforms :ruby do @@ -14,9 +15,9 @@ platforms :ruby do end platforms :jruby do - gem "activerecord-jdbc-adapter", "~> 60.0.rc1" - gem "activerecord-jdbcpostgresql-adapter", "~> 60.0.rc1" - gem "activerecord-jdbcmysql-adapter", "~> 60.0.rc1" + gem "activerecord-jdbc-adapter", "~> 60.0" + gem "activerecord-jdbcpostgresql-adapter", "~> 60.0" + gem "activerecord-jdbcmysql-adapter", "~> 60.0" end gemspec path: "../" diff --git a/gemfiles/rails_6_1.gemfile b/gemfiles/rails_6_1.gemfile new file mode 100644 index 00000000..e6de4f27 --- /dev/null +++ b/gemfiles/rails_6_1.gemfile @@ -0,0 +1,23 @@ +# This file was generated by Appraisal + +source "http://rubygems.org" + +gem "rails", "~> 6.1.0" +gem "rubocop" + +group :local do + gem "guard-rspec", "~> 4.2" + gem "pry" +end + +platforms :ruby do + gem "sqlite3", "~> 1.4" +end + +platforms :jruby do + gem "activerecord-jdbc-adapter", "~> 61.0" + gem "activerecord-jdbcpostgresql-adapter", "~> 61.0" + gem "activerecord-jdbcmysql-adapter", "~> 61.0" +end + +gemspec path: "../" diff --git a/gemfiles/rails_master.gemfile b/gemfiles/rails_master.gemfile index edc30465..82ad9191 100644 --- a/gemfiles/rails_master.gemfile +++ b/gemfiles/rails_master.gemfile @@ -3,10 +3,11 @@ source "http://rubygems.org" gem "rails", git: "https://github.com/rails/rails.git" +gem "rubocop" group :local do - gem "pry" gem "guard-rspec", "~> 4.2" + gem "pry" end platforms :ruby do diff --git a/lib/apartment.rb b/lib/apartment.rb index b20a7728..a9506fd9 100644 --- a/lib/apartment.rb +++ b/lib/apartment.rb @@ -1,22 +1,47 @@ +# frozen_string_literal: true + require 'apartment/railtie' if defined?(Rails) require 'active_support/core_ext/object/blank' require 'forwardable' require 'active_record' require 'apartment/tenant' -module Apartment +require_relative 'apartment/log_subscriber' - class << self +if ActiveRecord.version.release >= Gem::Version.new('6.0') + require_relative 'apartment/active_record/connection_handling' +end + +if ActiveRecord.version.release >= Gem::Version.new('6.1') + require_relative 'apartment/active_record/schema_migration' + require_relative 'apartment/active_record/internal_metadata' +end +# Apartment main definitions +module Apartment + class << self extend Forwardable - ACCESSOR_METHODS = [:use_schemas, :use_sql, :seed_after_create, :prepend_environment, :append_environment, :with_multi_server_setup] - WRITER_METHODS = [:tenant_names, :database_schema_file, :excluded_models, :default_schema, :persistent_schemas, :connection_class, :tld_length, :db_migrate_tenants, :seed_data_file, :parallel_migration_threads, :pg_excluded_names] + ACCESSOR_METHODS = %i[use_schemas use_sql seed_after_create prepend_environment default_tenant + append_environment with_multi_server_setup tenant_presence_check active_record_log].freeze + + WRITER_METHODS = %i[tenant_names database_schema_file excluded_models + persistent_schemas connection_class + db_migrate_tenants db_migrate_tenant_missing_strategy seed_data_file + parallel_migration_threads pg_excluded_names].freeze attr_accessor(*ACCESSOR_METHODS) attr_writer(*WRITER_METHODS) - def_delegators :connection_class, :connection, :connection_config, :establish_connection + if ActiveRecord.version.release >= Gem::Version.new('6.1') + def_delegators :connection_class, :connection, :connection_db_config, :establish_connection + + def connection_config + connection_db_config.configuration_hash + end + else + def_delegators :connection_class, :connection, :connection_config, :establish_connection + end # configure apartment with available options def configure @@ -31,8 +56,12 @@ def tenants_with_config extract_tenant_config end + def tld_length=(_) + Apartment::Deprecation.warn('`config.tld_length` have no effect because it was removed in https://github.com/influitive/apartment/pull/309') + end + def db_config_for(tenant) - (tenants_with_config[tenant] || connection_config).with_indifferent_access + (tenants_with_config[tenant] || connection_config) end # Whether or not db:migrate should also migrate tenants @@ -43,20 +72,29 @@ def db_migrate_tenants @db_migrate_tenants = true end + # How to handle tenant missing on db:migrate + # defaults to :rescue_exception + # available options: rescue_exception, raise_exception, create_tenant + def db_migrate_tenant_missing_strategy + valid = %i[rescue_exception raise_exception create_tenant] + value = @db_migrate_tenant_missing_strategy || :rescue_exception + + return value if valid.include?(value) + + key_name = 'config.db_migrate_tenant_missing_strategy' + opt_names = valid.join(', ') + + raise ApartmentError, "Option #{value} not valid for `#{key_name}`. Use one of #{opt_names}" + end + # Default to empty array def excluded_models @excluded_models || [] end - def default_schema - @default_schema || "public" # TODO 'public' is postgres specific - end - def parallel_migration_threads @parallel_migration_threads || 0 end - alias :default_tenant :default_schema - alias :default_tenant= :default_schema= def persistent_schemas @persistent_schemas || [] @@ -75,7 +113,7 @@ def database_schema_file def seed_data_file return @seed_data_file if defined?(@seed_data_file) - @seed_data_file = "#{Rails.root}/db/seeds.rb" + @seed_data_file = Rails.root.join('db', 'seeds.rb') end def pg_excluded_names @@ -84,11 +122,14 @@ def pg_excluded_names # Reset all the config for Apartment def reset - (ACCESSOR_METHODS + WRITER_METHODS).each{|method| remove_instance_variable(:"@#{method}") if instance_variable_defined?(:"@#{method}") } + (ACCESSOR_METHODS + WRITER_METHODS).each do |method| + remove_instance_variable(:"@#{method}") if instance_variable_defined?(:"@#{method}") + end end def extract_tenant_config return {} unless @tenant_names + values = @tenant_names.respond_to?(:call) ? @tenant_names.call : @tenant_names unless values.is_a? Hash values = values.each_with_object({}) do |tenant, hash| diff --git a/lib/apartment/active_record/connection_handling.rb b/lib/apartment/active_record/connection_handling.rb new file mode 100644 index 00000000..f306258e --- /dev/null +++ b/lib/apartment/active_record/connection_handling.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module ActiveRecord + # This is monkeypatching activerecord to ensure that whenever a new connection is established it + # switches to the same tenant as before the connection switching. This problem is more evident when + # using read replica in Rails 6 + module ConnectionHandling + def connected_to_with_tenant(database: nil, role: nil, prevent_writes: false, &blk) + current_tenant = Apartment::Tenant.current + + connected_to_without_tenant(database: database, role: role, prevent_writes: prevent_writes) do + Apartment::Tenant.switch!(current_tenant) + yield(blk) + end + end + + alias connected_to_without_tenant connected_to + alias connected_to connected_to_with_tenant + end +end diff --git a/lib/apartment/active_record/internal_metadata.rb b/lib/apartment/active_record/internal_metadata.rb new file mode 100644 index 00000000..4febd0cf --- /dev/null +++ b/lib/apartment/active_record/internal_metadata.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class InternalMetadata < ActiveRecord::Base # :nodoc: + class << self + def table_exists? + connection.table_exists?(table_name) + end + end +end diff --git a/lib/apartment/active_record/schema_migration.rb b/lib/apartment/active_record/schema_migration.rb new file mode 100644 index 00000000..cbad3648 --- /dev/null +++ b/lib/apartment/active_record/schema_migration.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module ActiveRecord + class SchemaMigration < ActiveRecord::Base # :nodoc: + class << self + def table_exists? + connection.table_exists?(table_name) + end + end + end +end diff --git a/lib/apartment/adapters/abstract_adapter.rb b/lib/apartment/adapters/abstract_adapter.rb index 78b844b9..e59ba523 100644 --- a/lib/apartment/adapters/abstract_adapter.rb +++ b/lib/apartment/adapters/abstract_adapter.rb @@ -1,5 +1,8 @@ +# frozen_string_literal: true + module Apartment module Adapters + # Abstract adapter from which all the Apartment DB related adapters will inherit the base logic class AbstractAdapter include ActiveSupport::Callbacks define_callbacks :create, :switch @@ -32,6 +35,12 @@ def create(tenant) end end + # Initialize Apartment config options such as excluded_models + # + def init + process_excluded_models + end + # Note alias_method here doesn't work with inheritence apparently ?? # def current @@ -45,7 +54,6 @@ def current def default_tenant @default_tenant || Apartment.default_tenant end - alias :default_schema :default_tenant # TODO deprecate default_schema # Drop the tenant # @@ -55,9 +63,8 @@ def drop(tenant) with_neutral_connection(tenant) do |conn| drop_command(conn, tenant) end - - rescue *rescuable_exceptions => exception - raise_drop_tenant_error!(tenant, exception) + rescue *rescuable_exceptions => e + raise_drop_tenant_error!(tenant, e) end # Switch to a new tenant @@ -66,8 +73,6 @@ def drop(tenant) # def switch!(tenant = nil) run_callbacks :switch do - return reset if tenant.nil? - connect_to_new(tenant).tap do Apartment.connection.clear_query_cache end @@ -79,13 +84,14 @@ def switch!(tenant = nil) # @param {String?} tenant to connect to # def switch(tenant = nil) + previous_tenant = current + switch!(tenant) + yield + ensure begin - previous_tenant = current - switch!(tenant) - yield - - ensure - switch!(previous_tenant) rescue reset + switch!(previous_tenant) + rescue StandardError => _e + reset end end @@ -93,14 +99,15 @@ def switch(tenant = nil) # def each(tenants = Apartment.tenant_names) tenants.each do |tenant| - switch(tenant){ yield tenant } + switch(tenant) { yield tenant } end end # Establish a new connection for each specific excluded model # def process_excluded_models - # All other models will shared a connection (at Apartment.connection_class) and we can modify at will + # All other models will shared a connection (at Apartment.connection_class) + # and we can modify at will Apartment.excluded_models.each do |excluded_model| process_excluded_model(excluded_model) end @@ -116,9 +123,9 @@ def reset # def seed_data # Don't log the output of seeding the db - silence_warnings{ load_or_raise(Apartment.seed_data_file) } if Apartment.seed_data_file + silence_warnings { load_or_raise(Apartment.seed_data_file) } if Apartment.seed_data_file end - alias_method :seed, :seed_data + alias seed seed_data # Prepend the environment if configured and the environment isn't already there # @@ -126,20 +133,18 @@ def seed_data # @return {String} tenant name with Rails environment *optionally* prepended # def environmentify(tenant) - unless tenant.include?(Rails.env) - if Apartment.prepend_environment - "#{Rails.env}_#{tenant}" - elsif Apartment.append_environment - "#{tenant}_#{Rails.env}" - else - tenant - end + return tenant if tenant.nil? || tenant.include?(Rails.env) + + if Apartment.prepend_environment + "#{Rails.env}_#{tenant}" + elsif Apartment.append_environment + "#{tenant}_#{Rails.env}" else tenant end end - protected + protected def process_excluded_model(excluded_model) excluded_model.constantize.establish_connection @config @@ -158,8 +163,8 @@ def create_tenant(tenant) with_neutral_connection(tenant) do |conn| create_tenant_command(conn, tenant) end - rescue *rescuable_exceptions => exception - raise_create_tenant_error!(tenant, exception) + rescue *rescuable_exceptions => e + raise_create_tenant_error!(tenant, e) end def create_tenant_command(conn, tenant) @@ -171,21 +176,23 @@ def create_tenant_command(conn, tenant) # @param {String} tenant Database name # def connect_to_new(tenant) + return reset if tenant.nil? + query_cache_enabled = ActiveRecord::Base.connection.query_cache_enabled Apartment.establish_connection multi_tenantify(tenant) - Apartment.connection.active? # call active? to manually check if this connection is valid + Apartment.connection.active? # call active? to manually check if this connection is valid Apartment.connection.enable_query_cache! if query_cache_enabled - rescue *rescuable_exceptions => exception + rescue *rescuable_exceptions => e Apartment::Tenant.reset if reset_on_connection_exception? - raise_connect_error!(tenant, exception) + raise_connect_error!(tenant, e) end # Import the database schema # def import_database_schema - ActiveRecord::Schema.verbose = false # do not log schema load output. + ActiveRecord::Schema.verbose = false # do not log schema load output. load_or_raise(Apartment.database_schema_file) if Apartment.database_schema_file end @@ -194,13 +201,13 @@ def import_database_schema # @param {String} tenant: Database name # @param {Boolean} with_database: if true, use the actual tenant's db name # if false, use the default db name from the db + # rubocop:disable Style/OptionalBooleanParameter def multi_tenantify(tenant, with_database = true) db_connection_config(tenant).tap do |config| - if with_database - multi_tenantify_with_tenant_db_name(config, tenant) - end + multi_tenantify_with_tenant_db_name(config, tenant) if with_database end end + # rubocop:enable Style/OptionalBooleanParameter def multi_tenantify_with_tenant_db_name(config, tenant) config[:database] = environmentify(tenant) @@ -209,14 +216,12 @@ def multi_tenantify_with_tenant_db_name(config, tenant) # Load a file or raise error if it doesn't exists # def load_or_raise(file) - if File.exists?(file) - load(file) - else - raise FileNotFound, "#{file} doesn't exist yet" - end + raise FileNotFound, "#{file} doesn't exist yet" unless File.exist?(file) + + load(file) end # Backward compatibility - alias_method :load_or_abort, :load_or_raise + alias load_or_abort load_or_raise # Exceptions to rescue from on db operations # @@ -231,13 +236,14 @@ def rescue_from end def db_connection_config(tenant) - Apartment.db_config_for(tenant).clone + Apartment.db_config_for(tenant).dup end - def with_neutral_connection(tenant, &block) + def with_neutral_connection(tenant, &_block) if Apartment.with_multi_server_setup # neutral connection is necessary whenever you need to create/remove a database from a server. - # example: when you use postgresql, you need to connect to the default postgresql database before you create your own. + # example: when you use postgresql, you need to connect to the default postgresql database before you create + # your own. SeparateDbConnectionHandler.establish_connection(multi_tenantify(tenant, false)) yield(SeparateDbConnectionHandler.connection) SeparateDbConnectionHandler.connection.close @@ -251,15 +257,15 @@ def reset_on_connection_exception? end def raise_drop_tenant_error!(tenant, exception) - raise TenantNotFound, "Error while dropping tenant #{environmentify(tenant)}: #{ exception.message }" + raise TenantNotFound, "Error while dropping tenant #{environmentify(tenant)}: #{exception.message}" end def raise_create_tenant_error!(tenant, exception) - raise TenantExists, "Error while creating tenant #{environmentify(tenant)}: #{ exception.message }" + raise TenantExists, "Error while creating tenant #{environmentify(tenant)}: #{exception.message}" end def raise_connect_error!(tenant, exception) - raise TenantNotFound, "Error while connecting to tenant #{environmentify(tenant)}: #{ exception.message }" + raise TenantNotFound, "Error while connecting to tenant #{environmentify(tenant)}: #{exception.message}" end class SeparateDbConnectionHandler < ::ActiveRecord::Base diff --git a/lib/apartment/adapters/abstract_jdbc_adapter.rb b/lib/apartment/adapters/abstract_jdbc_adapter.rb index c4f7f32f..4dd0748c 100644 --- a/lib/apartment/adapters/abstract_jdbc_adapter.rb +++ b/lib/apartment/adapters/abstract_jdbc_adapter.rb @@ -1,13 +1,15 @@ +# frozen_string_literal: true + require 'apartment/adapters/abstract_adapter' module Apartment module Adapters + # JDBC Abstract adapter class AbstractJDBCAdapter < AbstractAdapter - - private + private def multi_tenantify_with_tenant_db_name(config, tenant) - config[:url] = "#{config[:url].gsub(/(\S+)\/.+$/, '\1')}/#{environmentify(tenant)}" + config[:url] = "#{config[:url].gsub(%r{(\S+)/.+$}, '\1')}/#{environmentify(tenant)}" end def rescue_from diff --git a/lib/apartment/adapters/jdbc_mysql_adapter.rb b/lib/apartment/adapters/jdbc_mysql_adapter.rb index 53fc7dea..90c1bfeb 100644 --- a/lib/apartment/adapters/jdbc_mysql_adapter.rb +++ b/lib/apartment/adapters/jdbc_mysql_adapter.rb @@ -1,7 +1,8 @@ -require "apartment/adapters/abstract_jdbc_adapter" +# frozen_string_literal: true -module Apartment +require 'apartment/adapters/abstract_jdbc_adapter' +module Apartment module Tenant def self.jdbc_mysql_adapter(config) Adapters::JDBCMysqlAdapter.new config @@ -10,7 +11,6 @@ def self.jdbc_mysql_adapter(config) module Adapters class JDBCMysqlAdapter < AbstractJDBCAdapter - def reset_on_connection_exception? true end diff --git a/lib/apartment/adapters/jdbc_postgresql_adapter.rb b/lib/apartment/adapters/jdbc_postgresql_adapter.rb index f0922d8c..57cf2fa5 100644 --- a/lib/apartment/adapters/jdbc_postgresql_adapter.rb +++ b/lib/apartment/adapters/jdbc_postgresql_adapter.rb @@ -1,28 +1,30 @@ +# frozen_string_literal: true + require 'apartment/adapters/postgresql_adapter' module Apartment + # JDBC helper to decide wether to use JDBC Postgresql Adapter or JDBC Postgresql Adapter with Schemas module Tenant - def self.jdbc_postgresql_adapter(config) - Apartment.use_schemas ? - Adapters::JDBCPostgresqlSchemaAdapter.new(config) : + if Apartment.use_schemas + Adapters::JDBCPostgresqlSchemaAdapter.new(config) + else Adapters::JDBCPostgresqlAdapter.new(config) + end end end module Adapters - # Default adapter when not using Postgresql Schemas class JDBCPostgresqlAdapter < PostgresqlAdapter - - private + private def multi_tenantify_with_tenant_db_name(config, tenant) - config[:url] = "#{config[:url].gsub(/(\S+)\/.+$/, '\1')}/#{environmentify(tenant)}" + config[:url] = "#{config[:url].gsub(%r{(\S+)/.+$}, '\1')}/#{environmentify(tenant)}" end def create_tenant_command(conn, tenant) - conn.create_database(environmentify(tenant), { :thisisahack => '' }) + conn.create_database(environmentify(tenant), thisisahack: '') end def rescue_from @@ -32,21 +34,28 @@ def rescue_from # Separate Adapter for Postgresql when using schemas class JDBCPostgresqlSchemaAdapter < PostgresqlSchemaAdapter - # Set schema search path to new schema # def connect_to_new(tenant = nil) return reset if tenant.nil? - raise ActiveRecord::StatementInvalid.new("Could not find schema #{tenant}") unless Apartment.connection.all_schemas.include? tenant.to_s + raise ActiveRecord::StatementInvalid, "Could not find schema #{tenant}" unless schema_exists?(tenant) - @current = tenant.to_s + @current = tenant.is_a?(Array) ? tenant.map(&:to_s) : tenant.to_s Apartment.connection.schema_search_path = full_search_path + @current = tenant + Apartment.connection.schema_search_path = full_search_path rescue ActiveRecord::StatementInvalid, ActiveRecord::JDBCError raise TenantNotFound, "One of the following schema(s) is invalid: #{full_search_path}" end - private + private + + def tenant_exists?(tenant) + return true unless Apartment.tenant_presence_check + + Apartment.connection.all_schemas.include? tenant + end def rescue_from ActiveRecord::JDBCError diff --git a/lib/apartment/adapters/mysql2_adapter.rb b/lib/apartment/adapters/mysql2_adapter.rb index d1e324ca..ca9b1a7f 100644 --- a/lib/apartment/adapters/mysql2_adapter.rb +++ b/lib/apartment/adapters/mysql2_adapter.rb @@ -1,31 +1,36 @@ +# frozen_string_literal: true + require 'apartment/adapters/abstract_adapter' module Apartment + # Helper module to decide wether to use mysql2 adapter or mysql2 adapter with schemas module Tenant - def self.mysql2_adapter(config) - Apartment.use_schemas ? - Adapters::Mysql2SchemaAdapter.new(config) : + if Apartment.use_schemas + Adapters::Mysql2SchemaAdapter.new(config) + else Adapters::Mysql2Adapter.new(config) + end end end module Adapters + # Mysql2 Adapter class Mysql2Adapter < AbstractAdapter - def initialize(config) super @default_tenant = config[:database] end - protected + protected def rescue_from Mysql2::Error end end + # Mysql2 Schemas Adapter class Mysql2SchemaAdapter < AbstractAdapter def initialize(config) super @@ -37,10 +42,12 @@ def initialize(config) # Reset current tenant to the default_tenant # def reset + return unless default_tenant + Apartment.connection.execute "use `#{default_tenant}`" end - protected + protected # Connect to new tenant # @@ -48,10 +55,9 @@ def connect_to_new(tenant) return reset if tenant.nil? Apartment.connection.execute "use `#{environmentify(tenant)}`" - - rescue ActiveRecord::StatementInvalid => exception + rescue ActiveRecord::StatementInvalid => e Apartment::Tenant.reset - raise_connect_error!(tenant, exception) + raise_connect_error!(tenant, e) end def process_excluded_model(model) diff --git a/lib/apartment/adapters/postgis_adapter.rb b/lib/apartment/adapters/postgis_adapter.rb index 4f5e9176..bcf67be1 100644 --- a/lib/apartment/adapters/postgis_adapter.rb +++ b/lib/apartment/adapters/postgis_adapter.rb @@ -1,10 +1,11 @@ +# frozen_string_literal: true + # handle postgis adapter as if it were postgresql, # only override the adapter_method used for initialization -require "apartment/adapters/postgresql_adapter" +require 'apartment/adapters/postgresql_adapter' module Apartment module Tenant - def self.postgis_adapter(config) postgresql_adapter(config) end diff --git a/lib/apartment/adapters/postgresql_adapter.rb b/lib/apartment/adapters/postgresql_adapter.rb index 446b9b5d..860523a7 100644 --- a/lib/apartment/adapters/postgresql_adapter.rb +++ b/lib/apartment/adapters/postgresql_adapter.rb @@ -1,8 +1,9 @@ +# frozen_string_literal: true + require 'apartment/adapters/abstract_adapter' module Apartment module Tenant - def self.postgresql_adapter(config) adapter = Adapters::PostgresqlAdapter adapter = Adapters::PostgresqlSchemaAdapter if Apartment.use_schemas @@ -14,8 +15,7 @@ def self.postgresql_adapter(config) module Adapters # Default adapter when not using Postgresql Schemas class PostgresqlAdapter < AbstractAdapter - - private + private def rescue_from PG::Error @@ -24,13 +24,16 @@ def rescue_from # Separate Adapter for Postgresql when using schemas class PostgresqlSchemaAdapter < AbstractAdapter - def initialize(config) super reset end + def default_tenant + @default_tenant = Apartment.default_tenant || 'public' + end + # Reset schema search path to the default schema_search_path # # @return {String} default schema search path @@ -38,13 +41,19 @@ def initialize(config) def reset @current = default_tenant Apartment.connection.schema_search_path = full_search_path + reset_sequence_names + end + + def init + super + Apartment.connection.schema_search_path = full_search_path end def current @current || default_tenant end - protected + protected def process_excluded_model(excluded_model) excluded_model.constantize.tap do |klass| @@ -56,39 +65,59 @@ def process_excluded_model(excluded_model) end def drop_command(conn, tenant) - conn.execute(%{DROP SCHEMA "#{tenant}" CASCADE}) + conn.execute(%(DROP SCHEMA "#{tenant}" CASCADE)) end # Set schema search path to new schema # def connect_to_new(tenant = nil) return reset if tenant.nil? - raise ActiveRecord::StatementInvalid.new("Could not find schema #{tenant}") unless Apartment.connection.schema_exists?(tenant.to_s) + raise ActiveRecord::StatementInvalid, "Could not find schema #{tenant}" unless schema_exists?(tenant) - @current = tenant.to_s + @current = tenant.is_a?(Array) ? tenant.map(&:to_s) : tenant.to_s Apartment.connection.schema_search_path = full_search_path # When the PostgreSQL version is < 9.3, # there is a issue for prepared statement with changing search_path. # https://www.postgresql.org/docs/9.3/static/sql-prepare.html - if postgresql_version < 90300 - Apartment.connection.clear_cache! - end - + Apartment.connection.clear_cache! if postgresql_version < 90_300 + reset_sequence_names rescue *rescuable_exceptions raise TenantNotFound, "One of the following schema(s) is invalid: \"#{tenant}\" #{full_search_path}" end - private + private + + def tenant_exists?(tenant) + return true unless Apartment.tenant_presence_check + + Apartment.connection.schema_exists?(tenant) + end def create_tenant_command(conn, tenant) - conn.execute(%{CREATE SCHEMA "#{tenant}"}) + # NOTE: This was causing some tests to fail because of the database strategy for rspec + if ActiveRecord::Base.connection.open_transactions.positive? + conn.execute(%(CREATE SCHEMA "#{tenant}")) + else + schema = %(BEGIN; + CREATE SCHEMA "#{tenant}"; + COMMIT;) + + conn.execute(schema) + end + rescue *rescuable_exceptions => e + rollback_transaction(conn) + raise e + end + + def rollback_transaction(conn) + conn.execute('ROLLBACK;') end # Generate the final search path to set including persistent_schemas # def full_search_path - persistent_schemas.map(&:inspect).join(", ") + persistent_schemas.map(&:inspect).join(', ') end def persistent_schemas @@ -100,20 +129,40 @@ def postgresql_version # public from Rails 5.0. Apartment.connection.send(:postgresql_version) end + + def reset_sequence_names + # sequence_name contains the schema, so it must be reset after switch + # There is `reset_sequence_name`, but that method actually goes to the database + # to find out the new name. Therefore, we do this hack to only unset the name, + # and it will be dynamically found the next time it is needed + descendants_to_unset = ActiveRecord::Base.descendants + .select { |c| c.instance_variable_defined?(:@sequence_name) } + .reject do |c| + c.instance_variable_defined?(:@explicit_sequence_name) && + c.instance_variable_get(:@explicit_sequence_name) + end + descendants_to_unset.each do |c| + # NOTE: due to this https://github.com/rails-on-services/apartment/issues/81 + # unreproduceable error we're checking before trying to remove it + c.remove_instance_variable :@sequence_name if c.instance_variable_defined?(:@sequence_name) + end + + def schema_exists?(schemas) + [*schemas].all? { |schema| Apartment.connection.schema_exists?(schema.to_s) } + end end # Another Adapter for Postgresql when using schemas and SQL class PostgresqlSchemaFromSqlAdapter < PostgresqlSchemaAdapter - - PSQL_DUMP_BLACKLISTED_STATEMENTS= [ + PSQL_DUMP_BLACKLISTED_STATEMENTS = [ /SET search_path/i, # overridden later /SET lock_timeout/i, # new in postgresql 9.3 /SET row_security/i, # new in postgresql 9.5 /SET idle_in_transaction_session_timeout/i, # new in postgresql 9.6 /CREATE SCHEMA public/i, - /COMMENT ON SCHEMA public/i, + /COMMENT ON SCHEMA public/i - ] + ].freeze def import_database_schema preserving_search_path do @@ -122,14 +171,14 @@ def import_database_schema end end - private + private # Re-set search path after the schema is imported. # Postgres now sets search path to empty before dumping the schema # and it mut be reset # def preserving_search_path - search_path = Apartment.connection.execute("show search_path").first["search_path"] + search_path = Apartment.connection.execute('show search_path').first['search_path'] yield Apartment.connection.execute("set search_path = #{search_path}") end @@ -153,7 +202,6 @@ def copy_schema_migrations # @return {String} raw SQL contaning only postgres schema dump # def pg_dump_schema - # Skip excluded tables? :/ # excluded_tables = # collect_table_names(Apartment.excluded_models) @@ -169,14 +217,19 @@ def pg_dump_schema # # @return {String} raw SQL contaning inserts with data from schema_migrations # + # rubocop:disable Layout/LineLength def pg_dump_schema_migrations_data with_pg_env { `pg_dump -a --inserts -t #{default_tenant}.schema_migrations -t #{default_tenant}.ar_internal_metadata #{dbname}` } end + # rubocop:enable Layout/LineLength # Temporary set Postgresql related environment variables if there are in @config # def with_pg_env(&block) - pghost, pgport, pguser, pgpassword = ENV['PGHOST'], ENV['PGPORT'], ENV['PGUSER'], ENV['PGPASSWORD'] + pghost = ENV['PGHOST'] + pgport = ENV['PGPORT'] + pguser = ENV['PGUSER'] + pgpassword = ENV['PGPASSWORD'] ENV['PGHOST'] = @config[:host] if @config[:host] ENV['PGPORT'] = @config[:port].to_s if @config[:port] @@ -185,7 +238,10 @@ def with_pg_env(&block) block.call ensure - ENV['PGHOST'], ENV['PGPORT'], ENV['PGUSER'], ENV['PGPASSWORD'] = pghost, pgport, pguser, pgpassword + ENV['PGHOST'] = pghost + ENV['PGPORT'] = pgport + ENV['PGUSER'] = pguser + ENV['PGPASSWORD'] = pgpassword end # Remove "SET search_path ..." line from SQL dump and prepend search_path set to current tenant @@ -197,7 +253,7 @@ def patch_search_path(sql) swap_schema_qualifier(sql) .split("\n") - .select {|line| check_input_against_regexps(line, PSQL_DUMP_BLACKLISTED_STATEMENTS).empty?} + .select { |line| check_input_against_regexps(line, PSQL_DUMP_BLACKLISTED_STATEMENTS).empty? } .prepend(search_path) .join("\n") end @@ -207,7 +263,7 @@ def swap_schema_qualifier(sql) if Apartment.pg_excluded_names.any? { |name| match.include? name } match else - match.gsub("#{default_tenant}.", %{"#{current}".}) + match.gsub("#{default_tenant}.", %("#{current}".)) end end end @@ -215,7 +271,7 @@ def swap_schema_qualifier(sql) # Checks if any of regexps matches against input # def check_input_against_regexps(input, regexps) - regexps.select {|c| input.match c} + regexps.select { |c| input.match c } end # Collect table names from AR Models diff --git a/lib/apartment/adapters/sqlite3_adapter.rb b/lib/apartment/adapters/sqlite3_adapter.rb index dff5a21b..bfa6f3de 100644 --- a/lib/apartment/adapters/sqlite3_adapter.rb +++ b/lib/apartment/adapters/sqlite3_adapter.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'apartment/adapters/abstract_adapter' module Apartment @@ -16,8 +18,10 @@ def initialize(config) end def drop(tenant) - raise TenantNotFound, - "The tenant #{environmentify(tenant)} cannot be found." unless File.exists?(database_file(tenant)) + unless File.exist?(database_file(tenant)) + raise TenantNotFound, + "The tenant #{environmentify(tenant)} cannot be found." + end File.delete(database_file(tenant)) end @@ -26,18 +30,24 @@ def current File.basename(Apartment.connection.instance_variable_get(:@config)[:database], '.sqlite3') end - protected + protected def connect_to_new(tenant) - raise TenantNotFound, - "The tenant #{environmentify(tenant)} cannot be found." unless File.exists?(database_file(tenant)) + return reset if tenant.nil? + + unless File.exist?(database_file(tenant)) + raise TenantNotFound, + "The tenant #{environmentify(tenant)} cannot be found." + end super database_file(tenant) end def create_tenant(tenant) - raise TenantExists, - "The tenant #{environmentify(tenant)} already exists." if File.exists?(database_file(tenant)) + if File.exist?(database_file(tenant)) + raise TenantExists, + "The tenant #{environmentify(tenant)} already exists." + end begin f = File.new(database_file(tenant), File::CREAT) @@ -46,7 +56,7 @@ def create_tenant(tenant) end end - private + private def database_file(tenant) "#{@default_dir}/#{environmentify(tenant)}.sqlite3" diff --git a/lib/apartment/console.rb b/lib/apartment/console.rb index 6ce18832..91721222 100644 --- a/lib/apartment/console.rb +++ b/lib/apartment/console.rb @@ -1,12 +1,40 @@ -# A workaraound to get `reload!` to also call Apartment::Tenant.init +# frozen_string_literal: true + +# A workaround to get `reload!` to also call Apartment::Tenant.init # This is unfortunate, but I haven't figured out how to hook into the reload process *after* files are reloaded # reloads the environment -def reload!(print=true) - puts "Reloading..." if print +# rubocop:disable Style/OptionalBooleanParameter +def reload!(print = true) + puts 'Reloading...' if print + # This triggers the to_prepare callbacks - ActionDispatch::Callbacks.new(Proc.new {}).call({}) + ActionDispatch::Callbacks.new(proc {}).call({}) # Manually init Apartment again once classes are reloaded Apartment::Tenant.init true end +# rubocop:enable Style/OptionalBooleanParameter + +def st(schema_name = nil) + if schema_name.nil? + tenant_list.each { |t| puts t } + + elsif tenant_list.include? schema_name + Apartment::Tenant.switch!(schema_name) + else + puts "Tenant #{schema_name} is not part of the tenant list" + + end +end + +def tenant_list + tenant_list = [Apartment.default_tenant] + tenant_list += Apartment.tenant_names + tenant_list.uniq +end + +def tenant_info_msg + puts "Available Tenants: #{tenant_list}\n" + puts "Use `st 'tenant'` to switch tenants & `tenant_list` to see list\n" +end diff --git a/lib/apartment/custom_console.rb b/lib/apartment/custom_console.rb new file mode 100644 index 00000000..7b32a5b5 --- /dev/null +++ b/lib/apartment/custom_console.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require_relative 'console' + +module Apartment + module CustomConsole + begin + require 'pry-rails' + rescue LoadError + # rubocop:disable Layout/LineLength + puts '[Failed to load pry-rails] If you want to use Apartment custom prompt you need to add pry-rails to your gemfile' + # rubocop:enable Layout/LineLength + end + + desc = "Includes the current Rails environment and project folder name.\n" \ + '[1] [project_name][Rails.env][Apartment::Tenant.current] pry(main)>' + + prompt_procs = [ + proc { |target_self, nest_level, pry| prompt_contents(pry, target_self, nest_level, '>') }, + proc { |target_self, nest_level, pry| prompt_contents(pry, target_self, nest_level, '*') } + ] + + if Gem::Version.new(Pry::VERSION) >= Gem::Version.new('0.13') + Pry.config.prompt = Pry::Prompt.new 'ros', desc, prompt_procs + else + Pry::Prompt.add 'ros', desc, %w[> *] do |target_self, nest_level, pry, sep| + prompt_contents(pry, target_self, nest_level, sep) + end + Pry.config.prompt = Pry::Prompt[:ros][:value] + end + + Pry.config.hooks.add_hook(:when_started, 'startup message') do + tenant_info_msg + end + + def self.prompt_contents(pry, target_self, nest_level, sep) + "[#{pry.input_ring.size}] [#{PryRails::Prompt.formatted_env}][#{Apartment::Tenant.current}] " \ + "#{pry.config.prompt_name}(#{Pry.view_clip(target_self)})" \ + "#{":#{nest_level}" unless nest_level.zero?}#{sep} " + end + end +end diff --git a/lib/apartment/deprecation.rb b/lib/apartment/deprecation.rb index 0b225847..db73dd5d 100644 --- a/lib/apartment/deprecation.rb +++ b/lib/apartment/deprecation.rb @@ -1,8 +1,9 @@ +# frozen_string_literal: true + require 'active_support/deprecation' module Apartment module Deprecation - def self.warn(message) ActiveSupport::Deprecation.warn message end diff --git a/lib/apartment/elevators/domain.rb b/lib/apartment/elevators/domain.rb index 5b81bb8a..8915289a 100644 --- a/lib/apartment/elevators/domain.rb +++ b/lib/apartment/elevators/domain.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'apartment/elevators/generic' module Apartment @@ -8,14 +10,13 @@ module Elevators # eg. example.com => example # www.example.bc.ca => example # a.example.bc.ca => a - # + # # class Domain < Generic - def parse_tenant_name(request) return nil if request.host.blank? - request.host.match(/(www\.)?(?[^.]*)/)["sld"] + request.host.match(/(www\.)?(?[^.]*)/)['sld'] end end end diff --git a/lib/apartment/elevators/first_subdomain.rb b/lib/apartment/elevators/first_subdomain.rb index 71d0d8d1..20ef3e68 100644 --- a/lib/apartment/elevators/first_subdomain.rb +++ b/lib/apartment/elevators/first_subdomain.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'apartment/elevators/subdomain' module Apartment @@ -8,10 +10,9 @@ module Elevators # - example1.domain.com => example1 # - example2.something.domain.com => example2 class FirstSubdomain < Subdomain - def parse_tenant_name(request) super.split('.')[0] unless super.nil? end end end -end \ No newline at end of file +end diff --git a/lib/apartment/elevators/generic.rb b/lib/apartment/elevators/generic.rb index 21349378..a765486e 100644 --- a/lib/apartment/elevators/generic.rb +++ b/lib/apartment/elevators/generic.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'rack/request' require 'apartment/tenant' @@ -6,7 +8,6 @@ module Elevators # Provides a rack based tenant switching solution based on request # class Generic - def initialize(app, processor = nil) @app = app @processor = processor || method(:parse_tenant_name) @@ -24,8 +25,8 @@ def call(env) end end - def parse_tenant_name(request) - raise "Override" + def parse_tenant_name(_request) + raise 'Override' end end end diff --git a/lib/apartment/elevators/host.rb b/lib/apartment/elevators/host.rb index c8cbd2f8..2db8dfd0 100644 --- a/lib/apartment/elevators/host.rb +++ b/lib/apartment/elevators/host.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'apartment/elevators/generic' module Apartment @@ -16,15 +18,18 @@ def self.ignored_first_subdomains @ignored_first_subdomains ||= [] end + # rubocop:disable Style/TrivialAccessors def self.ignored_first_subdomains=(arg) @ignored_first_subdomains = arg end + # rubocop:enable Style/TrivialAccessors def parse_tenant_name(request) return nil if request.host.blank? + parts = request.host.split('.') self.class.ignored_first_subdomains.include?(parts[0]) ? parts.drop(1).join('.') : request.host end end end -end \ No newline at end of file +end diff --git a/lib/apartment/elevators/host_hash.rb b/lib/apartment/elevators/host_hash.rb index 7a0454f1..f68c10cb 100644 --- a/lib/apartment/elevators/host_hash.rb +++ b/lib/apartment/elevators/host_hash.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'apartment/elevators/generic' module Apartment @@ -12,8 +14,10 @@ def initialize(app, hash = {}, processor = nil) end def parse_tenant_name(request) - raise TenantNotFound, - "Cannot find tenant for host #{request.host}" unless @hash.has_key?(request.host) + unless @hash.key?(request.host) + raise TenantNotFound, + "Cannot find tenant for host #{request.host}" + end @hash[request.host] end diff --git a/lib/apartment/elevators/subdomain.rb b/lib/apartment/elevators/subdomain.rb index c625e29b..38604d60 100644 --- a/lib/apartment/elevators/subdomain.rb +++ b/lib/apartment/elevators/subdomain.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'apartment/elevators/generic' require 'public_suffix' @@ -11,9 +13,11 @@ def self.excluded_subdomains @excluded_subdomains ||= [] end + # rubocop:disable Style/TrivialAccessors def self.excluded_subdomains=(arg) @excluded_subdomains = arg end + # rubocop:enable Style/TrivialAccessors def parse_tenant_name(request) request_subdomain = subdomain(request.host) @@ -21,15 +25,15 @@ def parse_tenant_name(request) # If the domain acquired is set to be excluded, set the tenant to whatever is currently # next in line in the schema search path. tenant = if self.class.excluded_subdomains.include?(request_subdomain) - nil - else - request_subdomain - end + nil + else + request_subdomain + end tenant.presence end - protected + protected # *Almost* a direct ripoff of ActionDispatch::Request subdomain methods diff --git a/lib/apartment/log_subscriber.rb b/lib/apartment/log_subscriber.rb new file mode 100644 index 00000000..119acbee --- /dev/null +++ b/lib/apartment/log_subscriber.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'active_record/log_subscriber' + +module Apartment + # Custom Log subscriber to include database name and schema name in sql logs + class LogSubscriber < ActiveRecord::LogSubscriber + # NOTE: for some reason, if the method definition is not here, then the custom debug method is not called + # rubocop:disable Lint/UselessMethodDefinition + def sql(event) + super(event) + end + # rubocop:enable Lint/UselessMethodDefinition + + private + + def debug(progname = nil, &block) + progname = " #{apartment_log}#{progname}" unless progname.nil? + + super(progname, &block) + end + + def apartment_log + database = color("[#{Apartment.connection.raw_connection.db}] ", ActiveSupport::LogSubscriber::MAGENTA, true) + schema = nil + unless Apartment.connection.schema_search_path.nil? + schema = color("[#{Apartment.connection.schema_search_path.tr('"', '')}] ", + ActiveSupport::LogSubscriber::YELLOW, true) + end + "#{database}#{schema}" + end + end +end diff --git a/lib/apartment/migrator.rb b/lib/apartment/migrator.rb index c99e9a3b..089109df 100644 --- a/lib/apartment/migrator.rb +++ b/lib/apartment/migrator.rb @@ -1,16 +1,17 @@ +# frozen_string_literal: true + require 'apartment/tenant' module Apartment module Migrator - extend self # Migrate to latest def migrate(database) Tenant.switch(database) do - version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil + version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil - migration_scope_block = -> (migration) { ENV["SCOPE"].blank? || (ENV["SCOPE"] == migration.scope) } + migration_scope_block = ->(migration) { ENV['SCOPE'].blank? || (ENV['SCOPE'] == migration.scope) } if activerecord_below_5_2? ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, version, &migration_scope_block) diff --git a/lib/apartment/model.rb b/lib/apartment/model.rb new file mode 100644 index 00000000..5a75a140 --- /dev/null +++ b/lib/apartment/model.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Apartment + module Model + extend ActiveSupport::Concern + + module ClassMethods + # NOTE: key can either be an array of symbols or a single value. + # E.g. If we run the following query: + # `Setting.find_by(key: 'something', value: 'amazing')` key will have an array of symbols: `[:key, :something]` + # while if we run: + # `Setting.find(10)` key will have the value 'id' + def cached_find_by_statement(key, &block) + # Modifying the cache key to have a reference to the current tenant, + # so the cached statement is referring only to the tenant in which we've + # executed this + cache_key = if key.is_a? String + "#{Apartment::Tenant.current}_#{key}" + else + [Apartment::Tenant.current] + key + end + cache = @find_by_statement_cache[connection.prepared_statements] + cache.compute_if_absent(cache_key) { ActiveRecord::StatementCache.create(connection, &block) } + end + end + end +end diff --git a/lib/apartment/railtie.rb b/lib/apartment/railtie.rb index 6a18833f..649c6fcd 100644 --- a/lib/apartment/railtie.rb +++ b/lib/apartment/railtie.rb @@ -1,10 +1,11 @@ +# frozen_string_literal: true + require 'rails' require 'apartment/tenant' require 'apartment/reloader' module Apartment class Railtie < Rails::Railtie - # # Set up our default config options # Do this before the app initializers run so we don't override custom settings @@ -17,17 +18,22 @@ class Railtie < Rails::Railtie config.seed_after_create = false config.prepend_environment = false config.append_environment = false + config.tenant_presence_check = true + config.active_record_log = false end ActiveRecord::Migrator.migrations_paths = Rails.application.paths['db/migrate'].to_a end # Hook into ActionDispatch::Reloader to ensure Apartment is properly initialized - # Note that this doens't entirely work as expected in Development, because this is called before classes are reloaded + # Note that this doesn't entirely work as expected in Development, + # because this is called before classes are reloaded # See the middleware/console declarations below to help with this. Hope to fix that soon. # config.to_prepare do next if ARGV.any? { |arg| arg =~ /\Aassets:(?:precompile|clean)\z/ } + next if ARGV.any? { |arg| arg == 'webpacker:compile' } + next if ENV['APARTMENT_DISABLE_INIT'] begin Apartment.connection_class.connection_pool.with_connection do @@ -39,6 +45,14 @@ class Railtie < Rails::Railtie end end + config.after_initialize do + # NOTE: Load the custom log subscriber if enabled + if Apartment.active_record_log + ActiveSupport::Notifications.unsubscribe 'sql.active_record' + Apartment::LogSubscriber.attach_to :active_record + end + end + # # Ensure rake tasks are loaded # @@ -58,10 +72,13 @@ class Railtie < Rails::Railtie app.config.middleware.use Apartment::Reloader end - # Overrides reload! to also call Apartment::Tenant.init as well so that the reloaded classes have the proper table_names + # Overrides reload! to also call Apartment::Tenant.init as well + # so that the reloaded classes have the proper table_names + # rubocop:disable Lint/Debugger console do require 'apartment/console' end + # rubocop:enable Lint/Debugger end end end diff --git a/lib/apartment/reloader.rb b/lib/apartment/reloader.rb index fd315371..cd8b6861 100644 --- a/lib/apartment/reloader.rb +++ b/lib/apartment/reloader.rb @@ -1,6 +1,7 @@ +# frozen_string_literal: true + module Apartment class Reloader - # Middleware used in development to init Apartment for each request # Necessary due to code reload (annoying). When models are reloaded, they no longer have the proper table_name # That is prepended with the schema (if using postgresql schemas) diff --git a/lib/apartment/tasks/enhancements.rb b/lib/apartment/tasks/enhancements.rb index ce93e58a..f9a13206 100644 --- a/lib/apartment/tasks/enhancements.rb +++ b/lib/apartment/tasks/enhancements.rb @@ -1,12 +1,13 @@ +# frozen_string_literal: true + # Require this file to append Apartment rake tasks to ActiveRecord db rake tasks # Enabled by default in the initializer module Apartment class RakeTaskEnhancer - module TASKS - ENHANCE_BEFORE = %w(db:drop) - ENHANCE_AFTER = %w(db:migrate db:rollback db:migrate:up db:migrate:down db:migrate:redo db:seed) + ENHANCE_BEFORE = %w[db:drop].freeze + ENHANCE_AFTER = %w[db:migrate db:rollback db:migrate:up db:migrate:down db:migrate:redo db:seed].freeze freeze end @@ -28,7 +29,6 @@ def enhance! task = Rake::Task[name] enhance_after_task(task) end - end def should_enhance? @@ -48,9 +48,7 @@ def enhance_after_task(task) def inserted_task_name(task) task.name.sub(/db:/, 'apartment:') end - end - end end diff --git a/lib/apartment/tasks/task_helper.rb b/lib/apartment/tasks/task_helper.rb new file mode 100644 index 00000000..32cd908e --- /dev/null +++ b/lib/apartment/tasks/task_helper.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module Apartment + module TaskHelper + def self.each_tenant(&block) + Parallel.each(tenants_without_default, in_threads: Apartment.parallel_migration_threads) do |tenant| + block.call(tenant) + end + end + + def self.tenants_without_default + tenants - [Apartment.default_tenant] + end + + def self.tenants + ENV['DB'] ? ENV['DB'].split(',').map(&:strip) : Apartment.tenant_names || [] + end + + def self.warn_if_tenants_empty + return unless tenants.empty? && ENV['IGNORE_EMPTY_TENANTS'] != 'true' + + puts <<-WARNING + [WARNING] - The list of tenants to migrate appears to be empty. This could mean a few things: + + 1. You may not have created any, in which case you can ignore this message + 2. You've run `apartment:migrate` directly without loading the Rails environment + * `apartment:migrate` is now deprecated. Tenants will automatically be migrated with `db:migrate` + + Note that your tenants currently haven't been migrated. You'll need to run `db:migrate` to rectify this. + WARNING + end + + def self.create_tenant(tenant_name) + puts("Creating #{tenant_name} tenant") + Apartment::Tenant.create(tenant_name) + rescue Apartment::TenantExists => e + puts "Tried to create already existing tenant: #{e}" + end + + def self.migrate_tenant(tenant_name) + strategy = Apartment.db_migrate_tenant_missing_strategy + create_tenant(tenant_name) if strategy == :create_tenant + + puts("Migrating #{tenant_name} tenant") + Apartment::Migrator.migrate tenant_name + rescue Apartment::TenantNotFound => e + raise e if strategy == :raise_exception + + puts e.message + end + end +end diff --git a/lib/apartment/tenant.rb b/lib/apartment/tenant.rb index acabad31..abbe87d5 100644 --- a/lib/apartment/tenant.rb +++ b/lib/apartment/tenant.rb @@ -1,23 +1,19 @@ +# frozen_string_literal: true + require 'forwardable' module Apartment # The main entry point to Apartment functions # module Tenant - extend self extend Forwardable - def_delegators :adapter, :create, :drop, :switch, :switch!, :current, :each, :reset, :set_callback, :seed, :current_tenant, :default_tenant, :environmentify + def_delegators :adapter, :create, :drop, :switch, :switch!, :current, :each, + :reset, :init, :set_callback, :seed, :default_tenant, :environmentify attr_writer :config - # Initialize Apartment config options such as excluded_models - # - def init - adapter.process_excluded_models - end - # Fetch the proper multi-tenant adapter based on Rails config # # @return {subclass of Apartment::AbstractAdapter} @@ -27,9 +23,10 @@ def adapter adapter_method = "#{config[:adapter]}_adapter" if defined?(JRUBY_VERSION) - if config[:adapter] =~ /mysql/ + case config[:adapter] + when /mysql/ adapter_method = 'jdbc_mysql_adapter' - elsif config[:adapter] =~ /postgresql/ + when /postgresql/ adapter_method = 'jdbc_postgresql_adapter' end end diff --git a/lib/apartment/version.rb b/lib/apartment/version.rb index 40b2f751..7ec53121 100644 --- a/lib/apartment/version.rb +++ b/lib/apartment/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Apartment - VERSION = "2.2.1" + VERSION = '2.9.0' end diff --git a/lib/generators/apartment/install/install_generator.rb b/lib/generators/apartment/install/install_generator.rb index eb7cbe66..9fe7152e 100755 --- a/lib/generators/apartment/install/install_generator.rb +++ b/lib/generators/apartment/install/install_generator.rb @@ -1,10 +1,11 @@ +# frozen_string_literal: true + module Apartment class InstallGenerator < Rails::Generators::Base - source_root File.expand_path('../templates', __FILE__) + source_root File.expand_path('templates', __dir__) def copy_files - template "apartment.rb", File.join("config", "initializers", "apartment.rb") + template 'apartment.rb', File.join('config', 'initializers', 'apartment.rb') end - end end diff --git a/lib/generators/apartment/install/templates/apartment.rb b/lib/generators/apartment/install/templates/apartment.rb index 6c7c52fb..17f73c60 100644 --- a/lib/generators/apartment/install/templates/apartment.rb +++ b/lib/generators/apartment/install/templates/apartment.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # You can have Apartment route to the appropriate Tenant by adding some Rack middleware. # Apartment can support many different "Elevators" that can take care of this routing to your data. # Require whichever Elevator you're using below or none if you have a custom one. @@ -12,7 +14,6 @@ # Apartment Configuration # Apartment.configure do |config| - # Add any models that you do not want to be multi-tenanted, but remain in the global (public) namespace. # A typical example would be a Customer or Tenant model that stores each Tenant's information. # @@ -22,7 +23,8 @@ # You can make this dynamic by providing a Proc object to be called on migrations. # This object should yield either: # - an array of strings representing each Tenant name. - # - a hash which keys are tenant names, and values custom db config (must contain all key/values required in database.yml) + # - a hash which keys are tenant names, and values custom db config + # (must contain all key/values required in database.yml) # # config.tenant_names = lambda{ Customer.pluck(:tenant_name) } # config.tenant_names = ['tenant1', 'tenant2'] @@ -48,7 +50,7 @@ # end # end # - config.tenant_names = lambda { ToDo_Tenant_Or_User_Model.pluck :database } + config.tenant_names = -> { ToDo_Tenant_Or_User_Model.pluck :database } # PostgreSQL: # Specifies whether to use PostgreSQL schemas or create a new database per Tenant. @@ -95,6 +97,11 @@ # the new tenant # # config.pg_excluded_names = ["uuid_generate_v4"] + + # Specifies whether the database and schema (when using PostgreSQL schemas) will prepend in ActiveRecord log. + # Uncomment the line below if you want to enable this behavior. + # + # config.active_record_log = true end # Setup a custom Tenant switching middleware. The Proc should return the name of the Tenant that diff --git a/lib/tasks/apartment.rake b/lib/tasks/apartment.rake index bccd9dd2..5ae47c0f 100644 --- a/lib/tasks/apartment.rake +++ b/lib/tasks/apartment.rake @@ -1,51 +1,46 @@ +# frozen_string_literal: true + require 'apartment/migrator' +require 'apartment/tasks/task_helper' require 'parallel' apartment_namespace = namespace :apartment do - - desc "Create all tenants" + desc 'Create all tenants' task :create do - tenants.each do |tenant| - begin - puts("Creating #{tenant} tenant") - Apartment::Tenant.create(tenant) - rescue Apartment::TenantExists => e - puts e.message - end + Apartment::TaskHelper.warn_if_tenants_empty + + Apartment::TaskHelper.tenants.each do |tenant| + Apartment::TaskHelper.create_tenant(tenant) end end - desc "Drop all tenants" + desc 'Drop all tenants' task :drop do - tenants.each do |tenant| + Apartment::TaskHelper.tenants.each do |tenant| begin puts("Dropping #{tenant} tenant") Apartment::Tenant.drop(tenant) - rescue Apartment::TenantNotFound => e + rescue Apartment::TenantNotFound, ActiveRecord::NoDatabaseError => e puts e.message end end end - desc "Migrate all tenants" + desc 'Migrate all tenants' task :migrate do - warn_if_tenants_empty - each_tenant do |tenant| - begin - puts("Migrating #{tenant} tenant") - Apartment::Migrator.migrate tenant - rescue Apartment::TenantNotFound => e - puts e.message - end + Apartment::TaskHelper.warn_if_tenants_empty + Apartment::TaskHelper.each_tenant do |tenant| + Apartment::TaskHelper.migrate_tenant(tenant) end end - desc "Seed all tenants" + desc 'Seed all tenants' task :seed do - warn_if_tenants_empty + Apartment::TaskHelper.warn_if_tenants_empty - each_tenant do |tenant| + Apartment::TaskHelper.each_tenant do |tenant| begin + Apartment::TaskHelper.create_tenant(tenant) puts("Seeding #{tenant} tenant") Apartment::Tenant.switch(tenant) do Apartment::Tenant.seed @@ -56,13 +51,13 @@ apartment_namespace = namespace :apartment do end end - desc "Rolls the migration back to the previous version (specify steps w/ STEP=n) across all tenants." + desc 'Rolls the migration back to the previous version (specify steps w/ STEP=n) across all tenants.' task :rollback do - warn_if_tenants_empty + Apartment::TaskHelper.warn_if_tenants_empty step = ENV['STEP'] ? ENV['STEP'].to_i : 1 - each_tenant do |tenant| + Apartment::TaskHelper.each_tenant do |tenant| begin puts("Rolling back #{tenant} tenant") Apartment::Migrator.rollback tenant, step @@ -75,12 +70,12 @@ apartment_namespace = namespace :apartment do namespace :migrate do desc 'Runs the "up" for a given migration VERSION across all tenants.' task :up do - warn_if_tenants_empty + Apartment::TaskHelper.warn_if_tenants_empty version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil raise 'VERSION is required' unless version - each_tenant do |tenant| + Apartment::TaskHelper.each_tenant do |tenant| begin puts("Migrating #{tenant} tenant up") Apartment::Migrator.run :up, tenant, version @@ -92,12 +87,12 @@ apartment_namespace = namespace :apartment do desc 'Runs the "down" for a given migration VERSION across all tenants.' task :down do - warn_if_tenants_empty + Apartment::TaskHelper.warn_if_tenants_empty version = ENV['VERSION'] ? ENV['VERSION'].to_i : nil raise 'VERSION is required' unless version - each_tenant do |tenant| + Apartment::TaskHelper.each_tenant do |tenant| begin puts("Migrating #{tenant} tenant down") Apartment::Migrator.run :down, tenant, version @@ -107,7 +102,7 @@ apartment_namespace = namespace :apartment do end end - desc 'Rolls back the tenant one migration and re migrate up (options: STEP=x, VERSION=x).' + desc 'Rolls back the tenant one migration and re migrate up (options: STEP=x, VERSION=x).' task :redo do if ENV['VERSION'] apartment_namespace['migrate:down'].invoke @@ -118,28 +113,4 @@ apartment_namespace = namespace :apartment do end end end - - def each_tenant(&block) - Parallel.each(tenants, in_threads: Apartment.parallel_migration_threads) do |tenant| - block.call(tenant) - end - end - - def tenants - ENV['DB'] ? ENV['DB'].split(',').map { |s| s.strip } : Apartment.tenant_names || [] - end - - def warn_if_tenants_empty - if tenants.empty? && ENV['IGNORE_EMPTY_TENANTS'] != "true" - puts <<-WARNING - [WARNING] - The list of tenants to migrate appears to be empty. This could mean a few things: - - 1. You may not have created any, in which case you can ignore this message - 2. You've run `apartment:migrate` directly without loading the Rails environment - * `apartment:migrate` is now deprecated. Tenants will automatically be migrated with `db:migrate` - - Note that your tenants currently haven't been migrated. You'll need to run `db:migrate` to rectify this. - WARNING - end - end end diff --git a/ros-apartment.gemspec b/ros-apartment.gemspec new file mode 100644 index 00000000..95a60c35 --- /dev/null +++ b/ros-apartment.gemspec @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +$LOAD_PATH << File.expand_path('lib', __dir__) +require 'apartment/version' + +Gem::Specification.new do |s| + s.name = 'ros-apartment' + s.version = Apartment::VERSION + + s.authors = ['Ryan Brunner', 'Brad Robertson', 'Rui Baltazar'] + s.summary = 'A Ruby gem for managing database multitenancy. Apartment Gem drop in replacement' + s.description = 'Apartment allows Rack applications to deal with database multitenancy through ActiveRecord' + s.email = ['ryan@influitive.com', 'brad@influitive.com', 'rui.p.baltazar@gmail.com'] + # Specify which files should be added to the gem when it is released. + # The `git ls-files -z` loads the files in the RubyGem that have been + # added into git. + s.files = Dir.chdir(File.expand_path(__dir__)) do + `git ls-files -z`.split("\x0").reject do |f| + # NOTE: ignore all test related + f.match(%r{^(test|spec|features|documentation)/}) + end + end + s.executables = s.files.grep(%r{^bin/}).map { |f| File.basename(f) } + s.test_files = s.files.grep(%r{^(test|spec|features)/}) + s.require_paths = ['lib'] + + s.homepage = 'https://github.com/rails-on-services/apartment' + s.licenses = ['MIT'] + + s.add_dependency 'activerecord', '>= 5.0.0', '< 6.2' + s.add_dependency 'parallel', '< 2.0' + s.add_dependency 'public_suffix', '>= 2.0.5', '< 5.0' + s.add_dependency 'rack', '>= 1.3.6', '< 3.0' + + s.add_development_dependency 'appraisal', '~> 2.2' + s.add_development_dependency 'bundler', '>= 1.3', '< 3.0' + s.add_development_dependency 'capybara', '~> 2.0' + s.add_development_dependency 'rake', '~> 13.0' + s.add_development_dependency 'rspec', '~> 3.4' + s.add_development_dependency 'rspec-rails', '~> 3.4' + + if defined?(JRUBY_VERSION) + s.add_development_dependency 'activerecord-jdbc-adapter' + s.add_development_dependency 'activerecord-jdbcmysql-adapter' + s.add_development_dependency 'activerecord-jdbcpostgresql-adapter' + s.add_development_dependency 'jdbc-mysql' + s.add_development_dependency 'jdbc-postgres' + else + s.add_development_dependency 'mysql2', '~> 0.5' + s.add_development_dependency 'pg', '~> 1.2' + s.add_development_dependency 'sqlite3', '~> 1.3.6' + end +end diff --git a/spec/adapters/jdbc_mysql_adapter_spec.rb b/spec/adapters/jdbc_mysql_adapter_spec.rb index 1fee3e14..2d0fb975 100644 --- a/spec/adapters/jdbc_mysql_adapter_spec.rb +++ b/spec/adapters/jdbc_mysql_adapter_spec.rb @@ -1,19 +1,23 @@ +# frozen_string_literal: true + if defined?(JRUBY_VERSION) require 'spec_helper' require 'apartment/adapters/jdbc_mysql_adapter' describe Apartment::Adapters::JDBCMysqlAdapter, database: :mysql do - subject { Apartment::Tenant.jdbc_mysql_adapter config.symbolize_keys } def tenant_names - ActiveRecord::Base.connection.execute("SELECT schema_name FROM information_schema.schemata").collect { |row| row['schema_name'] } + ActiveRecord::Base.connection.execute('SELECT schema_name FROM information_schema.schemata').collect do |row| + row['schema_name'] + end end let(:default_tenant) { subject.switch { ActiveRecord::Base.connection.current_database } } - it_should_behave_like "a generic apartment adapter" - it_should_behave_like "a connection based apartment adapter" + it_should_behave_like 'a generic apartment adapter callbacks' + it_should_behave_like 'a generic apartment adapter' + it_should_behave_like 'a connection based apartment adapter' end end diff --git a/spec/adapters/jdbc_postgresql_adapter_spec.rb b/spec/adapters/jdbc_postgresql_adapter_spec.rb index 28cf135f..67d3c981 100644 --- a/spec/adapters/jdbc_postgresql_adapter_spec.rb +++ b/spec/adapters/jdbc_postgresql_adapter_spec.rb @@ -1,41 +1,41 @@ +# frozen_string_literal: true + if defined?(JRUBY_VERSION) require 'spec_helper' require 'apartment/adapters/jdbc_postgresql_adapter' describe Apartment::Adapters::JDBCPostgresqlAdapter, database: :postgresql do - subject { Apartment::Tenant.jdbc_postgresql_adapter config.symbolize_keys } - context "using schemas" do + it_should_behave_like 'a generic apartment adapter callbacks' + context 'using schemas' do before { Apartment.use_schemas = true } # Not sure why, but somehow using let(:tenant_names) memoizes for the whole example group, not just each test def tenant_names - ActiveRecord::Base.connection.execute("SELECT nspname FROM pg_namespace;").collect { |row| row['nspname'] } + ActiveRecord::Base.connection.execute('SELECT nspname FROM pg_namespace;').collect { |row| row['nspname'] } end let(:default_tenant) { subject.switch { ActiveRecord::Base.connection.schema_search_path.gsub('"', '') } } - it_should_behave_like "a generic apartment adapter" - it_should_behave_like "a schema based apartment adapter" + it_should_behave_like 'a generic apartment adapter' + it_should_behave_like 'a schema based apartment adapter' end - context "using databases" do - + context 'using databases' do before { Apartment.use_schemas = false } # Not sure why, but somehow using let(:tenant_names) memoizes for the whole example group, not just each test def tenant_names - connection.execute("select datname from pg_database;").collect { |row| row['datname'] } + connection.execute('select datname from pg_database;').collect { |row| row['datname'] } end let(:default_tenant) { subject.switch { ActiveRecord::Base.connection.current_database } } - it_should_behave_like "a generic apartment adapter" - it_should_behave_like "a connection based apartment adapter" - + it_should_behave_like 'a generic apartment adapter' + it_should_behave_like 'a connection based apartment adapter' end end end diff --git a/spec/adapters/mysql2_adapter_spec.rb b/spec/adapters/mysql2_adapter_spec.rb index 78f7b589..505b7d6b 100644 --- a/spec/adapters/mysql2_adapter_spec.rb +++ b/spec/adapters/mysql2_adapter_spec.rb @@ -1,34 +1,40 @@ +# frozen_string_literal: true + require 'spec_helper' require 'apartment/adapters/mysql2_adapter' describe Apartment::Adapters::Mysql2Adapter, database: :mysql do unless defined?(JRUBY_VERSION) - subject(:adapter){ Apartment::Tenant.mysql2_adapter config } + subject(:adapter) { Apartment::Tenant.mysql2_adapter config } def tenant_names - ActiveRecord::Base.connection.execute("SELECT schema_name FROM information_schema.schemata").collect { |row| row[0] } + ActiveRecord::Base.connection.execute('SELECT schema_name FROM information_schema.schemata').collect do |row| + row[0] + end end let(:default_tenant) { subject.switch { ActiveRecord::Base.connection.current_database } } - context "using - the equivalent of - schemas" do + it_should_behave_like 'a generic apartment adapter callbacks' + + context 'using - the equivalent of - schemas' do before { Apartment.use_schemas = true } - it_should_behave_like "a generic apartment adapter" + it_should_behave_like 'a generic apartment adapter' - describe "#default_tenant" do - it "is set to the original db from config" do + describe '#default_tenant' do + it 'is set to the original db from config' do expect(subject.default_tenant).to eq(config[:database]) end end - describe "#init" do + describe '#init' do include Apartment::Spec::AdapterRequirements before do Apartment.configure do |config| - config.excluded_models = ["Company"] + config.excluded_models = ['Company'] end end @@ -40,7 +46,7 @@ def tenant_names end end - it "should process model exclusions" do + it 'should process model exclusions' do Apartment::Tenant.init expect(Company.table_name).to eq("#{default_tenant}.companies") @@ -48,12 +54,12 @@ def tenant_names end end - context "using connections" do + context 'using connections' do before { Apartment.use_schemas = false } - it_should_behave_like "a generic apartment adapter" - it_should_behave_like "a generic apartment adapter able to handle custom configuration" - it_should_behave_like "a connection based apartment adapter" + it_should_behave_like 'a generic apartment adapter' + it_should_behave_like 'a generic apartment adapter able to handle custom configuration' + it_should_behave_like 'a connection based apartment adapter' end end end diff --git a/spec/adapters/postgresql_adapter_spec.rb b/spec/adapters/postgresql_adapter_spec.rb index e246fd10..d7689d26 100644 --- a/spec/adapters/postgresql_adapter_spec.rb +++ b/spec/adapters/postgresql_adapter_spec.rb @@ -1,61 +1,67 @@ +# frozen_string_literal: true + require 'spec_helper' require 'apartment/adapters/postgresql_adapter' describe Apartment::Adapters::PostgresqlAdapter, database: :postgresql do unless defined?(JRUBY_VERSION) - subject{ Apartment::Tenant.postgresql_adapter config } + subject { Apartment::Tenant.postgresql_adapter config } - context "using schemas with schema.rb" do + it_should_behave_like 'a generic apartment adapter callbacks' - before{ Apartment.use_schemas = true } + context 'using schemas with schema.rb' do + before { Apartment.use_schemas = true } # Not sure why, but somehow using let(:tenant_names) memoizes for the whole example group, not just each test def tenant_names - ActiveRecord::Base.connection.execute("SELECT nspname FROM pg_namespace;").collect { |row| row['nspname'] } + ActiveRecord::Base.connection.execute('SELECT nspname FROM pg_namespace;').collect { |row| row['nspname'] } end let(:default_tenant) { subject.switch { ActiveRecord::Base.connection.schema_search_path.gsub('"', '') } } - it_should_behave_like "a generic apartment adapter" - it_should_behave_like "a schema based apartment adapter" + it_should_behave_like 'a generic apartment adapter' + it_should_behave_like 'a schema based apartment adapter' end - context "using schemas with SQL dump" do - - before{ Apartment.use_schemas = true; Apartment.use_sql = true } + context 'using schemas with SQL dump' do + before do + Apartment.use_schemas = true + Apartment.use_sql = true + end # Not sure why, but somehow using let(:tenant_names) memoizes for the whole example group, not just each test def tenant_names - ActiveRecord::Base.connection.execute("SELECT nspname FROM pg_namespace;").collect { |row| row['nspname'] } + ActiveRecord::Base.connection.execute('SELECT nspname FROM pg_namespace;').collect { |row| row['nspname'] } end let(:default_tenant) { subject.switch { ActiveRecord::Base.connection.schema_search_path.gsub('"', '') } } - it_should_behave_like "a generic apartment adapter" - it_should_behave_like "a schema based apartment adapter" + it_should_behave_like 'a generic apartment adapter' + it_should_behave_like 'a schema based apartment adapter' it 'allows for dashes in the schema name' do expect { Apartment::Tenant.create('has-dashes') }.to_not raise_error end - after { Apartment::Tenant.drop('has-dashes') if Apartment.connection.schema_exists? 'has-dashes' } + after do + Apartment::Tenant.drop('has-dashes') if Apartment.connection.schema_exists? 'has-dashes' + end end - context "using connections" do - - before{ Apartment.use_schemas = false } + context 'using connections' do + before { Apartment.use_schemas = false } # Not sure why, but somehow using let(:tenant_names) memoizes for the whole example group, not just each test def tenant_names - connection.execute("select datname from pg_database;").collect { |row| row['datname'] } + connection.execute('select datname from pg_database;').collect { |row| row['datname'] } end let(:default_tenant) { subject.switch { ActiveRecord::Base.connection.current_database } } - it_should_behave_like "a generic apartment adapter" - it_should_behave_like "a generic apartment adapter able to handle custom configuration" - it_should_behave_like "a connection based apartment adapter" + it_should_behave_like 'a generic apartment adapter' + it_should_behave_like 'a generic apartment adapter able to handle custom configuration' + it_should_behave_like 'a connection based apartment adapter' end end end diff --git a/spec/adapters/sqlite3_adapter_spec.rb b/spec/adapters/sqlite3_adapter_spec.rb index 55004952..f05c76ba 100644 --- a/spec/adapters/sqlite3_adapter_spec.rb +++ b/spec/adapters/sqlite3_adapter_spec.rb @@ -1,14 +1,18 @@ +# frozen_string_literal: true + require 'spec_helper' require 'apartment/adapters/sqlite3_adapter' describe Apartment::Adapters::Sqlite3Adapter, database: :sqlite do unless defined?(JRUBY_VERSION) - subject{ Apartment::Tenant.sqlite3_adapter config } + subject { Apartment::Tenant.sqlite3_adapter config } + + it_should_behave_like 'a generic apartment adapter callbacks' - context "using connections" do + context 'using connections' do def tenant_names - db_dir = File.expand_path("../../dummy/db", __FILE__) + db_dir = File.expand_path('../dummy/db', __dir__) Dir.glob("#{db_dir}/*.sqlite3").map { |file| File.basename(file, '.sqlite3') } end @@ -16,18 +20,18 @@ def tenant_names subject.switch { File.basename(Apartment::Test.config['connections']['sqlite']['database'], '.sqlite3') } end - it_should_behave_like "a generic apartment adapter" - it_should_behave_like "a connection based apartment adapter" + it_should_behave_like 'a generic apartment adapter' + it_should_behave_like 'a connection based apartment adapter' after(:all) do File.delete(Apartment::Test.config['connections']['sqlite']['database']) end end - context "with prepend and append" do + context 'with prepend and append' do let(:default_dir) { File.expand_path(File.dirname(config[:database])) } - describe "#prepend" do - let (:db_name) { "db_with_prefix" } + describe '#prepend' do + let(:db_name) { 'db_with_prefix' } before do Apartment.configure do |config| config.prepend_environment = true @@ -35,32 +39,44 @@ def tenant_names end end - after { subject.drop db_name rescue nil } + after do + begin + subject.drop db_name + rescue StandardError => _e + nil + end + end - it "should create a new database" do + it 'should create a new database' do subject.create db_name - expect(File.exists?("#{default_dir}/#{Rails.env}_#{db_name}.sqlite3")).to eq true + expect(File.exist?("#{default_dir}/#{Rails.env}_#{db_name}.sqlite3")).to eq true end end - describe "#neither" do - let (:db_name) { "db_without_prefix_suffix" } + describe '#neither' do + let(:db_name) { 'db_without_prefix_suffix' } before do Apartment.configure { |config| config.prepend_environment = config.append_environment = false } end - after { subject.drop db_name rescue nil } + after do + begin + subject.drop db_name + rescue StandardError => _e + nil + end + end - it "should create a new database" do + it 'should create a new database' do subject.create db_name - expect(File.exists?("#{default_dir}/#{db_name}.sqlite3")).to eq true + expect(File.exist?("#{default_dir}/#{db_name}.sqlite3")).to eq true end end - describe "#append" do - let (:db_name) { "db_with_suffix" } + describe '#append' do + let(:db_name) { 'db_with_suffix' } before do Apartment.configure do |config| config.prepend_environment = false @@ -68,16 +84,20 @@ def tenant_names end end - after { subject.drop db_name rescue nil } + after do + begin + subject.drop db_name + rescue StandardError => _e + nil + end + end - it "should create a new database" do + it 'should create a new database' do subject.create db_name - expect(File.exists?("#{default_dir}/#{db_name}_#{Rails.env}.sqlite3")).to eq true + expect(File.exist?("#{default_dir}/#{db_name}_#{Rails.env}.sqlite3")).to eq true end end - end - end end diff --git a/spec/apartment_spec.rb b/spec/apartment_spec.rb index b00e0b2d..f90a09f2 100644 --- a/spec/apartment_spec.rb +++ b/spec/apartment_spec.rb @@ -1,11 +1,13 @@ +# frozen_string_literal: true + require 'spec_helper' describe Apartment do - it "should be valid" do + it 'should be valid' do expect(Apartment).to be_a(Module) end - it "should be a valid app" do + it 'should be a valid app' do expect(::Rails.application).to be_a(Dummy::Application) end end diff --git a/spec/dummy/Rakefile b/spec/dummy/Rakefile index 9724472e..d189df36 100644 --- a/spec/dummy/Rakefile +++ b/spec/dummy/Rakefile @@ -1,7 +1,9 @@ +# frozen_string_literal: true + # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. -require File.expand_path('../config/application', __FILE__) +require File.expand_path('config/application', __dir__) require 'rake' Dummy::Application.load_tasks diff --git a/spec/dummy/app/controllers/application_controller.rb b/spec/dummy/app/controllers/application_controller.rb index 4c655b70..3615e013 100644 --- a/spec/dummy/app/controllers/application_controller.rb +++ b/spec/dummy/app/controllers/application_controller.rb @@ -1,6 +1,7 @@ +# frozen_string_literal: true + class ApplicationController < ActionController::Base protect_from_forgery - - def index - end + + def index; end end diff --git a/spec/dummy/app/helpers/application_helper.rb b/spec/dummy/app/helpers/application_helper.rb index de6be794..15b06f0f 100644 --- a/spec/dummy/app/helpers/application_helper.rb +++ b/spec/dummy/app/helpers/application_helper.rb @@ -1,2 +1,4 @@ +# frozen_string_literal: true + module ApplicationHelper end diff --git a/spec/dummy/app/models/application_record.rb b/spec/dummy/app/models/application_record.rb new file mode 100644 index 00000000..1d405ba4 --- /dev/null +++ b/spec/dummy/app/models/application_record.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +# NOTE: Dummy model base +class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true +end diff --git a/spec/dummy/app/models/company.rb b/spec/dummy/app/models/company.rb index dd5bf8c7..96986b7d 100644 --- a/spec/dummy/app/models/company.rb +++ b/spec/dummy/app/models/company.rb @@ -1,3 +1,5 @@ -class Company < ActiveRecord::Base +# frozen_string_literal: true + +class Company < ApplicationRecord # Dummy models -end \ No newline at end of file +end diff --git a/spec/dummy/app/models/user.rb b/spec/dummy/app/models/user.rb index 145f27f9..463ce352 100644 --- a/spec/dummy/app/models/user.rb +++ b/spec/dummy/app/models/user.rb @@ -1,3 +1,5 @@ -class User < ActiveRecord::Base +# frozen_string_literal: true + +class User < ApplicationRecord # Dummy models -end \ No newline at end of file +end diff --git a/spec/dummy/app/models/user_with_tenant_model.rb b/spec/dummy/app/models/user_with_tenant_model.rb new file mode 100644 index 00000000..9871d748 --- /dev/null +++ b/spec/dummy/app/models/user_with_tenant_model.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'apartment/model' + +class UserWithTenantModel < ApplicationRecord + include Apartment::Model + + self.table_name = 'users' + # Dummy models +end diff --git a/spec/dummy/config.ru b/spec/dummy/config.ru index 1989ed8d..4f079dd4 100644 --- a/spec/dummy/config.ru +++ b/spec/dummy/config.ru @@ -1,4 +1,6 @@ +# frozen_string_literal: true + # This file is used by Rack-based servers to start the application. -require ::File.expand_path('../config/environment', __FILE__) +require ::File.expand_path('config/environment', __dir__) run Dummy::Application diff --git a/spec/dummy/config/application.rb b/spec/dummy/config/application.rb index e8f51ed9..49b5d459 100644 --- a/spec/dummy/config/application.rb +++ b/spec/dummy/config/application.rb @@ -1,13 +1,15 @@ -require File.expand_path('../boot', __FILE__) +# frozen_string_literal: true -require "active_model/railtie" -require "active_record/railtie" -require "action_controller/railtie" -require "action_view/railtie" -require "action_mailer/railtie" +require File.expand_path('boot', __dir__) + +require 'active_model/railtie' +require 'active_record/railtie' +require 'action_controller/railtie' +require 'action_view/railtie' +require 'action_mailer/railtie' Bundler.require -require "apartment" +require 'apartment' module Dummy class Application < Rails::Application @@ -20,7 +22,7 @@ class Application < Rails::Application config.middleware.use Apartment::Elevators::Subdomain # Custom directories with classes and modules you want to be autoloadable. - config.autoload_paths += %W(#{config.root}/lib) + config.autoload_paths += %W[#{config.root}/lib] # Only load the plugins named here, in the order given (default is alphabetical). # :all can be used as a placeholder for all plugins not explicitly named. @@ -41,7 +43,7 @@ class Application < Rails::Application # config.action_view.javascript_expansions[:defaults] = %w(jquery rails) # Configure the default encoding used in templates for Ruby 1.9. - config.encoding = "utf-8" + config.encoding = 'utf-8' # Configure sensitive parameters which will be filtered from the log file. config.filter_parameters += [:password] diff --git a/spec/dummy/config/boot.rb b/spec/dummy/config/boot.rb index 490d7917..0c68bf2c 100644 --- a/spec/dummy/config/boot.rb +++ b/spec/dummy/config/boot.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + require 'rubygems' -gemfile = File.expand_path('../../../../Gemfile', __FILE__) +gemfile = File.expand_path('../../../Gemfile', __dir__) if File.exist?(gemfile) ENV['BUNDLE_GEMFILE'] = gemfile @@ -8,4 +10,4 @@ Bundler.setup end -$:.unshift File.expand_path('../../../../lib', __FILE__) \ No newline at end of file +$LOAD_PATH.unshift File.expand_path('../../../lib', __dir__) diff --git a/spec/dummy/config/environment.rb b/spec/dummy/config/environment.rb index 3da5eb91..65c03fc7 100644 --- a/spec/dummy/config/environment.rb +++ b/spec/dummy/config/environment.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + # Load the rails application -require File.expand_path('../application', __FILE__) +require File.expand_path('application', __dir__) # Initialize the rails application Dummy::Application.initialize! diff --git a/spec/dummy/config/environments/development.rb b/spec/dummy/config/environments/development.rb index c143a826..77567be6 100644 --- a/spec/dummy/config/environments/development.rb +++ b/spec/dummy/config/environments/development.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + Dummy::Application.configure do # Settings specified here will take precedence over those in config/application.rb @@ -25,4 +27,3 @@ # Only use best-standards-support built into browsers config.action_dispatch.best_standards_support = :builtin end - diff --git a/spec/dummy/config/environments/production.rb b/spec/dummy/config/environments/production.rb index a71b06c4..1d9b71e1 100644 --- a/spec/dummy/config/environments/production.rb +++ b/spec/dummy/config/environments/production.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + Dummy::Application.configure do # Settings specified here will take precedence over those in config/application.rb @@ -12,7 +14,7 @@ config.action_controller.perform_caching = true # Specifies the header that your server uses for sending files - config.action_dispatch.x_sendfile_header = "X-Sendfile" + config.action_dispatch.x_sendfile_header = 'X-Sendfile' # For nginx: # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' diff --git a/spec/dummy/config/environments/test.rb b/spec/dummy/config/environments/test.rb index 30879efd..3044c445 100644 --- a/spec/dummy/config/environments/test.rb +++ b/spec/dummy/config/environments/test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + Dummy::Application.configure do # Settings specified here will take precedence over those in config/application.rb @@ -17,7 +19,7 @@ config.action_dispatch.show_exceptions = false # Disable request forgery protection in test environment - config.action_controller.allow_forgery_protection = false + config.action_controller.allow_forgery_protection = false # Tell Action Mailer not to deliver emails to the real world. # The :test delivery method accumulates sent emails in the diff --git a/spec/dummy/config/initializers/apartment.rb b/spec/dummy/config/initializers/apartment.rb index a49dda91..7db9fd26 100644 --- a/spec/dummy/config/initializers/apartment.rb +++ b/spec/dummy/config/initializers/apartment.rb @@ -1,4 +1,6 @@ +# frozen_string_literal: true + Apartment.configure do |config| - config.excluded_models = ["Company"] - config.tenant_names = lambda{ Company.pluck(:database) } + config.excluded_models = ['Company'] + config.tenant_names = -> { Company.pluck(:database) } end diff --git a/spec/dummy/config/initializers/backtrace_silencers.rb b/spec/dummy/config/initializers/backtrace_silencers.rb index 59385cdf..d0f0d3b5 100644 --- a/spec/dummy/config/initializers/backtrace_silencers.rb +++ b/spec/dummy/config/initializers/backtrace_silencers.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true # Be sure to restart your server when you modify this file. # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. diff --git a/spec/dummy/config/initializers/inflections.rb b/spec/dummy/config/initializers/inflections.rb index 9e8b0131..8138cabc 100644 --- a/spec/dummy/config/initializers/inflections.rb +++ b/spec/dummy/config/initializers/inflections.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true # Be sure to restart your server when you modify this file. # Add new inflection rules using the following format diff --git a/spec/dummy/config/initializers/mime_types.rb b/spec/dummy/config/initializers/mime_types.rb index 72aca7e4..f75864f9 100644 --- a/spec/dummy/config/initializers/mime_types.rb +++ b/spec/dummy/config/initializers/mime_types.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true # Be sure to restart your server when you modify this file. # Add new mime types for use in respond_to blocks: diff --git a/spec/dummy/config/initializers/secret_token.rb b/spec/dummy/config/initializers/secret_token.rb index 1c651c86..1ba0d52f 100644 --- a/spec/dummy/config/initializers/secret_token.rb +++ b/spec/dummy/config/initializers/secret_token.rb @@ -1,7 +1,12 @@ +# frozen_string_literal: true + # Be sure to restart your server when you modify this file. # Your secret key for verifying the integrity of signed cookies. # If you change this key, all old signed cookies will become invalid! # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. + +# rubocop:disable Layout/LineLength Dummy::Application.config.secret_token = '7d33999a86884f74c897c98ecca4277090b69e9f23df8d74bcadd57435320a7a16de67966f9b69d62e7d5ec553bd2febbe64c721e05bc1bc1e82c7a7d2395201' +# rubocop:enable Layout/LineLength diff --git a/spec/dummy/config/initializers/session_store.rb b/spec/dummy/config/initializers/session_store.rb index aa2f5129..66099cf5 100644 --- a/spec/dummy/config/initializers/session_store.rb +++ b/spec/dummy/config/initializers/session_store.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + # Be sure to restart your server when you modify this file. -Dummy::Application.config.session_store :cookie_store, :key => '_dummy_session' +Dummy::Application.config.session_store :cookie_store, key: '_dummy_session' # Use the database for sessions instead of the cookie-based default, # which shouldn't be used to store highly confidential information diff --git a/spec/dummy/config/routes.rb b/spec/dummy/config/routes.rb index 99d3e5c1..d0be0e6d 100644 --- a/spec/dummy/config/routes.rb +++ b/spec/dummy/config/routes.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + Dummy::Application.routes.draw do - root :to => 'application#index' + root to: 'application#index' end diff --git a/spec/dummy/db/migrate/20110613152810_create_dummy_models.rb b/spec/dummy/db/migrate/20110613152810_create_dummy_models.rb index d1f620eb..1da7c0c7 100644 --- a/spec/dummy/db/migrate/20110613152810_create_dummy_models.rb +++ b/spec/dummy/db/migrate/20110613152810_create_dummy_models.rb @@ -1,4 +1,6 @@ -migration_class = (ActiveRecord::VERSION::MAJOR >= 5) ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration +# frozen_string_literal: true + +migration_class = ActiveRecord::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration class CreateDummyModels < migration_class def self.up create_table :companies do |t| @@ -10,24 +12,23 @@ def self.up t.string :name t.datetime :birthdate t.string :sex - end - - create_table :delayed_jobs do |t| - t.integer :priority, :default => 0 - t.integer :attempts, :default => 0 - t.text :handler - t.text :last_error - t.datetime :run_at - t.datetime :locked_at - t.datetime :failed_at - t.string :locked_by - t.datetime :created_at - t.datetime :updated_at - t.string :queue - end + end - add_index "delayed_jobs", ["priority", "run_at"], :name => "delayed_jobs_priority" + create_table :delayed_jobs do |t| + t.integer :priority, default: 0 + t.integer :attempts, default: 0 + t.text :handler + t.text :last_error + t.datetime :run_at + t.datetime :locked_at + t.datetime :failed_at + t.string :locked_by + t.datetime :created_at + t.datetime :updated_at + t.string :queue + end + add_index 'delayed_jobs', %w[priority run_at], name: 'delayed_jobs_priority' end def self.down @@ -35,5 +36,4 @@ def self.down drop_table :users drop_table :delayed_jobs end - end diff --git a/spec/dummy/db/migrate/20111202022214_create_table_books.rb b/spec/dummy/db/migrate/20111202022214_create_table_books.rb index e841e2dd..d525063d 100644 --- a/spec/dummy/db/migrate/20111202022214_create_table_books.rb +++ b/spec/dummy/db/migrate/20111202022214_create_table_books.rb @@ -1,4 +1,6 @@ -migration_class = (ActiveRecord::VERSION::MAJOR >= 5) ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration +# frozen_string_literal: true + +migration_class = ActiveRecord::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration class CreateTableBooks < migration_class def up create_table :books do |t| diff --git a/spec/dummy/db/migrate/20180415260934_create_public_tokens.rb b/spec/dummy/db/migrate/20180415260934_create_public_tokens.rb index c24c45bf..24e27249 100644 --- a/spec/dummy/db/migrate/20180415260934_create_public_tokens.rb +++ b/spec/dummy/db/migrate/20180415260934_create_public_tokens.rb @@ -1,4 +1,6 @@ -migration_class = (ActiveRecord::VERSION::MAJOR >= 5) ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration +# frozen_string_literal: true + +migration_class = ActiveRecord::VERSION::MAJOR >= 5 ? ActiveRecord::Migration[4.2] : ActiveRecord::Migration class CreatePublicTokens < migration_class def up create_table :public_tokens do |t| diff --git a/spec/dummy/db/schema.rb b/spec/dummy/db/schema.rb index 3fb281f7..ab0d1c00 100644 --- a/spec/dummy/db/schema.rb +++ b/spec/dummy/db/schema.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # This file is auto-generated from the current state of the database. Instead # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. @@ -10,46 +12,44 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2018_04_15_260934) do - +ActiveRecord::Schema.define(version: 20_180_415_260_934) do # These are extensions that must be enabled in order to support this database - enable_extension "plpgsql" + enable_extension 'plpgsql' - create_table "books", force: :cascade do |t| - t.string "name" - t.integer "pages" - t.datetime "published" + create_table 'books', force: :cascade do |t| + t.string 'name' + t.integer 'pages' + t.datetime 'published' end - create_table "companies", force: :cascade do |t| - t.boolean "dummy" - t.string "database" + create_table 'companies', force: :cascade do |t| + t.boolean 'dummy' + t.string 'database' end - create_table "delayed_jobs", force: :cascade do |t| - t.integer "priority", default: 0 - t.integer "attempts", default: 0 - t.text "handler" - t.text "last_error" - t.datetime "run_at" - t.datetime "locked_at" - t.datetime "failed_at" - t.string "locked_by" - t.datetime "created_at" - t.datetime "updated_at" - t.string "queue" - t.index ["priority", "run_at"], name: "delayed_jobs_priority" + create_table 'delayed_jobs', force: :cascade do |t| + t.integer 'priority', default: 0 + t.integer 'attempts', default: 0 + t.text 'handler' + t.text 'last_error' + t.datetime 'run_at' + t.datetime 'locked_at' + t.datetime 'failed_at' + t.string 'locked_by' + t.datetime 'created_at' + t.datetime 'updated_at' + t.string 'queue' + t.index %w[priority run_at], name: 'delayed_jobs_priority' end - create_table "public_tokens", id: :serial, force: :cascade do |t| - t.string "token" - t.integer "user_id" + create_table 'public_tokens', id: :serial, force: :cascade do |t| + t.string 'token' + t.integer 'user_id' end - create_table "users", force: :cascade do |t| - t.string "name" - t.datetime "birthdate" - t.string "sex" + create_table 'users', force: :cascade do |t| + t.string 'name' + t.datetime 'birthdate' + t.string 'sex' end - end diff --git a/spec/dummy/db/seeds.rb b/spec/dummy/db/seeds.rb index 4aa28df6..66ad2762 100644 --- a/spec/dummy/db/seeds.rb +++ b/spec/dummy/db/seeds.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + def create_users - 3.times {|x| User.where(name: "Some User #{x}").first_or_create! } + 3.times { |x| User.where(name: "Some User #{x}").first_or_create! } end -create_users \ No newline at end of file +create_users diff --git a/spec/dummy/db/seeds/import.rb b/spec/dummy/db/seeds/import.rb index 8265d6be..10b479b9 100644 --- a/spec/dummy/db/seeds/import.rb +++ b/spec/dummy/db/seeds/import.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + def create_users - 6.times {|x| User.where(name: "Different User #{x}").first_or_create! } + 6.times { |x| User.where(name: "Different User #{x}").first_or_create! } end create_users diff --git a/spec/dummy/script/rails b/spec/dummy/script/rails index f8da2cff..6c919a61 100755 --- a/spec/dummy/script/rails +++ b/spec/dummy/script/rails @@ -1,6 +1,9 @@ #!/usr/bin/env ruby -# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. +# frozen_string_literal: true -APP_PATH = File.expand_path('../../config/application', __FILE__) -require File.expand_path('../../config/boot', __FILE__) +# This command will automatically be run when you run "rails" with Rails 3 gems installed +# from the root of your application. + +APP_PATH = File.expand_path('../config/application', __dir__) +require File.expand_path('../config/boot', __dir__) require 'rails/commands' diff --git a/spec/dummy_engine/Gemfile b/spec/dummy_engine/Gemfile index 2ae66b0d..59f9baba 100644 --- a/spec/dummy_engine/Gemfile +++ b/spec/dummy_engine/Gemfile @@ -1,4 +1,6 @@ -source "https://rubygems.org" +# frozen_string_literal: true + +source 'https://rubygems.org' # Declare your gem's dependencies in dummy_engine.gemspec. # Bundler will treat runtime dependencies like base dependencies, and diff --git a/spec/dummy_engine/Rakefile b/spec/dummy_engine/Rakefile index 565abf87..72a61a74 100644 --- a/spec/dummy_engine/Rakefile +++ b/spec/dummy_engine/Rakefile @@ -1,3 +1,5 @@ +# frozen_string_literal: true + begin require 'bundler/setup' rescue LoadError @@ -14,11 +16,9 @@ RDoc::Task.new(:rdoc) do |rdoc| rdoc.rdoc_files.include('lib/**/*.rb') end -APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__) +APP_RAKEFILE = File.expand_path('test/dummy/Rakefile', __dir__) load 'rails/tasks/engine.rake' - - Bundler::GemHelper.install_tasks require 'rake/testtask' @@ -30,5 +30,4 @@ Rake::TestTask.new(:test) do |t| t.verbose = false end - task default: :test diff --git a/spec/dummy_engine/bin/rails b/spec/dummy_engine/bin/rails index cb6a9e45..397f409e 100755 --- a/spec/dummy_engine/bin/rails +++ b/spec/dummy_engine/bin/rails @@ -1,11 +1,14 @@ #!/usr/bin/env ruby -# This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application. +# frozen_string_literal: true -ENGINE_ROOT = File.expand_path('../..', __FILE__) -ENGINE_PATH = File.expand_path('../../lib/dummy_engine/engine', __FILE__) +# This command will automatically be run when you run "rails" with Rails 4 gems installed +# from the root of your application. + +ENGINE_ROOT = File.expand_path('..', __dir__) +ENGINE_PATH = File.expand_path('../lib/dummy_engine/engine', __dir__) # Set up gems listed in the Gemfile. -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) require 'rails/all' diff --git a/spec/dummy_engine/config/initializers/apartment.rb b/spec/dummy_engine/config/initializers/apartment.rb index 611f5322..a65748eb 100644 --- a/spec/dummy_engine/config/initializers/apartment.rb +++ b/spec/dummy_engine/config/initializers/apartment.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Require whichever elevator you're using below here... # # require 'apartment/elevators/generic' @@ -8,7 +10,6 @@ # Apartment Configuration # Apartment.configure do |config| - # These models will not be multi-tenanted, # but remain in the global (public) namespace # @@ -17,13 +18,13 @@ # # config.excluded_models = %w{Tenant} # - config.excluded_models = %w{} + config.excluded_models = %w[] # use postgres schemas? config.use_schemas = true # use raw SQL dumps for creating postgres schemas? (only appies with use_schemas set to true) - #config.use_sql = true + # config.use_sql = true # configure persistent schemas (E.g. hstore ) # config.persistent_schemas = %w{ hstore } @@ -35,7 +36,8 @@ # supply list of database names for migrations to run on # config.tenant_names = lambda{ ToDo_Tenant_Or_User_Model.pluck :database } - # Specify a connection other than ActiveRecord::Base for apartment to use (only needed if your models are using a different connection) + # Specify a connection other than ActiveRecord::Base for apartment to use + # (only needed if your models are using a different connection) # config.connection_class = ActiveRecord::Base end diff --git a/spec/dummy_engine/dummy_engine.gemspec b/spec/dummy_engine/dummy_engine.gemspec index d17f9156..afb00e8f 100644 --- a/spec/dummy_engine/dummy_engine.gemspec +++ b/spec/dummy_engine/dummy_engine.gemspec @@ -1,24 +1,26 @@ -$:.push File.expand_path("../lib", __FILE__) +# frozen_string_literal: true + +$LOAD_PATH << File.expand_path('lib', __dir__) # Maintain your gem's version: -require "dummy_engine/version" +require 'dummy_engine/version' # Describe your gem and declare its dependencies: Gem::Specification.new do |s| - s.name = "dummy_engine" + s.name = 'dummy_engine' s.version = DummyEngine::VERSION - s.authors = ["Your name"] - s.email = ["Your email"] - s.homepage = "" - s.summary = "Summary of DummyEngine." - s.description = "Description of DummyEngine." - s.license = "MIT" + s.authors = ['Your name'] + s.email = ['Your email'] + s.homepage = '' + s.summary = 'Summary of DummyEngine.' + s.description = 'Description of DummyEngine.' + s.license = 'MIT' - s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.rdoc"] - s.test_files = Dir["test/**/*"] + s.files = Dir['{app,config,db,lib}/**/*', 'MIT-LICENSE', 'Rakefile', 'README.rdoc'] + s.test_files = Dir['test/**/*'] - s.add_dependency "rails", "~> 4.1.6" - s.add_dependency "apartment" + s.add_dependency 'apartment' + s.add_dependency 'rails', '~> 4.1.6' - s.add_development_dependency "sqlite3" + s.add_development_dependency 'sqlite3' end diff --git a/spec/dummy_engine/lib/dummy_engine.rb b/spec/dummy_engine/lib/dummy_engine.rb index b1a14145..8f9c8111 100644 --- a/spec/dummy_engine/lib/dummy_engine.rb +++ b/spec/dummy_engine/lib/dummy_engine.rb @@ -1,4 +1,6 @@ -require "dummy_engine/engine" +# frozen_string_literal: true + +require 'dummy_engine/engine' module DummyEngine end diff --git a/spec/dummy_engine/lib/dummy_engine/engine.rb b/spec/dummy_engine/lib/dummy_engine/engine.rb index cc8821c3..d308ec0d 100644 --- a/spec/dummy_engine/lib/dummy_engine/engine.rb +++ b/spec/dummy_engine/lib/dummy_engine/engine.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module DummyEngine class Engine < ::Rails::Engine end diff --git a/spec/dummy_engine/lib/dummy_engine/version.rb b/spec/dummy_engine/lib/dummy_engine/version.rb index 82d2c8b8..76d025df 100644 --- a/spec/dummy_engine/lib/dummy_engine/version.rb +++ b/spec/dummy_engine/lib/dummy_engine/version.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module DummyEngine - VERSION = "0.0.1" + VERSION = '0.0.1' end diff --git a/spec/dummy_engine/test/dummy/Rakefile b/spec/dummy_engine/test/dummy/Rakefile index ba6b733d..e51cf0e1 100644 --- a/spec/dummy_engine/test/dummy/Rakefile +++ b/spec/dummy_engine/test/dummy/Rakefile @@ -1,6 +1,8 @@ +# frozen_string_literal: true + # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. -require File.expand_path('../config/application', __FILE__) +require File.expand_path('config/application', __dir__) Rails.application.load_tasks diff --git a/spec/dummy_engine/test/dummy/config.ru b/spec/dummy_engine/test/dummy/config.ru index 5bc2a619..667e328d 100644 --- a/spec/dummy_engine/test/dummy/config.ru +++ b/spec/dummy_engine/test/dummy/config.ru @@ -1,4 +1,6 @@ +# frozen_string_literal: true + # This file is used by Rack-based servers to start the application. -require ::File.expand_path('../config/environment', __FILE__) +require ::File.expand_path('config/environment', __dir__) run Rails.application diff --git a/spec/dummy_engine/test/dummy/config/application.rb b/spec/dummy_engine/test/dummy/config/application.rb index 5ef001e9..0984f6ce 100644 --- a/spec/dummy_engine/test/dummy/config/application.rb +++ b/spec/dummy_engine/test/dummy/config/application.rb @@ -1,9 +1,11 @@ -require File.expand_path('../boot', __FILE__) +# frozen_string_literal: true + +require File.expand_path('boot', __dir__) require 'rails/all' Bundler.require(*Rails.groups) -require "dummy_engine" +require 'dummy_engine' module Dummy class Application < Rails::Application diff --git a/spec/dummy_engine/test/dummy/config/boot.rb b/spec/dummy_engine/test/dummy/config/boot.rb index 6266cfc5..6d2cba07 100644 --- a/spec/dummy_engine/test/dummy/config/boot.rb +++ b/spec/dummy_engine/test/dummy/config/boot.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + # Set up gems listed in the Gemfile. -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__) +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../Gemfile', __dir__) require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) -$LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__) +$LOAD_PATH.unshift File.expand_path('../../../lib', __dir__) diff --git a/spec/dummy_engine/test/dummy/config/environment.rb b/spec/dummy_engine/test/dummy/config/environment.rb index ee8d90dc..32d57aa4 100644 --- a/spec/dummy_engine/test/dummy/config/environment.rb +++ b/spec/dummy_engine/test/dummy/config/environment.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + # Load the Rails application. -require File.expand_path('../application', __FILE__) +require File.expand_path('application', __dir__) # Initialize the Rails application. Rails.application.initialize! diff --git a/spec/dummy_engine/test/dummy/config/environments/development.rb b/spec/dummy_engine/test/dummy/config/environments/development.rb index ddf0e90c..8296624e 100644 --- a/spec/dummy_engine/test/dummy/config/environments/development.rb +++ b/spec/dummy_engine/test/dummy/config/environments/development.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. diff --git a/spec/dummy_engine/test/dummy/config/environments/production.rb b/spec/dummy_engine/test/dummy/config/environments/production.rb index b93a877c..1bd152f1 100644 --- a/spec/dummy_engine/test/dummy/config/environments/production.rb +++ b/spec/dummy_engine/test/dummy/config/environments/production.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. diff --git a/spec/dummy_engine/test/dummy/config/environments/test.rb b/spec/dummy_engine/test/dummy/config/environments/test.rb index 053f5b66..bd942389 100644 --- a/spec/dummy_engine/test/dummy/config/environments/test.rb +++ b/spec/dummy_engine/test/dummy/config/environments/test.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. diff --git a/spec/dummy_engine/test/dummy/config/initializers/assets.rb b/spec/dummy_engine/test/dummy/config/initializers/assets.rb index d2f4ec33..761905a7 100644 --- a/spec/dummy_engine/test/dummy/config/initializers/assets.rb +++ b/spec/dummy_engine/test/dummy/config/initializers/assets.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Be sure to restart your server when you modify this file. # Version of your assets, change this if you want to expire all your assets. diff --git a/spec/dummy_engine/test/dummy/config/initializers/backtrace_silencers.rb b/spec/dummy_engine/test/dummy/config/initializers/backtrace_silencers.rb index 59385cdf..d0f0d3b5 100644 --- a/spec/dummy_engine/test/dummy/config/initializers/backtrace_silencers.rb +++ b/spec/dummy_engine/test/dummy/config/initializers/backtrace_silencers.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true # Be sure to restart your server when you modify this file. # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. diff --git a/spec/dummy_engine/test/dummy/config/initializers/cookies_serializer.rb b/spec/dummy_engine/test/dummy/config/initializers/cookies_serializer.rb index 7a06a89f..0a23b25e 100644 --- a/spec/dummy_engine/test/dummy/config/initializers/cookies_serializer.rb +++ b/spec/dummy_engine/test/dummy/config/initializers/cookies_serializer.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Be sure to restart your server when you modify this file. -Rails.application.config.action_dispatch.cookies_serializer = :json \ No newline at end of file +Rails.application.config.action_dispatch.cookies_serializer = :json diff --git a/spec/dummy_engine/test/dummy/config/initializers/filter_parameter_logging.rb b/spec/dummy_engine/test/dummy/config/initializers/filter_parameter_logging.rb index 4a994e1e..7a4f47b4 100644 --- a/spec/dummy_engine/test/dummy/config/initializers/filter_parameter_logging.rb +++ b/spec/dummy_engine/test/dummy/config/initializers/filter_parameter_logging.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Be sure to restart your server when you modify this file. # Configure sensitive parameters which will be filtered from the log file. diff --git a/spec/dummy_engine/test/dummy/config/initializers/inflections.rb b/spec/dummy_engine/test/dummy/config/initializers/inflections.rb index ac033bf9..aa7435fb 100644 --- a/spec/dummy_engine/test/dummy/config/initializers/inflections.rb +++ b/spec/dummy_engine/test/dummy/config/initializers/inflections.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true # Be sure to restart your server when you modify this file. # Add new inflection rules using the following format. Inflections diff --git a/spec/dummy_engine/test/dummy/config/initializers/mime_types.rb b/spec/dummy_engine/test/dummy/config/initializers/mime_types.rb index dc189968..6e1d16f0 100644 --- a/spec/dummy_engine/test/dummy/config/initializers/mime_types.rb +++ b/spec/dummy_engine/test/dummy/config/initializers/mime_types.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true # Be sure to restart your server when you modify this file. # Add new mime types for use in respond_to blocks: diff --git a/spec/dummy_engine/test/dummy/config/initializers/session_store.rb b/spec/dummy_engine/test/dummy/config/initializers/session_store.rb index e766b67b..969d977f 100644 --- a/spec/dummy_engine/test/dummy/config/initializers/session_store.rb +++ b/spec/dummy_engine/test/dummy/config/initializers/session_store.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Be sure to restart your server when you modify this file. Rails.application.config.session_store :cookie_store, key: '_dummy_session' diff --git a/spec/dummy_engine/test/dummy/config/initializers/wrap_parameters.rb b/spec/dummy_engine/test/dummy/config/initializers/wrap_parameters.rb index 33725e95..246168a4 100644 --- a/spec/dummy_engine/test/dummy/config/initializers/wrap_parameters.rb +++ b/spec/dummy_engine/test/dummy/config/initializers/wrap_parameters.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # Be sure to restart your server when you modify this file. # This file contains settings for ActionController::ParamsWrapper which diff --git a/spec/dummy_engine/test/dummy/config/routes.rb b/spec/dummy_engine/test/dummy/config/routes.rb index 3f66539d..189947fc 100644 --- a/spec/dummy_engine/test/dummy/config/routes.rb +++ b/spec/dummy_engine/test/dummy/config/routes.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + Rails.application.routes.draw do # The priority is based upon order of creation: first created -> highest priority. # See how all your routes lay out with "rake routes". diff --git a/spec/examples/connection_adapter_examples.rb b/spec/examples/connection_adapter_examples.rb index a7d4ac9b..973ed1fa 100644 --- a/spec/examples/connection_adapter_examples.rb +++ b/spec/examples/connection_adapter_examples.rb @@ -1,11 +1,13 @@ +# frozen_string_literal: true + require 'spec_helper' -shared_examples_for "a connection based apartment adapter" do +shared_examples_for 'a connection based apartment adapter' do include Apartment::Spec::AdapterRequirements - let(:default_tenant){ subject.switch{ ActiveRecord::Base.connection.current_database } } + let(:default_tenant) { subject.switch { ActiveRecord::Base.connection.current_database } } - describe "#init" do + describe '#init' do after do # Apartment::Tenant.init creates per model connection. # Remove the connection after testing not to unintentionally keep the connection across tests. @@ -14,9 +16,9 @@ end end - it "should process model exclusions" do + it 'should process model exclusions' do Apartment.configure do |config| - config.excluded_models = ["Company"] + config.excluded_models = ['Company'] end Apartment::Tenant.init @@ -24,19 +26,19 @@ end end - describe "#drop" do - it "should raise an error for unknown database" do - expect { + describe '#drop' do + it 'should raise an error for unknown database' do + expect do subject.drop 'unknown_database' - }.to raise_error(Apartment::TenantNotFound) + end.to raise_error(Apartment::TenantNotFound) end end - describe "#switch!" do - it "should raise an error if database is invalid" do - expect { + describe '#switch!' do + it 'should raise an error if database is invalid' do + expect do subject.switch! 'unknown_database' - }.to raise_error(Apartment::TenantNotFound) + end.to raise_error(Apartment::TenantNotFound) end end end diff --git a/spec/examples/generic_adapter_custom_configuration_example.rb b/spec/examples/generic_adapter_custom_configuration_example.rb index a9f053a3..2ad7ee20 100644 --- a/spec/examples/generic_adapter_custom_configuration_example.rb +++ b/spec/examples/generic_adapter_custom_configuration_example.rb @@ -1,12 +1,13 @@ -require 'spec_helper' +# frozen_string_literal: true -shared_examples_for "a generic apartment adapter able to handle custom configuration" do +require 'spec_helper' +shared_examples_for 'a generic apartment adapter able to handle custom configuration' do let(:custom_tenant_name) { 'test_tenantwwww' } - let(:db) { |example| example.metadata[:database]} + let(:db) { |example| example.metadata[:database] } let(:custom_tenant_names) do { - custom_tenant_name => get_custom_db_conf + custom_tenant_name => custom_db_conf } end @@ -19,23 +20,26 @@ Apartment.with_multi_server_setup = false end - context "database key taken from specific config" do + context 'database key taken from specific config' do + let(:expected_args) { custom_db_conf } - let(:expected_args) { get_custom_db_conf } + describe '#create' do + it 'should establish_connection with the separate connection with expected args' do + expect(Apartment::Adapters::AbstractAdapter::SeparateDbConnectionHandler).to( + receive(:establish_connection).with(expected_args).and_call_original + ) - describe "#create" do - it "should establish_connection with the separate connection with expected args" do - expect(Apartment::Adapters::AbstractAdapter::SeparateDbConnectionHandler).to receive(:establish_connection).with(expected_args).and_call_original - - # because we dont have another server to connect to it errors + # because we don't have another server to connect to it errors # what matters is establish_connection receives proper args expect { subject.create(custom_tenant_name) }.to raise_error(Apartment::TenantExists) end end - describe "#drop" do - it "should establish_connection with the separate connection with expected args" do - expect(Apartment::Adapters::AbstractAdapter::SeparateDbConnectionHandler).to receive(:establish_connection).with(expected_args).and_call_original + describe '#drop' do + it 'should establish_connection with the separate connection with expected args' do + expect(Apartment::Adapters::AbstractAdapter::SeparateDbConnectionHandler).to( + receive(:establish_connection).with(expected_args).and_call_original + ) # because we dont have another server to connect to it errors # what matters is establish_connection receives proper args @@ -44,15 +48,13 @@ end end - context "database key from tenant name" do - - let(:expected_args) { - get_custom_db_conf.tap {|args| args.delete(:database) } - } - - describe "#switch!" do + context 'database key from tenant name' do + let(:expected_args) do + custom_db_conf.tap { |args| args.delete(:database) } + end - it "should connect to new db" do + describe '#switch!' do + it 'should connect to new db' do expect(Apartment).to receive(:establish_connection) do |args| db_name = args.delete(:database) @@ -72,24 +74,24 @@ def specific_connection { postgresql: { - adapter: 'postgresql', + adapter: 'postgresql', database: 'override_database', password: 'override_password', username: 'overridepostgres' }, mysql: { - adapter: 'mysql2', + adapter: 'mysql2', database: 'override_database', username: 'root' }, sqlite: { - adapter: 'sqlite3', + adapter: 'sqlite3', database: 'override_database' } } end - def get_custom_db_conf + def custom_db_conf specific_connection[db.to_sym].with_indifferent_access end end diff --git a/spec/examples/generic_adapter_examples.rb b/spec/examples/generic_adapter_examples.rb index 6c1b32ac..cbe6013f 100644 --- a/spec/examples/generic_adapter_examples.rb +++ b/spec/examples/generic_adapter_examples.rb @@ -1,28 +1,45 @@ +# frozen_string_literal: true + require 'spec_helper' -shared_examples_for "a generic apartment adapter" do +shared_examples_for 'a generic apartment adapter' do include Apartment::Spec::AdapterRequirements - before { + before do Apartment.prepend_environment = false Apartment.append_environment = false - } + Apartment.tenant_presence_check = true + end + + describe '#init' do + it 'should not retain a connection after railtie' do + ActiveRecord::Base.connection_pool.disconnect! + + Apartment::Railtie.config.to_prepare_blocks.map(&:call) + + num_available_connections = Apartment.connection_class.connection_pool + .instance_variable_get(:@available) + .instance_variable_get(:@queue) + .size - describe "#init" do - it "should not retain a connection after railtie" do - # this test should work on rails >= 4, the connection pool code is - # completely different for 3.2 so we'd have to have a messy conditional.. - unless Rails::VERSION::MAJOR < 4 + expect(num_available_connections).to eq(1) + end + + it 'should not connect if env var is set' do + ENV['APARTMENT_DISABLE_INIT'] = 'true' + begin ActiveRecord::Base.connection_pool.disconnect! Apartment::Railtie.config.to_prepare_blocks.map(&:call) num_available_connections = Apartment.connection_class.connection_pool - .instance_variable_get(:@available) - .instance_variable_get(:@queue) - .size + .instance_variable_get(:@available) + .instance_variable_get(:@queue) + .size - expect(num_available_connections).to eq(1) + expect(num_available_connections).to eq(0) + ensure + ENV.delete('APARTMENT_DISABLE_INIT') end end end @@ -30,23 +47,22 @@ # # Creates happen already in our before_filter # - describe "#create" do - - it "should create the new databases" do + describe '#create' do + it 'should create the new databases' do expect(tenant_names).to include(db1) expect(tenant_names).to include(db2) end - it "should load schema.rb to new schema" do + it 'should load schema.rb to new schema' do subject.switch(db1) do expect(connection.tables).to include('companies') end end - it "should yield to block if passed and reset" do + it 'should yield to block if passed and reset' do subject.drop(db2) # so we don't get errors on creation - @count = 0 # set our variable so its visible in and outside of blocks + @count = 0 # set our variable so its visible in and outside of blocks subject.create(db2) do @count = User.count @@ -56,19 +72,19 @@ expect(subject.current).not_to eq(db2) - subject.switch(db2){ expect(User.count).to eq(@count + 1) } + subject.switch(db2) { expect(User.count).to eq(@count + 1) } end - it "should raise error when the schema.rb is missing unless Apartment.use_sql is set to true" do + it 'should raise error when the schema.rb is missing unless Apartment.use_sql is set to true' do next if Apartment.use_sql subject.drop(db1) begin Dir.mktmpdir do |tmpdir| Apartment.database_schema_file = "#{tmpdir}/schema.rb" - expect { + expect do subject.create(db1) - }.to raise_error(Apartment::FileNotFound) + end.to raise_error(Apartment::FileNotFound) end ensure Apartment.remove_instance_variable(:@database_schema_file) @@ -76,33 +92,33 @@ end end - describe "#drop" do - it "should remove the db" do + describe '#drop' do + it 'should remove the db' do subject.drop db1 expect(tenant_names).not_to include(db1) end end - describe "#switch!" do - it "should connect to new db" do + describe '#switch!' do + it 'should connect to new db' do subject.switch!(db1) expect(subject.current).to eq(db1) end - it "should reset connection if database is nil" do + it 'should reset connection if database is nil' do subject.switch! expect(subject.current).to eq(default_tenant) end - it "should raise an error if database is invalid" do - expect { + it 'should raise an error if database is invalid' do + expect do subject.switch! 'unknown_database' - }.to raise_error(Apartment::ApartmentError) + end.to raise_error(Apartment::ApartmentError) end end - describe "#switch" do - it "connects and resets the tenant" do + describe '#switch' do + it 'connects and resets the tenant' do subject.switch(db1) do expect(subject.current).to eq(db1) end @@ -111,32 +127,32 @@ # We're often finding when using Apartment in tests, the `current` (ie the previously connect to db) # gets dropped, but switch will try to return to that db in a test. We should just reset if it doesn't exist - it "should not throw exception if current is no longer accessible" do + it 'should not throw exception if current is no longer accessible' do subject.switch!(db2) - expect { - subject.switch(db1){ subject.drop(db2) } - }.to_not raise_error + expect do + subject.switch(db1) { subject.drop(db2) } + end.to_not raise_error end end - describe "#reset" do - it "should reset connection" do + describe '#reset' do + it 'should reset connection' do subject.switch!(db1) subject.reset expect(subject.current).to eq(default_tenant) end end - describe "#current" do - it "should return the current db name" do + describe '#current' do + it 'should return the current db name' do subject.switch!(db1) expect(subject.current).to eq(db1) end end - describe "#each" do - it "iterates over each tenant by default" do + describe '#each' do + it 'iterates over each tenant by default' do result = [] Apartment.tenant_names = [db2, db1] @@ -148,7 +164,7 @@ expect(result).to eq([db2, db1]) end - it "iterates over the given tenants" do + it 'iterates over the given tenants' do result = [] Apartment.tenant_names = [db2] diff --git a/spec/examples/generic_adapters_callbacks_examples.rb b/spec/examples/generic_adapters_callbacks_examples.rb new file mode 100644 index 00000000..3184a4a1 --- /dev/null +++ b/spec/examples/generic_adapters_callbacks_examples.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'spec_helper' + +shared_examples_for 'a generic apartment adapter callbacks' do + # rubocop:disable Lint/ConstantDefinitionInBlock + class MyProc + def self.call(tenant_name); end + end + # rubocop:enable Lint/ConstantDefinitionInBlock + + include Apartment::Spec::AdapterRequirements + + before do + Apartment.prepend_environment = false + Apartment.append_environment = false + end + + describe '#switch!' do + before do + Apartment::Adapters::AbstractAdapter.set_callback :switch, :before do + MyProc.call(Apartment::Tenant.current) + end + + Apartment::Adapters::AbstractAdapter.set_callback :switch, :after do + MyProc.call(Apartment::Tenant.current) + end + + allow(MyProc).to receive(:call) + end + + # NOTE: Part of the test setup creates and switches tenants, so we need + # to reset the callbacks to ensure that each test run has the correct + # counts + after do + Apartment::Adapters::AbstractAdapter.reset_callbacks :switch + end + + context 'when tenant is nil' do + before do + Apartment::Tenant.switch!(nil) + end + + it 'runs both before and after callbacks' do + expect(MyProc).to have_received(:call).twice + end + end + + context 'when tenant is not nil' do + before do + Apartment::Tenant.switch!(db1) + end + + it 'runs both before and after callbacks' do + expect(MyProc).to have_received(:call).twice + end + end + end +end diff --git a/spec/examples/schema_adapter_examples.rb b/spec/examples/schema_adapter_examples.rb index 5eed5091..f8c98ac7 100644 --- a/spec/examples/schema_adapter_examples.rb +++ b/spec/examples/schema_adapter_examples.rb @@ -1,17 +1,17 @@ -require 'spec_helper' +# frozen_string_literal: true -shared_examples_for "a schema based apartment adapter" do +require 'spec_helper' +shared_examples_for 'a schema based apartment adapter' do include Apartment::Spec::AdapterRequirements - let(:schema1){ db1 } - let(:schema2){ db2 } - let(:public_schema){ default_tenant } - - describe "#init" do + let(:schema1) { db1 } + let(:schema2) { db2 } + let(:public_schema) { default_tenant } + describe '#init' do before do Apartment.configure do |config| - config.excluded_models = ["Company"] + config.excluded_models = ['Company'] end end @@ -23,31 +23,30 @@ end end - it "should process model exclusions" do + it 'should process model exclusions' do Apartment::Tenant.init - expect(Company.table_name).to eq("public.companies") + expect(Company.table_name).to eq('public.companies') end - context "with a default_schema", :default_schema => true do - - it "should set the proper table_name on excluded_models" do + context 'with a default_tenant', default_tenant: true do + it 'should set the proper table_name on excluded_models' do Apartment::Tenant.init - expect(Company.table_name).to eq("#{default_schema}.companies") + expect(Company.table_name).to eq("#{default_tenant}.companies") end it 'sets the search_path correctly' do Apartment::Tenant.init - expect(User.connection.schema_search_path).to match(%r|#{default_schema}|) + expect(User.connection.schema_search_path).to match(/|#{default_tenant}|/) end end - context "persistent_schemas", :persistent_schemas => true do - it "sets the persistent schemas in the schema_search_path" do + context 'persistent_schemas', persistent_schemas: true do + it 'sets the persistent schemas in the schema_search_path' do Apartment::Tenant.init - expect(connection.schema_search_path).to end_with persistent_schemas.map { |schema| %{"#{schema}"} }.join(', ') + expect(connection.schema_search_path).to end_with persistent_schemas.map { |schema| %("#{schema}") }.join(', ') end end end @@ -55,177 +54,205 @@ # # Creates happen already in our before_filter # - describe "#create" do - - it "should load schema.rb to new schema" do + describe '#create' do + it 'should load schema.rb to new schema' do connection.schema_search_path = schema1 expect(connection.tables).to include('companies') end - it "should yield to block if passed and reset" do + it 'should yield to block if passed and reset' do subject.drop(schema2) # so we don't get errors on creation - @count = 0 # set our variable so its visible in and outside of blocks + @count = 0 # set our variable so its visible in and outside of blocks subject.create(schema2) do @count = User.count - expect(connection.schema_search_path).to start_with %{"#{schema2}"} + expect(connection.schema_search_path).to start_with %("#{schema2}") User.create end - expect(connection.schema_search_path).not_to start_with %{"#{schema2}"} + expect(connection.schema_search_path).not_to start_with %("#{schema2}") - subject.switch(schema2){ expect(User.count).to eq(@count + 1) } + subject.switch(schema2) { expect(User.count).to eq(@count + 1) } end - context "numeric database names" do - let(:db){ 1234 } - it "should allow them" do - expect { + context 'numeric database names' do + let(:db) { 1234 } + it 'should allow them' do + expect do subject.create(db) - }.to_not raise_error + end.to_not raise_error expect(tenant_names).to include(db.to_s) end - after{ subject.drop(db) } + after { subject.drop(db) } end - end - describe "#drop" do - it "should raise an error for unknown database" do - expect { - subject.drop "unknown_database" - }.to raise_error(Apartment::TenantNotFound) + describe '#drop' do + it 'should raise an error for unknown database' do + expect do + subject.drop 'unknown_database' + end.to raise_error(Apartment::TenantNotFound) end - context "numeric database names" do - let(:db){ 1234 } + context 'numeric database names' do + let(:db) { 1234 } - it "should be able to drop them" do + it 'should be able to drop them' do subject.create(db) - expect { + expect do subject.drop(db) - }.to_not raise_error + end.to_not raise_error expect(tenant_names).not_to include(db.to_s) end - after { subject.drop(db) rescue nil } + after do + begin + subject.drop(db) + rescue StandardError => _e + nil + end + end end end - describe "#switch" do - it "connects and resets" do + describe '#switch' do + it 'connects and resets' do subject.switch(schema1) do - expect(connection.schema_search_path).to start_with %{"#{schema1}"} + expect(connection.schema_search_path).to start_with %("#{schema1}") + expect(User.sequence_name).to eq "#{schema1}.#{User.table_name}_id_seq" end - expect(connection.schema_search_path).to start_with %{"#{public_schema}"} + expect(connection.schema_search_path).to start_with %("#{public_schema}") + expect(User.sequence_name).to eq "#{public_schema}.#{User.table_name}_id_seq" + end + + it "allows a list of schemas" do + subject.switch([schema1, schema2]) do + expect(connection.schema_search_path).to include %{"#{schema1}"} + expect(connection.schema_search_path).to include %{"#{schema2}"} + end end end - describe "#reset" do - it "should reset connection" do + describe '#reset' do + it 'should reset connection' do subject.switch!(schema1) subject.reset - expect(connection.schema_search_path).to start_with %{"#{public_schema}"} + expect(connection.schema_search_path).to start_with %("#{public_schema}") end - context "with default_schema", :default_schema => true do - it "should reset to the default schema" do + context 'with default_tenant', default_tenant: true do + it 'should reset to the default schema' do subject.switch!(schema1) subject.reset - expect(connection.schema_search_path).to start_with %{"#{default_schema}"} + expect(connection.schema_search_path).to start_with %("#{default_tenant}") end end - context "persistent_schemas", :persistent_schemas => true do + context 'persistent_schemas', persistent_schemas: true do before do subject.switch!(schema1) subject.reset end - it "maintains the persistent schemas in the schema_search_path" do - expect(connection.schema_search_path).to end_with persistent_schemas.map { |schema| %{"#{schema}"} }.join(', ') + it 'maintains the persistent schemas in the schema_search_path' do + expect(connection.schema_search_path).to end_with persistent_schemas.map { |schema| %("#{schema}") }.join(', ') end - context "with default_schema", :default_schema => true do - it "prioritizes the switched schema to front of schema_search_path" do - subject.reset # need to re-call this as the default_schema wasn't set at the time that the above reset ran - expect(connection.schema_search_path).to start_with %{"#{default_schema}"} + context 'with default_tenant', default_tenant: true do + it 'prioritizes the switched schema to front of schema_search_path' do + subject.reset # need to re-call this as the default_tenant wasn't set at the time that the above reset ran + expect(connection.schema_search_path).to start_with %("#{default_tenant}") end end end end - describe "#switch!" do - it "should connect to new schema" do + describe '#switch!' do + let(:tenant_presence_check) { true } + + before { Apartment.tenant_presence_check = tenant_presence_check } + + it 'should connect to new schema' do subject.switch!(schema1) - expect(connection.schema_search_path).to start_with %{"#{schema1}"} + expect(connection.schema_search_path).to start_with %("#{schema1}") end - it "should reset connection if database is nil" do + it 'should reset connection if database is nil' do subject.switch! - expect(connection.schema_search_path).to eq(%{"#{public_schema}"}) + expect(connection.schema_search_path).to eq(%("#{public_schema}")) + end + + context 'when configuration checks for tenant presence before switching' do + it 'should raise an error if schema is invalid' do + expect do + subject.switch! 'unknown_schema' + end.to raise_error(Apartment::TenantNotFound) + end end - it "should raise an error if schema is invalid" do - expect { - subject.switch! 'unknown_schema' - }.to raise_error(Apartment::TenantNotFound) + context 'when configuration skips tenant presence check before switching' do + let(:tenant_presence_check) { false } + + it 'should not raise any errors' do + expect do + subject.switch! 'unknown_schema' + end.to_not raise_error(Apartment::TenantNotFound) + end end - context "numeric databases" do - let(:db){ 1234 } + context 'numeric databases' do + let(:db) { 1234 } - it "should connect to them" do + it 'should connect to them' do subject.create(db) - expect { + expect do subject.switch!(db) - }.to_not raise_error + end.to_not raise_error - expect(connection.schema_search_path).to start_with %{"#{db.to_s}"} + expect(connection.schema_search_path).to start_with %("#{db}") end - after{ subject.drop(db) } + after { subject.drop(db) } end - describe "with default_schema specified", :default_schema => true do + describe 'with default_tenant specified', default_tenant: true do before do subject.switch!(schema1) end - it "should switch out the default schema rather than public" do - expect(connection.schema_search_path).not_to include default_schema + it 'should switch out the default schema rather than public' do + expect(connection.schema_search_path).not_to include default_tenant end - it "should still switch to the switched schema" do - expect(connection.schema_search_path).to start_with %{"#{schema1}"} + it 'should still switch to the switched schema' do + expect(connection.schema_search_path).to start_with %("#{schema1}") end end - context "persistent_schemas", :persistent_schemas => true do - - before{ subject.switch!(schema1) } + context 'persistent_schemas', persistent_schemas: true do + before { subject.switch!(schema1) } - it "maintains the persistent schemas in the schema_search_path" do - expect(connection.schema_search_path).to end_with persistent_schemas.map { |schema| %{"#{schema}"} }.join(', ') + it 'maintains the persistent schemas in the schema_search_path' do + expect(connection.schema_search_path).to end_with persistent_schemas.map { |schema| %("#{schema}") }.join(', ') end - it "prioritizes the switched schema to front of schema_search_path" do - expect(connection.schema_search_path).to start_with %{"#{schema1}"} + it 'prioritizes the switched schema to front of schema_search_path' do + expect(connection.schema_search_path).to start_with %("#{schema1}") end end end - describe "#current" do - it "should return the current schema name" do + describe '#current' do + it 'should return the current schema name' do subject.switch!(schema1) expect(subject.current).to eq(schema1) end - context "persistent_schemas", :persistent_schemas => true do - it "should exlude persistent_schemas" do + context 'persistent_schemas', persistent_schemas: true do + it 'should exlude persistent_schemas' do subject.switch!(schema1) expect(subject.current).to eq(schema1) end diff --git a/spec/integration/apartment_rake_integration_spec.rb b/spec/integration/apartment_rake_integration_spec.rb index 4b9b098d..fbfd0474 100644 --- a/spec/integration/apartment_rake_integration_spec.rb +++ b/spec/integration/apartment_rake_integration_spec.rb @@ -1,8 +1,9 @@ +# frozen_string_literal: true + require 'spec_helper' require 'rake' -describe "apartment rake tasks", database: :postgresql do - +describe 'apartment rake tasks', database: :postgresql do before do @rake = Rake::Application.new Rake.application = @rake @@ -18,8 +19,8 @@ Apartment.configure do |config| config.use_schemas = true - config.excluded_models = ["Company"] - config.tenant_names = lambda{ Company.pluck(:database) } + config.excluded_models = ['Company'] + config.tenant_names = -> { Company.pluck(:database) } end Apartment::Tenant.reload!(config) @@ -29,40 +30,39 @@ after { Rake.application = nil } - context "with x number of databases" do - - let(:x){ 1 + rand(5) } # random number of dbs to create - let(:db_names){ x.times.map{ Apartment::Test.next_db } } - let!(:company_count){ db_names.length } + context 'with x number of databases' do + let(:x) { rand(1..5) } # random number of dbs to create + let(:db_names) { x.times.map { Apartment::Test.next_db } } + let!(:company_count) { db_names.length } before do db_names.collect do |db_name| Apartment::Tenant.create(db_name) - Company.create :database => db_name + Company.create database: db_name end end after do - db_names.each{ |db| Apartment::Tenant.drop(db) } + db_names.each { |db| Apartment::Tenant.drop(db) } Company.delete_all end - context "with ActiveRecord below 5.2.0" do + context 'with ActiveRecord below 5.2.0' do before do - allow(ActiveRecord::Migrator).to receive(:migrations_paths) { %w(spec/dummy/db/migrate) } + allow(ActiveRecord::Migrator).to receive(:migrations_paths) { %w[spec/dummy/db/migrate] } allow(Apartment::Migrator).to receive(:activerecord_below_5_2?) { true } end - describe "#migrate" do - it "should migrate all databases" do + describe '#migrate' do + it 'should migrate all databases' do expect(ActiveRecord::Migrator).to receive(:migrate).exactly(company_count).times @rake['apartment:migrate'].invoke end end - describe "#rollback" do - it "should rollback all dbs" do + describe '#rollback' do + it 'should rollback all dbs' do expect(ActiveRecord::Migrator).to receive(:rollback).exactly(company_count).times @rake['apartment:rollback'].invoke @@ -70,15 +70,15 @@ end end - context "with ActiveRecord above or equal to 5.2.0" do + context 'with ActiveRecord above or equal to 5.2.0' do let(:migration_context_double) { double(:migration_context) } before do allow(Apartment::Migrator).to receive(:activerecord_below_5_2?) { false } end - describe "#migrate" do - it "should migrate all databases" do + describe '#migrate' do + it 'should migrate all databases' do allow(ActiveRecord::Base.connection).to receive(:migration_context) { migration_context_double } expect(migration_context_double).to receive(:migrate).exactly(company_count).times @@ -86,8 +86,8 @@ end end - describe "#rollback" do - it "should rollback all dbs" do + describe '#rollback' do + it 'should rollback all dbs' do allow(ActiveRecord::Base.connection).to receive(:migration_context) { migration_context_double } expect(migration_context_double).to receive(:rollback).exactly(company_count).times @@ -96,8 +96,8 @@ end end - describe "apartment:seed" do - it "should seed all databases" do + describe 'apartment:seed' do + it 'should seed all databases' do expect(Apartment::Tenant).to receive(:seed).exactly(company_count).times @rake['apartment:seed'].invoke diff --git a/spec/integration/query_caching_spec.rb b/spec/integration/query_caching_spec.rb index 7bf08996..6026e5a1 100644 --- a/spec/integration/query_caching_spec.rb +++ b/spec/integration/query_caching_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'query caching' do @@ -6,8 +8,8 @@ before do Apartment.configure do |config| - config.excluded_models = ["Company"] - config.tenant_names = lambda{ Company.pluck(:database) } + config.excluded_models = ['Company'] + config.tenant_names = -> { Company.pluck(:database) } config.use_schemas = true end @@ -20,7 +22,7 @@ end after do - db_names.each{ |db| Apartment::Tenant.drop(db) } + db_names.each { |db| Apartment::Tenant.drop(db) } Apartment::Tenant.reset Company.delete_all end @@ -34,10 +36,10 @@ ActiveRecord::Base.connection.enable_query_cache! Apartment::Tenant.switch! db_names.first - expect(User.find_by_name(db_names.first).name).to eq(db_names.first) + expect(User.find_by(name: db_names.first).name).to eq(db_names.first) Apartment::Tenant.switch! db_names.last - expect(User.find_by_name(db_names.first)).to be_nil + expect(User.find_by(name: db_names.first)).to be_nil end end @@ -46,8 +48,8 @@ before do Apartment.configure do |config| - config.excluded_models = ["Company"] - config.tenant_names = lambda{ Company.pluck(:database) } + config.excluded_models = ['Company'] + config.tenant_names = -> { Company.pluck(:database) } config.use_schemas = false end @@ -66,7 +68,7 @@ Company.delete_all end - it "configuration value is kept after switching databases" do + it 'configuration value is kept after switching databases' do ActiveRecord::Base.connection.enable_query_cache! Apartment::Tenant.switch! db_name diff --git a/spec/integration/use_within_an_engine_spec.rb b/spec/integration/use_within_an_engine_spec.rb index 904a9238..072efac6 100644 --- a/spec/integration/use_within_an_engine_spec.rb +++ b/spec/integration/use_within_an_engine_spec.rb @@ -1,7 +1,8 @@ -describe 'using apartment within an engine' do +# frozen_string_literal: true +describe 'using apartment within an engine' do before do - engine_path = Pathname.new(File.expand_path('../../dummy_engine', __FILE__)) + engine_path = Pathname.new(File.expand_path('../dummy_engine', __dir__)) require engine_path.join('test/dummy/config/application') @rake = Rake::Application.new Rake.application = @rake @@ -10,11 +11,11 @@ end it 'sucessfully runs rake db:migrate in the engine root' do - expect{ Rake::Task['db:migrate'].invoke }.to_not raise_error + expect { Rake::Task['db:migrate'].invoke }.to_not raise_error end it 'sucessfully runs rake app:db:migrate in the engine root' do - expect{ Rake::Task['app:db:migrate'].invoke }.to_not raise_error + expect { Rake::Task['app:db:migrate'].invoke }.to_not raise_error end context 'when Apartment.db_migrate_tenants is false' do @@ -24,5 +25,4 @@ Rake::Task['db:migrate'].invoke end end - end diff --git a/spec/schemas/v1.rb b/spec/schemas/v1.rb index b5e6a796..052f5663 100644 --- a/spec/schemas/v1.rb +++ b/spec/schemas/v1.rb @@ -1,4 +1,4 @@ -# encoding: UTF-8 +# frozen_string_literal: true # This file is auto-generated from the current state of the database. Instead # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. @@ -11,6 +11,5 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 0) do - +ActiveRecord::Schema.define(version: 0) do end diff --git a/spec/schemas/v2.rb b/spec/schemas/v2.rb index c6eeaba1..baf7b998 100644 --- a/spec/schemas/v2.rb +++ b/spec/schemas/v2.rb @@ -1,4 +1,4 @@ -# encoding: UTF-8 +# frozen_string_literal: true # This file is auto-generated from the current state of the database. Instead # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. @@ -11,33 +11,31 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20110613152810) do - - create_table "companies", :force => true do |t| - t.boolean "dummy" - t.string "database" +ActiveRecord::Schema.define(version: 20110613152810) do + create_table 'companies', force: true do |t| + t.boolean 'dummy' + t.string 'database' end - create_table "delayed_jobs", :force => true do |t| - t.integer "priority", :default => 0 - t.integer "attempts", :default => 0 - t.text "handler" - t.text "last_error" - t.datetime "run_at" - t.datetime "locked_at" - t.datetime "failed_at" - t.string "locked_by" - t.datetime "created_at" - t.datetime "updated_at" - t.string "queue" + create_table 'delayed_jobs', force: true do |t| + t.integer 'priority', default: 0 + t.integer 'attempts', default: 0 + t.text 'handler' + t.text 'last_error' + t.datetime 'run_at' + t.datetime 'locked_at' + t.datetime 'failed_at' + t.string 'locked_by' + t.datetime 'created_at' + t.datetime 'updated_at' + t.string 'queue' end - add_index "delayed_jobs", ["priority", "run_at"], :name => "delayed_jobs_priority" + add_index 'delayed_jobs', ['priority', 'run_at'], name: 'delayed_jobs_priority' - create_table "users", :force => true do |t| - t.string "name" - t.datetime "birthdate" - t.string "sex" + create_table 'users', force: true do |t| + t.string 'name' + t.datetime 'birthdate' + t.string 'sex' end - end diff --git a/spec/schemas/v3.rb b/spec/schemas/v3.rb index 4dbc011d..2cd09a57 100644 --- a/spec/schemas/v3.rb +++ b/spec/schemas/v3.rb @@ -1,4 +1,4 @@ -# encoding: UTF-8 +# frozen_string_literal: true # This file is auto-generated from the current state of the database. Instead # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. @@ -11,39 +11,37 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20111202022214) do - - create_table "books", :force => true do |t| - t.string "name" - t.integer "pages" - t.datetime "published" +ActiveRecord::Schema.define(version: 20111202022214) do + create_table 'books', force: true do |t| + t.string 'name' + t.integer 'pages' + t.datetime 'published' end - create_table "companies", :force => true do |t| - t.boolean "dummy" - t.string "database" + create_table 'companies', force: true do |t| + t.boolean 'dummy' + t.string 'database' end - create_table "delayed_jobs", :force => true do |t| - t.integer "priority", :default => 0 - t.integer "attempts", :default => 0 - t.text "handler" - t.text "last_error" - t.datetime "run_at" - t.datetime "locked_at" - t.datetime "failed_at" - t.string "locked_by" - t.datetime "created_at" - t.datetime "updated_at" - t.string "queue" + create_table 'delayed_jobs', force: true do |t| + t.integer 'priority', default: 0 + t.integer 'attempts', default: 0 + t.text 'handler' + t.text 'last_error' + t.datetime 'run_at' + t.datetime 'locked_at' + t.datetime 'failed_at' + t.string 'locked_by' + t.datetime 'created_at' + t.datetime 'updated_at' + t.string 'queue' end - add_index "delayed_jobs", ["priority", "run_at"], :name => "delayed_jobs_priority" + add_index 'delayed_jobs', ['priority', 'run_at'], name: 'delayed_jobs_priority' - create_table "users", :force => true do |t| - t.string "name" - t.datetime "birthdate" - t.string "sex" + create_table 'users', force: true do |t| + t.string 'name' + t.datetime 'birthdate' + t.string 'sex' end - end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 73cca8f4..f5ba6bbe 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + $LOAD_PATH.unshift(File.dirname(__FILE__)) # Configure Rails Environment -ENV["RAILS_ENV"] = "test" +ENV['RAILS_ENV'] = 'test' -require File.expand_path("../dummy/config/environment.rb", __FILE__) +require File.expand_path('dummy/config/environment.rb', __dir__) # Loading dummy applications affects table_name of each excluded models # defined in `spec/dummy/config/initializers/apartment.rb`. @@ -16,19 +18,22 @@ klass.reset_table_name end -require "rspec/rails" +require 'rspec/rails' require 'capybara/rspec' require 'capybara/rails' +# rubocop:disable Lint/ConstantDefinitionInBlock begin require 'pry' - silence_warnings{ IRB = Pry } + silence_warnings { IRB = Pry } rescue LoadError + nil end +# rubocop:enable Lint/ConstantDefinitionInBlock ActionMailer::Base.delivery_method = :test ActionMailer::Base.perform_deliveries = true -ActionMailer::Base.default_url_options[:host] = "test.com" +ActionMailer::Base.default_url_options[:host] = 'test.com' Rails.backtrace_cleaner.remove_silencers! @@ -36,7 +41,6 @@ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } RSpec.configure do |config| - config.include RSpec::Integration::CapybaraSessions, type: :request config.include Apartment::Spec::Setup diff --git a/spec/support/apartment_helpers.rb b/spec/support/apartment_helpers.rb index 795f172a..0641b13f 100644 --- a/spec/support/apartment_helpers.rb +++ b/spec/support/apartment_helpers.rb @@ -1,22 +1,27 @@ +# frozen_string_literal: true + module Apartment module Test - + # rubocop:disable Style/ModuleFunction extend self + # rubocop:enable Style/ModuleFunction def reset Apartment.excluded_models = nil Apartment.use_schemas = nil Apartment.seed_after_create = nil - Apartment.default_schema = nil + Apartment.default_tenant = nil end def next_db @x ||= 0 - "db%d" % @x += 1 + format('db%d', db_idx: @x += 1) end def drop_schema(schema) - ActiveRecord::Base.connection.execute("DROP SCHEMA IF EXISTS #{schema} CASCADE") rescue true + ActiveRecord::Base.connection.execute("DROP SCHEMA IF EXISTS #{schema} CASCADE") + rescue StandardError => _e + true end # Use this if you don't want to import schema.rb etc... but need the postgres schema to exist @@ -28,7 +33,7 @@ def create_schema(schema) def load_schema(version = 3) file = File.expand_path("../../schemas/v#{version}.rb", __FILE__) - silence_warnings{ load(file) } + silence_warnings { load(file) } end def migrate @@ -38,6 +43,5 @@ def migrate def rollback ActiveRecord::Migrator.rollback(Rails.root + ActiveRecord::Migrator.migrations_path) end - end end diff --git a/spec/support/capybara_sessions.rb b/spec/support/capybara_sessions.rb index 727d6c07..def4985c 100644 --- a/spec/support/capybara_sessions.rb +++ b/spec/support/capybara_sessions.rb @@ -1,15 +1,15 @@ +# frozen_string_literal: true + module RSpec module Integration module CapybaraSessions - - def in_new_session(&block) + def in_new_session(&_block) yield new_session end - + def new_session Capybara::Session.new(Capybara.current_driver, Capybara.app) end - end end -end \ No newline at end of file +end diff --git a/spec/support/config.rb b/spec/support/config.rb index 999279e5..ce91ad1a 100644 --- a/spec/support/config.rb +++ b/spec/support/config.rb @@ -1,10 +1,13 @@ +# frozen_string_literal: true + require 'yaml' module Apartment module Test - def self.config + # rubocop:disable Security/YAMLLoad @config ||= YAML.load(ERB.new(IO.read('spec/config/database.yml')).result) + # rubocop:enable Security/YAMLLoad end end -end \ No newline at end of file +end diff --git a/spec/support/contexts.rb b/spec/support/contexts.rb index ffb10a04..6581f8b0 100644 --- a/spec/support/contexts.rb +++ b/spec/support/contexts.rb @@ -1,22 +1,24 @@ +# frozen_string_literal: true + # Some shared contexts for specs -shared_context "with default schema", :default_schema => true do - let(:default_schema){ Apartment::Test.next_db } +shared_context 'with default schema', default_tenant: true do + let(:default_tenant) { Apartment::Test.next_db } before do - Apartment::Test.create_schema(default_schema) - Apartment.default_schema = default_schema + Apartment::Test.create_schema(default_tenant) + Apartment.default_tenant = default_tenant end after do - # resetting default_schema so we can drop and any further resets won't try to access droppped schema - Apartment.default_schema = nil - Apartment::Test.drop_schema(default_schema) + # resetting default_tenant so we can drop and any further resets won't try to access droppped schema + Apartment.default_tenant = nil + Apartment::Test.drop_schema(default_tenant) end end # Some default setup for elevator specs -shared_context "elevators", elevator: true do +shared_context 'elevators', elevator: true do let(:company1) { mock_model(Company, database: db1).as_null_object } let(:company2) { mock_model(Company, database: db2).as_null_object } @@ -37,16 +39,16 @@ end end -shared_context "persistent_schemas", :persistent_schemas => true do - let(:persistent_schemas){ ['hstore', 'postgis'] } +shared_context 'persistent_schemas', persistent_schemas: true do + let(:persistent_schemas) { %w[hstore postgis] } before do - persistent_schemas.map{|schema| subject.create(schema) } + persistent_schemas.map { |schema| subject.create(schema) } Apartment.persistent_schemas = persistent_schemas end after do Apartment.persistent_schemas = [] - persistent_schemas.map{|schema| subject.drop(schema) } + persistent_schemas.map { |schema| subject.drop(schema) } end -end \ No newline at end of file +end diff --git a/spec/support/requirements.rb b/spec/support/requirements.rb index dc856c9e..0a5fdf7d 100644 --- a/spec/support/requirements.rb +++ b/spec/support/requirements.rb @@ -1,6 +1,7 @@ +# frozen_string_literal: true + module Apartment module Spec - # # Define the interface methods required to # use an adapter shared example @@ -19,16 +20,28 @@ module AdapterRequirements # Reset before dropping (can't drop a db you're connected to) subject.reset - # sometimes we manually drop these schemas in testing, don't care if we can't drop, hence rescue - subject.drop(db1) rescue true - subject.drop(db2) rescue true + # sometimes we manually drop these schemas in testing, don't care if + # we can't drop, hence rescue + begin + subject.drop(db1) + rescue StandardError => _e + true + end + + begin + subject.drop(db2) + rescue StandardError => _e + true + end end end - %w{subject tenant_names default_tenant}.each do |method| + %w[subject tenant_names default_tenant].each do |method| + next if defined?(method) + define_method method do raise "You must define a `#{method}` method in your host group" - end unless defined?(method) + end end end end diff --git a/spec/support/setup.rb b/spec/support/setup.rb index 8441da3d..109cbaef 100644 --- a/spec/support/setup.rb +++ b/spec/support/setup.rb @@ -1,18 +1,19 @@ +# frozen_string_literal: true + module Apartment module Spec module Setup - + # rubocop:disable Metrics/AbcSize def self.included(base) base.instance_eval do - let(:db1){ Apartment::Test.next_db } - let(:db2){ Apartment::Test.next_db } - let(:connection){ ActiveRecord::Base.connection } + let(:db1) { Apartment::Test.next_db } + let(:db2) { Apartment::Test.next_db } + let(:connection) { ActiveRecord::Base.connection } # This around ensures that we run these hooks before and after # any before/after hooks defined in individual tests # Otherwise these actually get run after test defined hooks around(:each) do |example| - def config db = RSpec.current_example.metadata.fetch(:database, :postgresql) @@ -41,6 +42,7 @@ def config end end end + # rubocop:enable Metrics/AbcSize end end end diff --git a/spec/tasks/apartment_rake_spec.rb b/spec/tasks/apartment_rake_spec.rb index 0f86aeee..f71727a9 100644 --- a/spec/tasks/apartment_rake_spec.rb +++ b/spec/tasks/apartment_rake_spec.rb @@ -1,10 +1,11 @@ +# frozen_string_literal: true + require 'spec_helper' require 'rake' require 'apartment/migrator' require 'apartment/tenant' -describe "apartment rake tasks" do - +describe 'apartment rake tasks' do before do @rake = Rake::Application.new Rake.application = @rake @@ -20,110 +21,104 @@ after do Rake.application = nil - ENV['VERSION'] = nil # linux users reported env variable carrying on between tests + ENV['VERSION'] = nil # linux users reported env variable carrying on between tests end after(:all) do Apartment::Test.load_schema end - let(:version){ '1234' } + let(:version) { '1234' } context 'database migration' do - - let(:tenant_names){ 3.times.map{ Apartment::Test.next_db } } - let(:tenant_count){ tenant_names.length } + let(:tenant_names) { 3.times.map { Apartment::Test.next_db } } + let(:tenant_count) { tenant_names.length } before do allow(Apartment).to receive(:tenant_names).and_return tenant_names end - describe "apartment:migrate" do + describe 'apartment:migrate' do before do - allow(ActiveRecord::Migrator).to receive(:migrate) # don't care about this + allow(ActiveRecord::Migrator).to receive(:migrate) # don't care about this end - it "should migrate public and all multi-tenant dbs" do + it 'should migrate public and all multi-tenant dbs' do expect(Apartment::Migrator).to receive(:migrate).exactly(tenant_count).times @rake['apartment:migrate'].invoke end end - describe "apartment:migrate:up" do - - context "without a version" do + describe 'apartment:migrate:up' do + context 'without a version' do before do ENV['VERSION'] = nil end - it "requires a version to migrate to" do - expect{ + it 'requires a version to migrate to' do + expect do @rake['apartment:migrate:up'].invoke - }.to raise_error("VERSION is required") + end.to raise_error('VERSION is required') end end - context "with version" do - + context 'with version' do before do ENV['VERSION'] = version end - it "migrates up to a specific version" do + it 'migrates up to a specific version' do expect(Apartment::Migrator).to receive(:run).with(:up, anything, version.to_i).exactly(tenant_count).times @rake['apartment:migrate:up'].invoke end end end - describe "apartment:migrate:down" do - - context "without a version" do + describe 'apartment:migrate:down' do + context 'without a version' do before do ENV['VERSION'] = nil end - it "requires a version to migrate to" do - expect{ + it 'requires a version to migrate to' do + expect do @rake['apartment:migrate:down'].invoke - }.to raise_error("VERSION is required") + end.to raise_error('VERSION is required') end end - context "with version" do - + context 'with version' do before do ENV['VERSION'] = version end - it "migrates up to a specific version" do + it 'migrates up to a specific version' do expect(Apartment::Migrator).to receive(:run).with(:down, anything, version.to_i).exactly(tenant_count).times @rake['apartment:migrate:down'].invoke end end end - describe "apartment:rollback" do - let(:step){ '3' } + describe 'apartment:rollback' do + let(:step) { '3' } - it "should rollback dbs" do + it 'should rollback dbs' do expect(Apartment::Migrator).to receive(:rollback).exactly(tenant_count).times @rake['apartment:rollback'].invoke end - it "should rollback dbs STEP amt" do + it 'should rollback dbs STEP amt' do expect(Apartment::Migrator).to receive(:rollback).with(anything, step.to_i).exactly(tenant_count).times ENV['STEP'] = step @rake['apartment:rollback'].invoke end end - describe "apartment:drop" do - it "should migrate public and all multi-tenant dbs" do + describe 'apartment:drop' do + it 'should migrate public and all multi-tenant dbs' do expect(Apartment::Tenant).to receive(:drop).exactly(tenant_count).times @rake['apartment:drop'].invoke end end - end end diff --git a/spec/tenant_spec.rb b/spec/tenant_spec.rb index 261185a6..e3aacf04 100644 --- a/spec/tenant_spec.rb +++ b/spec/tenant_spec.rb @@ -1,24 +1,25 @@ +# frozen_string_literal: true + require 'spec_helper' describe Apartment::Tenant do - context "using mysql", database: :mysql do - + context 'using mysql', database: :mysql do before { subject.reload!(config) } - describe "#adapter" do - it "should load mysql adapter" do + describe '#adapter' do + it 'should load mysql adapter' do subject.adapter expect(Apartment::Adapters::Mysql2Adapter).to be_a(Class) end end - # TODO this doesn't belong here, but there aren't integration tests currently for mysql + # TODO: this doesn't belong here, but there aren't integration tests currently for mysql # where to put??? - describe "exception recovery", :type => :request do + describe 'exception recovery', type: :request do before do subject.create db1 end - after{ subject.drop db1 } + after { subject.drop db1 } # it "should recover from incorrect database" do # session = Capybara::Session.new(:rack_test, Capybara.app) @@ -30,9 +31,9 @@ # end end - # TODO re-organize these tests - context "with prefix and schemas" do - describe "#create" do + # TODO: re-organize these tests + context 'with prefix and schemas' do + describe '#create' do before do Apartment.configure do |config| config.prepend_environment = true @@ -42,49 +43,55 @@ subject.reload!(config) end - after { subject.drop "db_with_prefix" rescue nil } + after do + begin + subject.drop 'db_with_prefix' + rescue StandardError => _e + nil + end + end - it "should create a new database" do - subject.create "db_with_prefix" + it 'should create a new database' do + subject.create 'db_with_prefix' end end end end - context "using postgresql", database: :postgresql do + context 'using postgresql', database: :postgresql do before do Apartment.use_schemas = true subject.reload!(config) end - describe "#adapter" do - it "should load postgresql adapter" do + describe '#adapter' do + it 'should load postgresql adapter' do expect(subject.adapter).to be_a(Apartment::Adapters::PostgresqlSchemaAdapter) end - it "raises exception with invalid adapter specified" do + it 'raises exception with invalid adapter specified' do subject.reload!(config.merge(adapter: 'unknown')) - expect { + expect do Apartment::Tenant.adapter - }.to raise_error(RuntimeError) + end.to raise_error(RuntimeError) end - context "threadsafety" do + context 'threadsafety' do before { subject.create db1 } after { subject.drop db1 } it 'has a threadsafe adapter' do subject.switch!(db1) - thread = Thread.new { expect(subject.current).to eq(Apartment.default_tenant) } + thread = Thread.new { expect(subject.current).to eq(subject.adapter.default_tenant) } thread.join expect(subject.current).to eq(db1) end end end - # TODO above spec are also with use_schemas=true - context "with schemas" do + # TODO: above spec are also with use_schemas=true + context 'with schemas' do before do Apartment.configure do |config| config.excluded_models = [] @@ -94,30 +101,28 @@ subject.create db1 end - after{ subject.drop db1 } + after { subject.drop db1 } - describe "#create" do - it "should seed data" do + describe '#create' do + it 'should seed data' do subject.switch! db1 expect(User.count).to be > 0 end end - describe "#switch!" do + describe '#switch!' do + let(:x) { rand(3) } - let(:x){ rand(3) } + context 'creating models' do + before { subject.create db2 } + after { subject.drop db2 } - context "creating models" do - - before{ subject.create db2 } - after{ subject.drop db2 } - - it "should create a model instance in the current schema" do + it 'should create a model instance in the current schema' do subject.switch! db2 - db2_count = User.count + x.times{ User.create } + db2_count = User.count + x.times { User.create } subject.switch! db1 - db_count = User.count + x.times{ User.create } + db_count = User.count + x.times { User.create } subject.switch! db2 expect(User.count).to eq(db2_count) @@ -127,11 +132,10 @@ end end - context "with excluded models" do - + context 'with excluded models' do before do Apartment.configure do |config| - config.excluded_models = ["Company"] + config.excluded_models = ['Company'] end subject.init end @@ -144,12 +148,12 @@ end end - it "should create excluded models in public schema" do + it 'should create excluded models in public schema' do subject.reset # ensure we're on public schema - count = Company.count + x.times{ Company.create } + count = Company.count + x.times { Company.create } subject.switch! db1 - x.times{ Company.create } + x.times { Company.create } expect(Company.count).to eq(count + x) subject.reset expect(Company.count).to eq(count + x) @@ -158,7 +162,7 @@ end end - context "seed paths" do + context 'seed paths' do before do Apartment.configure do |config| config.excluded_models = [] @@ -167,7 +171,7 @@ end end - after{ subject.drop db1 } + after { subject.drop db1 } it 'should seed from default path' do subject.create db1 @@ -178,7 +182,7 @@ it 'should seed from custom path' do Apartment.configure do |config| - config.seed_data_file = "#{Rails.root}/db/seeds/import.rb" + config.seed_data_file = Rails.root.join('db', 'seeds', 'import.rb') end subject.create db1 subject.switch! db1 diff --git a/spec/unit/config_spec.rb b/spec/unit/config_spec.rb index 44901952..9adaf84c 100644 --- a/spec/unit/config_spec.rb +++ b/spec/unit/config_spec.rb @@ -1,11 +1,11 @@ +# frozen_string_literal: true + require 'spec_helper' describe Apartment do - - describe "#config" do - - let(:excluded_models){ ["Company"] } - let(:seed_data_file_path){ "#{Rails.root}/db/seeds/import.rb" } + describe '#config' do + let(:excluded_models) { ['Company'] } + let(:seed_data_file_path) { Rails.root.join('db', 'seeds', 'import.rb') } def tenant_names_from_array(names) names.each_with_object({}) do |tenant, hash| @@ -13,21 +13,21 @@ def tenant_names_from_array(names) end.with_indifferent_access end - it "should yield the Apartment object" do + it 'should yield the Apartment object' do Apartment.configure do |config| config.excluded_models = [] expect(config).to eq(Apartment) end end - it "should set excluded models" do + it 'should set excluded models' do Apartment.configure do |config| config.excluded_models = excluded_models end expect(Apartment.excluded_models).to eq(excluded_models) end - it "should set use_schemas" do + it 'should set use_schemas' do Apartment.configure do |config| config.excluded_models = [] config.use_schemas = false @@ -35,14 +35,14 @@ def tenant_names_from_array(names) expect(Apartment.use_schemas).to be false end - it "should set seed_data_file" do + it 'should set seed_data_file' do Apartment.configure do |config| config.seed_data_file = seed_data_file_path end expect(Apartment.seed_data_file).to eq(seed_data_file_path) end - it "should set seed_after_create" do + it 'should set seed_after_create' do Apartment.configure do |config| config.excluded_models = [] config.seed_after_create = true @@ -50,7 +50,21 @@ def tenant_names_from_array(names) expect(Apartment.seed_after_create).to be true end - context "databases" do + it 'should set tenant_presence_check' do + Apartment.configure do |config| + config.tenant_presence_check = true + end + expect(Apartment.tenant_presence_check).to be true + end + + it 'should set active_record_log' do + Apartment.configure do |config| + config.active_record_log = true + end + expect(Apartment.active_record_log).to be true + end + + context 'databases' do let(:users_conf_hash) { { port: 5444 } } before do @@ -59,54 +73,53 @@ def tenant_names_from_array(names) end end - context "tenant_names as string array" do - let(:tenant_names) { ['users', 'companies'] } + context 'tenant_names as string array' do + let(:tenant_names) { %w[users companies] } - it "should return object if it doesnt respond_to call" do + it 'should return object if it doesnt respond_to call' do expect(Apartment.tenant_names).to eq(tenant_names_from_array(tenant_names).keys) end - it "should set tenants_with_config" do + it 'should set tenants_with_config' do expect(Apartment.tenants_with_config).to eq(tenant_names_from_array(tenant_names)) end end - context "tenant_names as proc returning an array" do - let(:tenant_names) { lambda { ['users', 'companies'] } } + context 'tenant_names as proc returning an array' do + let(:tenant_names) { -> { %w[users companies] } } - it "should return object if it doesnt respond_to call" do + it 'should return object if it doesnt respond_to call' do expect(Apartment.tenant_names).to eq(tenant_names_from_array(tenant_names.call).keys) end - it "should set tenants_with_config" do + it 'should set tenants_with_config' do expect(Apartment.tenants_with_config).to eq(tenant_names_from_array(tenant_names.call)) end end - context "tenant_names as Hash" do + context 'tenant_names as Hash' do let(:tenant_names) { { users: users_conf_hash }.with_indifferent_access } - it "should return object if it doesnt respond_to call" do + it 'should return object if it doesnt respond_to call' do expect(Apartment.tenant_names).to eq(tenant_names.keys) end - it "should set tenants_with_config" do + it 'should set tenants_with_config' do expect(Apartment.tenants_with_config).to eq(tenant_names) end end - context "tenant_names as proc returning a Hash" do - let(:tenant_names) { lambda { { users: users_conf_hash }.with_indifferent_access } } + context 'tenant_names as proc returning a Hash' do + let(:tenant_names) { -> { { users: users_conf_hash }.with_indifferent_access } } - it "should return object if it doesnt respond_to call" do + it 'should return object if it doesnt respond_to call' do expect(Apartment.tenant_names).to eq(tenant_names.call.keys) end - it "should set tenants_with_config" do + it 'should set tenants_with_config' do expect(Apartment.tenants_with_config).to eq(tenant_names.call) end end end - end end diff --git a/spec/unit/elevators/domain_spec.rb b/spec/unit/elevators/domain_spec.rb index cbdf9fb2..520b315c 100644 --- a/spec/unit/elevators/domain_spec.rb +++ b/spec/unit/elevators/domain_spec.rb @@ -1,29 +1,30 @@ +# frozen_string_literal: true + require 'spec_helper' require 'apartment/elevators/domain' describe Apartment::Elevators::Domain do + subject(:elevator) { described_class.new(proc {}) } - subject(:elevator){ described_class.new(Proc.new{}) } - - describe "#parse_tenant_name" do - it "parses the host for a domain name" do + describe '#parse_tenant_name' do + it 'parses the host for a domain name' do request = ActionDispatch::Request.new('HTTP_HOST' => 'example.com') expect(elevator.parse_tenant_name(request)).to eq('example') end - it "ignores a www prefix and domain suffix" do + it 'ignores a www prefix and domain suffix' do request = ActionDispatch::Request.new('HTTP_HOST' => 'www.example.bc.ca') expect(elevator.parse_tenant_name(request)).to eq('example') end - it "returns nil if there is no host" do + it 'returns nil if there is no host' do request = ActionDispatch::Request.new('HTTP_HOST' => '') expect(elevator.parse_tenant_name(request)).to be_nil end end - describe "#call" do - it "switches to the proper tenant" do + describe '#call' do + it 'switches to the proper tenant' do expect(Apartment::Tenant).to receive(:switch).with('example') elevator.call('HTTP_HOST' => 'www.example.com') diff --git a/spec/unit/elevators/first_subdomain_spec.rb b/spec/unit/elevators/first_subdomain_spec.rb index 899b7bba..f608d834 100644 --- a/spec/unit/elevators/first_subdomain_spec.rb +++ b/spec/unit/elevators/first_subdomain_spec.rb @@ -1,24 +1,26 @@ +# frozen_string_literal: true + require 'spec_helper' require 'apartment/elevators/first_subdomain' describe Apartment::Elevators::FirstSubdomain do - describe "subdomain" do - subject { described_class.new("test").parse_tenant_name(request) } - let(:request) { double(:request, :host => "#{subdomain}.example.com") } + describe 'subdomain' do + subject { described_class.new('test').parse_tenant_name(request) } + let(:request) { double(:request, host: "#{subdomain}.example.com") } - context "one subdomain" do - let(:subdomain) { "test" } - it { is_expected.to eq("test") } + context 'one subdomain' do + let(:subdomain) { 'test' } + it { is_expected.to eq('test') } end - context "nested subdomains" do - let(:subdomain) { "test1.test2" } - it { is_expected.to eq("test1") } + context 'nested subdomains' do + let(:subdomain) { 'test1.test2' } + it { is_expected.to eq('test1') } end - - context "no subdomain" do + + context 'no subdomain' do let(:subdomain) { nil } it { is_expected.to eq(nil) } end end -end \ No newline at end of file +end diff --git a/spec/unit/elevators/generic_spec.rb b/spec/unit/elevators/generic_spec.rb index 6278dfe7..f4112e78 100644 --- a/spec/unit/elevators/generic_spec.rb +++ b/spec/unit/elevators/generic_spec.rb @@ -1,49 +1,52 @@ +# frozen_string_literal: true + require 'spec_helper' require 'apartment/elevators/generic' describe Apartment::Elevators::Generic do - + # rubocop:disable Lint/ConstantDefinitionInBlock class MyElevator < described_class def parse_tenant_name(*) 'tenant2' end end + # rubocop:enable Lint/ConstantDefinitionInBlock - subject(:elevator){ described_class.new(Proc.new{}) } + subject(:elevator) { described_class.new(proc {}) } - describe "#call" do - it "calls the processor if given" do - elevator = described_class.new(Proc.new{}, Proc.new{'tenant1'}) + describe '#call' do + it 'calls the processor if given' do + elevator = described_class.new(proc {}, proc { 'tenant1' }) expect(Apartment::Tenant).to receive(:switch).with('tenant1') elevator.call('HTTP_HOST' => 'foo.bar.com') end - it "raises if parse_tenant_name not implemented" do - expect { + it 'raises if parse_tenant_name not implemented' do + expect do elevator.call('HTTP_HOST' => 'foo.bar.com') - }.to raise_error(RuntimeError) + end.to raise_error(RuntimeError) end - it "switches to the parsed db_name" do - elevator = MyElevator.new(Proc.new{}) + it 'switches to the parsed db_name' do + elevator = MyElevator.new(proc {}) expect(Apartment::Tenant).to receive(:switch).with('tenant2') elevator.call('HTTP_HOST' => 'foo.bar.com') end - it "calls the block implementation of `switch`" do - elevator = MyElevator.new(Proc.new{}, Proc.new{'tenant2'}) + it 'calls the block implementation of `switch`' do + elevator = MyElevator.new(proc {}, proc { 'tenant2' }) expect(Apartment::Tenant).to receive(:switch).with('tenant2').and_yield elevator.call('HTTP_HOST' => 'foo.bar.com') end - it "does not call `switch` if no database given" do - app = Proc.new{} - elevator = MyElevator.new(app, Proc.new{}) + it 'does not call `switch` if no database given' do + app = proc {} + elevator = MyElevator.new(app, proc {}) expect(Apartment::Tenant).not_to receive(:switch) expect(app).to receive :call diff --git a/spec/unit/elevators/host_hash_spec.rb b/spec/unit/elevators/host_hash_spec.rb index c748b70d..6fc2f72b 100644 --- a/spec/unit/elevators/host_hash_spec.rb +++ b/spec/unit/elevators/host_hash_spec.rb @@ -1,29 +1,30 @@ +# frozen_string_literal: true + require 'spec_helper' require 'apartment/elevators/host_hash' describe Apartment::Elevators::HostHash do + subject(:elevator) { Apartment::Elevators::HostHash.new(proc {}, 'example.com' => 'example_tenant') } - subject(:elevator){ Apartment::Elevators::HostHash.new(Proc.new{}, 'example.com' => 'example_tenant') } - - describe "#parse_tenant_name" do - it "parses the host for a domain name" do + describe '#parse_tenant_name' do + it 'parses the host for a domain name' do request = ActionDispatch::Request.new('HTTP_HOST' => 'example.com') expect(elevator.parse_tenant_name(request)).to eq('example_tenant') end - it "raises TenantNotFound exception if there is no host" do + it 'raises TenantNotFound exception if there is no host' do request = ActionDispatch::Request.new('HTTP_HOST' => '') expect { elevator.parse_tenant_name(request) }.to raise_error(Apartment::TenantNotFound) end - it "raises TenantNotFound exception if there is no database associated to current host" do + it 'raises TenantNotFound exception if there is no database associated to current host' do request = ActionDispatch::Request.new('HTTP_HOST' => 'example2.com') expect { elevator.parse_tenant_name(request) }.to raise_error(Apartment::TenantNotFound) end end - describe "#call" do - it "switches to the proper tenant" do + describe '#call' do + it 'switches to the proper tenant' do expect(Apartment::Tenant).to receive(:switch).with('example_tenant') elevator.call('HTTP_HOST' => 'example.com') diff --git a/spec/unit/elevators/host_spec.rb b/spec/unit/elevators/host_spec.rb index 96ea057c..e0cb9c3c 100644 --- a/spec/unit/elevators/host_spec.rb +++ b/spec/unit/elevators/host_spec.rb @@ -1,87 +1,87 @@ +# frozen_string_literal: true + require 'spec_helper' require 'apartment/elevators/host' describe Apartment::Elevators::Host do + subject(:elevator) { described_class.new(proc {}) } - subject(:elevator){ described_class.new(Proc.new{}) } - - describe "#parse_tenant_name" do - - it "should return nil when no host" do + describe '#parse_tenant_name' do + it 'should return nil when no host' do request = ActionDispatch::Request.new('HTTP_HOST' => '') expect(elevator.parse_tenant_name(request)).to be_nil end - context "assuming no ignored_first_subdomains" do + context 'assuming no ignored_first_subdomains' do before { allow(described_class).to receive(:ignored_first_subdomains).and_return([]) } - context "with 3 parts" do - it "should return the whole host" do + context 'with 3 parts' do + it 'should return the whole host' do request = ActionDispatch::Request.new('HTTP_HOST' => 'foo.bar.com') expect(elevator.parse_tenant_name(request)).to eq('foo.bar.com') end end - context "with 6 parts" do - it "should return the whole host" do + context 'with 6 parts' do + it 'should return the whole host' do request = ActionDispatch::Request.new('HTTP_HOST' => 'one.two.three.foo.bar.com') expect(elevator.parse_tenant_name(request)).to eq('one.two.three.foo.bar.com') end end end - context "assuming ignored_first_subdomains is set" do - before { allow(described_class).to receive(:ignored_first_subdomains).and_return(%w{www foo}) } + context 'assuming ignored_first_subdomains is set' do + before { allow(described_class).to receive(:ignored_first_subdomains).and_return(%w[www foo]) } - context "with 3 parts" do - it "should return host without www" do + context 'with 3 parts' do + it 'should return host without www' do request = ActionDispatch::Request.new('HTTP_HOST' => 'www.bar.com') expect(elevator.parse_tenant_name(request)).to eq('bar.com') end - it "should return host without foo" do + it 'should return host without foo' do request = ActionDispatch::Request.new('HTTP_HOST' => 'foo.bar.com') expect(elevator.parse_tenant_name(request)).to eq('bar.com') end end - context "with 6 parts" do - it "should return host without www" do + context 'with 6 parts' do + it 'should return host without www' do request = ActionDispatch::Request.new('HTTP_HOST' => 'www.one.two.three.foo.bar.com') expect(elevator.parse_tenant_name(request)).to eq('one.two.three.foo.bar.com') end - it "should return host without www" do + it 'should return host without www' do request = ActionDispatch::Request.new('HTTP_HOST' => 'foo.one.two.three.bar.com') expect(elevator.parse_tenant_name(request)).to eq('one.two.three.bar.com') end end end - context "assuming localhost" do - it "should return localhost" do + context 'assuming localhost' do + it 'should return localhost' do request = ActionDispatch::Request.new('HTTP_HOST' => 'localhost') expect(elevator.parse_tenant_name(request)).to eq('localhost') end end - context "assuming ip address" do - it "should return the ip address" do + context 'assuming ip address' do + it 'should return the ip address' do request = ActionDispatch::Request.new('HTTP_HOST' => '127.0.0.1') expect(elevator.parse_tenant_name(request)).to eq('127.0.0.1') end end end - describe "#call" do - it "switches to the proper tenant" do + describe '#call' do + it 'switches to the proper tenant' do allow(described_class).to receive(:ignored_first_subdomains).and_return([]) expect(Apartment::Tenant).to receive(:switch).with('foo.bar.com') elevator.call('HTTP_HOST' => 'foo.bar.com') end - it "ignores ignored_first_subdomains" do - allow(described_class).to receive(:ignored_first_subdomains).and_return(%w{foo}) + it 'ignores ignored_first_subdomains' do + allow(described_class).to receive(:ignored_first_subdomains).and_return(%w[foo]) expect(Apartment::Tenant).to receive(:switch).with('bar.com') elevator.call('HTTP_HOST' => 'foo.bar.com') end diff --git a/spec/unit/elevators/subdomain_spec.rb b/spec/unit/elevators/subdomain_spec.rb index 1df11466..4c66ce67 100644 --- a/spec/unit/elevators/subdomain_spec.rb +++ b/spec/unit/elevators/subdomain_spec.rb @@ -1,70 +1,71 @@ +# frozen_string_literal: true + require 'spec_helper' require 'apartment/elevators/subdomain' describe Apartment::Elevators::Subdomain do + subject(:elevator) { described_class.new(proc {}) } - subject(:elevator){ described_class.new(Proc.new{}) } - - describe "#parse_tenant_name" do - context "assuming one tld" do - it "should parse subdomain" do + describe '#parse_tenant_name' do + context 'assuming one tld' do + it 'should parse subdomain' do request = ActionDispatch::Request.new('HTTP_HOST' => 'foo.bar.com') expect(elevator.parse_tenant_name(request)).to eq('foo') end - it "should return nil when no subdomain" do + it 'should return nil when no subdomain' do request = ActionDispatch::Request.new('HTTP_HOST' => 'bar.com') expect(elevator.parse_tenant_name(request)).to be_nil end end - context "assuming two tlds" do - it "should parse subdomain in the third level domain" do + context 'assuming two tlds' do + it 'should parse subdomain in the third level domain' do request = ActionDispatch::Request.new('HTTP_HOST' => 'foo.bar.co.uk') - expect(elevator.parse_tenant_name(request)).to eq("foo") + expect(elevator.parse_tenant_name(request)).to eq('foo') end - it "should return nil when no subdomain in the third level domain" do + it 'should return nil when no subdomain in the third level domain' do request = ActionDispatch::Request.new('HTTP_HOST' => 'bar.co.uk') expect(elevator.parse_tenant_name(request)).to be_nil end end - context "assuming two subdomains" do - it "should parse two subdomains in the two level domain" do + context 'assuming two subdomains' do + it 'should parse two subdomains in the two level domain' do request = ActionDispatch::Request.new('HTTP_HOST' => 'foo.xyz.bar.com') - expect(elevator.parse_tenant_name(request)).to eq("foo") + expect(elevator.parse_tenant_name(request)).to eq('foo') end - it "should parse two subdomains in the third level domain" do + it 'should parse two subdomains in the third level domain' do request = ActionDispatch::Request.new('HTTP_HOST' => 'foo.xyz.bar.co.uk') - expect(elevator.parse_tenant_name(request)).to eq("foo") + expect(elevator.parse_tenant_name(request)).to eq('foo') end end - context "assuming localhost" do - it "should return nil for localhost" do + context 'assuming localhost' do + it 'should return nil for localhost' do request = ActionDispatch::Request.new('HTTP_HOST' => 'localhost') expect(elevator.parse_tenant_name(request)).to be_nil end end - context "assuming ip address" do - it "should return nil for an ip address" do + context 'assuming ip address' do + it 'should return nil for an ip address' do request = ActionDispatch::Request.new('HTTP_HOST' => '127.0.0.1') expect(elevator.parse_tenant_name(request)).to be_nil end end end - describe "#call" do - it "switches to the proper tenant" do + describe '#call' do + it 'switches to the proper tenant' do expect(Apartment::Tenant).to receive(:switch).with('tenant1') elevator.call('HTTP_HOST' => 'tenant1.example.com') end - it "ignores excluded subdomains" do - described_class.excluded_subdomains = %w{foo} + it 'ignores excluded subdomains' do + described_class.excluded_subdomains = %w[foo] expect(Apartment::Tenant).not_to receive(:switch) diff --git a/spec/unit/migrator_spec.rb b/spec/unit/migrator_spec.rb index 242b6b97..8cbcb9e2 100644 --- a/spec/unit/migrator_spec.rb +++ b/spec/unit/migrator_spec.rb @@ -1,21 +1,22 @@ +# frozen_string_literal: true + require 'spec_helper' require 'apartment/migrator' describe Apartment::Migrator do - - let(:tenant){ Apartment::Test.next_db } + let(:tenant) { Apartment::Test.next_db } # Don't need a real switch here, just testing behaviour before { allow(Apartment::Tenant.adapter).to receive(:connect_to_new) } - context "with ActiveRecord below 5.2.0", skip: ActiveRecord.version >= Gem::Version.new("5.2.0") do + context 'with ActiveRecord below 5.2.0', skip: ActiveRecord.version >= Gem::Version.new('5.2.0') do before do - allow(ActiveRecord::Migrator).to receive(:migrations_paths) { %w(spec/dummy/db/migrate) } + allow(ActiveRecord::Migrator).to receive(:migrations_paths) { %w[spec/dummy/db/migrate] } allow(Apartment::Migrator).to receive(:activerecord_below_5_2?) { true } end - describe "::migrate" do - it "switches and migrates" do + describe '::migrate' do + it 'switches and migrates' do expect(Apartment::Tenant).to receive(:switch).with(tenant).and_call_original expect(ActiveRecord::Migrator).to receive(:migrate) @@ -23,8 +24,8 @@ end end - describe "::run" do - it "switches and runs" do + describe '::run' do + it 'switches and runs' do expect(Apartment::Tenant).to receive(:switch).with(tenant).and_call_original expect(ActiveRecord::Migrator).to receive(:run).with(:up, anything, 1234) @@ -32,8 +33,8 @@ end end - describe "::rollback" do - it "switches and rolls back" do + describe '::rollback' do + it 'switches and rolls back' do expect(Apartment::Tenant).to receive(:switch).with(tenant).and_call_original expect(ActiveRecord::Migrator).to receive(:rollback).with(anything, 2) @@ -42,13 +43,13 @@ end end - context "with ActiveRecord above or equal to 5.2.0", skip: ActiveRecord.version < Gem::Version.new("5.2.0") do + context 'with ActiveRecord above or equal to 5.2.0', skip: ActiveRecord.version < Gem::Version.new('5.2.0') do before do allow(Apartment::Migrator).to receive(:activerecord_below_5_2?) { false } end - describe "::migrate" do - it "switches and migrates" do + describe '::migrate' do + it 'switches and migrates' do expect(Apartment::Tenant).to receive(:switch).with(tenant).and_call_original expect_any_instance_of(ActiveRecord::MigrationContext).to receive(:migrate) @@ -56,8 +57,8 @@ end end - describe "::run" do - it "switches and runs" do + describe '::run' do + it 'switches and runs' do expect(Apartment::Tenant).to receive(:switch).with(tenant).and_call_original expect_any_instance_of(ActiveRecord::MigrationContext).to receive(:run).with(:up, 1234) @@ -65,8 +66,8 @@ end end - describe "::rollback" do - it "switches and rolls back" do + describe '::rollback' do + it 'switches and rolls back' do expect(Apartment::Tenant).to receive(:switch).with(tenant).and_call_original expect_any_instance_of(ActiveRecord::MigrationContext).to receive(:rollback).with(2) diff --git a/spec/unit/reloader_spec.rb b/spec/unit/reloader_spec.rb index ffe30988..54bd2ff7 100644 --- a/spec/unit/reloader_spec.rb +++ b/spec/unit/reloader_spec.rb @@ -1,24 +1,24 @@ +# frozen_string_literal: true + require 'spec_helper' describe Apartment::Reloader do - - context "using postgresql schemas" do - + context 'using postgresql schemas' do before do Apartment.configure do |config| - config.excluded_models = ["Company"] + config.excluded_models = ['Company'] config.use_schemas = true end Apartment::Tenant.reload!(config) - Company.reset_table_name # ensure we're clean + Company.reset_table_name # ensure we're clean end - subject{ Apartment::Reloader.new(double("Rack::Application", :call => nil)) } + subject { Apartment::Reloader.new(double('Rack::Application', call: nil)) } - it "should initialize apartment when called" do + it 'should initialize apartment when called' do expect(Company.table_name).not_to include('public.') subject.call(double('env')) expect(Company.table_name).to include('public.') end end -end \ No newline at end of file +end