New Upstream Release - ruby-cancancan

Ready changes

Summary

Merged new upstream version: 3.5.0 (was: 3.4.0).

Resulting package

Built on 2023-06-03T10:18 (took 4m42s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases ruby-cancancan

Lintian Result

Diff

diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..b372c22
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,10 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+max_line_length = 120
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..88039a1
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,12 @@
+# These are supported funding model platforms
+
+github: [coorasse]
+patreon: # Replace with a single Patreon username
+open_collective: # Replace with a single Open Collective username
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+otechie: # Replace with a single Otechie username
+custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..08d0a5a
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,109 @@
+name: Test & lint
+on: [push, pull_request]
+
+env:
+  RAILS_ENV: test
+  PGHOST: localhost
+  PGUSER: postgres
+
+jobs:
+  tests:
+    name: Test
+    runs-on: ubuntu-latest
+
+    strategy:
+      fail-fast: false
+      matrix:
+        ruby: ['2.6', '2.7', '3.0', 'jruby', 'truffleruby']
+        gemfile: ['gemfiles/activerecord_5.0.2.gemfile', 'gemfiles/activerecord_5.1.0.gemfile', 'gemfiles/activerecord_5.2.2.gemfile', 'gemfiles/activerecord_6.0.0.gemfile', 'gemfiles/activerecord_6.1.0.gemfile', 'gemfiles/activerecord_main.gemfile']
+        include:
+          - gemfile: 'gemfiles/activerecord_7.0.0.gemfile'
+            ruby: '3.1'
+          - gemfile: 'gemfiles/activerecord_7.0.0.gemfile'
+            ruby: '3.0'
+          - gemfile: 'gemfiles/activerecord_6.1.0.gemfile'
+            ruby: '3.1'
+          - gemfile: 'gemfiles/activerecord_6.1.0.gemfile'
+            ruby: '3.0'
+        exclude:
+          - gemfile: 'gemfiles/activerecord_5.2.2.gemfile'
+            ruby: '3.0' # rails 5.2 can't run on ruby 3.0
+          - gemfile: 'gemfiles/activerecord_5.1.0.gemfile'
+            ruby: '3.0' # rails 5.1 can't run on ruby 3.0
+          - gemfile: 'gemfiles/activerecord_5.0.2.gemfile'
+            ruby: '3.0' # rails 5.0 can't run on ruby 3.0
+          - gemfile: 'gemfiles/activerecord_5.0.2.gemfile'
+            ruby: '3.0' # rails 5.0 can't run on ruby 3.0
+          - gemfile: 'gemfiles/activerecord_5.0.2.gemfile'
+            ruby: 'truffleruby' # TruffleRuby 21.0 targets Ruby 2.7, same as above
+          - gemfile: 'gemfiles/activerecord_5.1.0.gemfile'
+            ruby: 'truffleruby' # TruffleRuby 21.0 targets Ruby 2.7, same as above
+          - gemfile: 'gemfiles/activerecord_5.2.2.gemfile'
+            ruby: 'truffleruby' # TruffleRuby 21.0 targets Ruby 2.7, same as above
+          - gemfile: 'gemfiles/activerecord_main.gemfile'
+            ruby: '2.6' # rails 7+ requires ruby 3.0+
+          - gemfile: 'gemfiles/activerecord_5.0.2.gemfile'
+            ruby: 'jruby' # this *should* work - there's a test failure; it's not incompatible like the other excludes. could be an issue in Rails 5.0.2?
+          - gemfile: 'gemfiles/activerecord_6.1.0.gemfile'
+            ruby: 'jruby' # this *should* work. it seems like there's an issue with rails 6 on jruby.
+          - gemfile: 'gemfiles/activerecord_main.gemfile'
+            ruby: 'jruby' # this *should* work. it seems like there's an issue with rails 6 on jruby.
+
+    env:
+      BUNDLE_GEMFILE: ${{ matrix.gemfile }}
+
+    services:
+      postgres:
+        image: postgres
+        env:
+          POSTGRES_USER: postgres
+          POSTGRES_DB: cancan_postgresql_spec
+          POSTGRES_PASSWORD: postgres
+        options: >-
+          --health-cmd pg_isready
+          --health-interval 10s
+          --health-timeout 5s
+          --health-retries 5
+        ports: ["5432:5432"]
+
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v2
+        with:
+          fetch-depth: '20'
+
+      # https://github.com/CanCanCommunity/cancancan/pull/669#issuecomment-748019539
+      - name: Nokogiri support for Truffleruby
+        run: sudo apt-get -yqq install libxml2-dev libxslt-dev
+        if: ${{ matrix.ruby == 'truffleruby' }}
+
+      - name: Setup Ruby
+        uses: ruby/setup-ruby@v1
+        with:
+          ruby-version: ${{ matrix.ruby }}
+          bundler-cache: true
+
+      - name: Run tests on sqlite
+        run: DB=sqlite bundle exec rake
+
+      - name: Run tests on postgres
+        run: DB=postgres bundle exec rake
+
+  lint:
+    name: Lint
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v2
+        with:
+          fetch-depth: '20'
+
+      - name: Setup Ruby
+        uses: ruby/setup-ruby@v1
+        with:
+          ruby-version: 2.7
+          bundler-cache: true
+
+      - name: Run linter
+        run: bundle exec rubocop
diff --git a/.rubocop.yml b/.rubocop.yml
index 214673a..8585e75 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1,16 +1,21 @@
+inherit_from: .rubocop_todo.yml
+
 Style/Documentation:
   Enabled: false
 
 Style/NonNilCheck:
   IncludeSemanticChanges: true
 
+Style/RedundantInitialize:
+  Enabled: false
+
 Style/EmptyMethod:
   Enabled: false
 
 Style/ClassAndModuleChildren:
   Enabled: false
 
-Metrics/LineLength:
+Layout/LineLength:
   Max: 120
 
 Metrics/BlockLength:
@@ -18,6 +23,10 @@ Metrics/BlockLength:
     - 'lib/cancan/matchers.rb'
     - '**/*_spec.rb'
 
+Metrics/ClassLength:
+  Exclude:
+    - 'lib/cancan/model_adapters/active_record_adapter.rb'
+
 # TODO
 # Offense count: 2
 # Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist.
@@ -34,7 +43,10 @@ Lint/AmbiguousBlockAssociation:
   Enabled: false
 
 AllCops:
-  TargetRubyVersion: 2.2.0
+  NewCops: enable
+  SuggestExtensions: false
+  TargetRubyVersion: 3.0
   Exclude:
     - 'gemfiles/**/*'
+    - 'vendor/**/*'
     - 'Appraisals'
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
new file mode 100644
index 0000000..f6c643a
--- /dev/null
+++ b/.rubocop_todo.yml
@@ -0,0 +1,216 @@
+# This configuration was generated by
+# `rubocop --auto-gen-config`
+# on 2022-03-15 14:26:54 UTC using RuboCop version 1.26.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
+# This cop supports safe auto-correction (--auto-correct).
+# Configuration parameters: Include.
+# Include: **/*.gemspec
+Gemspec/RequireMFA:
+  Exclude:
+    - 'cancancan.gemspec'
+
+# Offense count: 1
+# Configuration parameters: Include.
+# Include: **/*.gemspec
+Gemspec/RequiredRubyVersion:
+  Exclude:
+    - 'cancancan.gemspec'
+
+# Offense count: 2
+# This cop supports safe auto-correction (--auto-correct).
+# Configuration parameters: EmptyLineBetweenMethodDefs, EmptyLineBetweenClassDefs, EmptyLineBetweenModuleDefs, AllowAdjacentOneLineDefs, NumberOfEmptyLines.
+Layout/EmptyLineBetweenDefs:
+  Exclude:
+    - 'spec/cancan/ability_spec.rb'
+
+# Offense count: 3
+# This cop supports safe auto-correction (--auto-correct).
+# Configuration parameters: EnforcedStyle, IndentationWidth.
+# SupportedStyles: aligned, indented
+Layout/LineEndStringConcatenationIndentation:
+  Exclude:
+    - 'lib/cancan/ability/rules.rb'
+    - 'lib/cancan/rule.rb'
+    - 'spec/cancan/model_adapters/active_record_adapter_spec.rb'
+
+# Offense count: 1
+# This cop supports safe auto-correction (--auto-correct).
+Lint/AmbiguousOperatorPrecedence:
+  Exclude:
+    - 'lib/cancan/controller_resource.rb'
+
+# Offense count: 77
+# Configuration parameters: AllowedMethods.
+# AllowedMethods: enums
+Lint/ConstantDefinitionInBlock:
+  Exclude:
+    - 'spec/cancan/ability_spec.rb'
+    - 'spec/cancan/controller_resource_spec.rb'
+    - 'spec/cancan/model_adapters/accessible_by_has_many_through_spec.rb'
+    - 'spec/cancan/model_adapters/accessible_by_integration_spec.rb'
+    - 'spec/cancan/model_adapters/active_record_4_adapter_spec.rb'
+    - 'spec/cancan/model_adapters/active_record_adapter_spec.rb'
+    - 'spec/cancan/model_adapters/conditions_extractor_spec.rb'
+    - 'spec/cancan/model_adapters/conditions_normalizer_spec.rb'
+    - 'spec/cancan/model_adapters/has_and_belongs_to_many_spec.rb'
+    - 'spec/cancan/rule_compressor_spec.rb'
+    - 'spec/cancan/rule_spec.rb'
+
+# Offense count: 1
+# Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches.
+Lint/DuplicateBranch:
+  Exclude:
+    - 'spec/cancan/model_adapters/active_record_adapter_spec.rb'
+
+# Offense count: 3
+# Configuration parameters: AllowComments, AllowEmptyLambdas.
+Lint/EmptyBlock:
+  Exclude:
+    - 'spec/cancan/model_adapters/conditions_normalizer_spec.rb'
+
+# Offense count: 4
+# Configuration parameters: AllowComments.
+Lint/EmptyClass:
+  Exclude:
+    - 'spec/cancan/controller_resource_spec.rb'
+    - 'spec/cancan/rule_compressor_spec.rb'
+
+# Offense count: 2
+Lint/MissingSuper:
+  Exclude:
+    - 'lib/cancan/exceptions.rb'
+    - 'lib/cancan/model_adapters/abstract_adapter.rb'
+
+# Offense count: 1
+# Configuration parameters: IgnoredMethods, CountRepeatedAttributes.
+Metrics/AbcSize:
+  Max: 18
+
+# Offense count: 1
+# Configuration parameters: Max, CountKeywordArgs.
+Metrics/ParameterLists:
+  MaxOptionalParameters: 4
+
+# Offense count: 1
+# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers.
+# SupportedStyles: snake_case, normalcase, non_integer
+# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339
+Naming/VariableNumber:
+  Exclude:
+    - 'spec/cancan/model_adapters/conditions_extractor_spec.rb'
+
+# Offense count: 2
+# This cop supports safe auto-correction (--auto-correct).
+Style/BisectedAttrAccessor:
+  Exclude:
+    - 'lib/cancan/rule.rb'
+
+# Offense count: 5
+# This cop supports safe auto-correction (--auto-correct).
+# Configuration parameters: IgnoredMethods.
+# IgnoredMethods: ==, equal?, eql?
+Style/ClassEqualityComparison:
+  Exclude:
+    - 'lib/cancan/ability.rb'
+    - 'lib/cancan/conditions_matcher.rb'
+    - 'lib/cancan/relevant.rb'
+    - 'lib/cancan/unauthorized_message_resolver.rb'
+
+# Offense count: 1
+# This cop supports unsafe auto-correction (--auto-correct-all).
+Style/CollectionCompact:
+  Exclude:
+    - 'lib/cancan/ability.rb'
+
+# Offense count: 4
+# Configuration parameters: EnforcedStyle, MaxUnannotatedPlaceholdersAllowed, IgnoredMethods.
+# SupportedStyles: annotated, template, unannotated
+Style/FormatStringToken:
+  Exclude:
+    - 'spec/cancan/ability_spec.rb'
+
+# Offense count: 9
+# This cop supports safe auto-correction (--auto-correct).
+# Configuration parameters: EnforcedStyle.
+# SupportedStyles: always, always_true, never
+Style/FrozenStringLiteralComment:
+  Exclude:
+    - 'lib/cancan/class_matcher.rb'
+    - 'lib/cancan/model_adapters/conditions_normalizer.rb'
+    - 'lib/cancan/model_adapters/sti_normalizer.rb'
+    - 'lib/cancan/model_adapters/strategies/base.rb'
+    - 'lib/cancan/model_adapters/strategies/left_join.rb'
+    - 'lib/cancan/model_adapters/strategies/subquery.rb'
+    - 'spec/cancan/model_adapters/accessible_by_has_many_through_spec.rb'
+    - 'spec/cancan/model_adapters/conditions_normalizer_spec.rb'
+    - 'spec/cancan/model_adapters/has_and_belongs_to_many_spec.rb'
+
+# Offense count: 1
+# This cop supports safe auto-correction (--auto-correct).
+# Configuration parameters: EnforcedStyle.
+# SupportedStyles: same_as_string_literals, single_quotes, double_quotes
+Style/QuotedSymbols:
+  Exclude:
+    - 'lib/cancan/exceptions.rb'
+
+# Offense count: 3
+# This cop supports safe auto-correction (--auto-correct).
+Style/RedundantBegin:
+  Exclude:
+    - 'spec/cancan/ability_spec.rb'
+
+# Offense count: 12
+# This cop supports safe auto-correction (--auto-correct).
+Style/RedundantFileExtensionInRequire:
+  Exclude:
+    - 'lib/cancan/ability.rb'
+    - 'lib/cancan/controller_resource.rb'
+    - 'lib/cancan/controller_resource_loader.rb'
+    - 'lib/cancan/rule.rb'
+    - 'lib/cancan/rules_compressor.rb'
+
+# Offense count: 1
+# This cop supports safe auto-correction (--auto-correct).
+Style/RedundantFreeze:
+  Exclude:
+    - 'lib/cancan/version.rb'
+
+# Offense count: 4
+# This cop supports safe auto-correction (--auto-correct).
+Style/RedundantRegexpEscape:
+  Exclude:
+    - 'spec/changelog_spec.rb'
+
+# Offense count: 1
+# This cop supports safe auto-correction (--auto-correct).
+# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods.
+# AllowedMethods: present?, blank?, presence, try, try!
+Style/SafeNavigation:
+  Exclude:
+    - 'lib/cancan/controller_resource_builder.rb'
+
+# Offense count: 3
+# This cop supports unsafe auto-correction (--auto-correct-all).
+Style/SlicingWithRange:
+  Exclude:
+    - 'lib/cancan/matchers.rb'
+    - 'lib/cancan/rules_compressor.rb'
+    - 'spec/cancan/rule_compressor_spec.rb'
+
+# Offense count: 2
+# This cop supports unsafe auto-correction (--auto-correct-all).
+Style/StringChars:
+  Exclude:
+    - 'spec/matchers.rb'
+
+# Offense count: 1
+# This cop supports unsafe auto-correction (--auto-correct-all).
+# Configuration parameters: Mode.
+Style/StringConcatenation:
+  Exclude:
+    - 'lib/cancan/rule.rb'
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 8ef48b7..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,57 +0,0 @@
-language: ruby
-cache: bundler
-sudo: false
-addons:
-  postgresql: "9.6"
-rvm:
-  - 2.3.5
-  - 2.4.2
-  - 2.5.1
-  - 2.6.0
-  - ruby-head
-  - jruby-9.1.9.0
-  - jruby-9.2.5.0
-  - jruby-head
-
-gemfile:
-  - gemfiles/activerecord_4.2.0.gemfile
-  - gemfiles/activerecord_5.0.2.gemfile
-  - gemfiles/activerecord_5.1.0.gemfile
-  - gemfiles/activerecord_5.2.2.gemfile
-  - gemfiles/activerecord_6.0.0.gemfile
-env:
-  - DB=sqlite
-  - DB=postgres
-
-matrix:
-  fast_finish: true
-  exclude:
-  - rvm: 2.2.6
-    gemfile: gemfiles/activerecord_6.0.0.gemfile
-  - rvm: 2.3.5
-    gemfile: gemfiles/activerecord_6.0.0.gemfile
-  - rvm: 2.4.2
-    gemfile: gemfiles/activerecord_6.0.0.gemfile
-  - rvm: jruby-9.1.9.0
-    gemfile: gemfiles/activerecord_5.0.2.gemfile
-  - rvm: jruby-9.1.9.0
-    gemfile: gemfiles/activerecord_6.0.0.gemfile
-  - rvm: jruby-9.2.5.0
-    gemfile: gemfiles/activerecord_5.0.2.gemfile
-  - rvm: jruby-9.2.5.0
-    gemfile: gemfiles/activerecord_6.0.0.gemfile
-  allow_failures:
-    - rvm: ruby-head
-    - rvm: jruby-head
-
-notifications:
-  email:
-    recipients:
-      - alessandro.rodi@renuo.ch
-    on_success: change
-    on_failure: change
-before_install:
-  - gem update --system
-  - gem install bundler
-script:
-  - bundle exec rubocop && bundle exec rake
diff --git a/Appraisals b/Appraisals
index 08ed518..d025b2d 100644
--- a/Appraisals
+++ b/Appraisals
@@ -1,21 +1,3 @@
-appraise 'activerecord_4.2.0' do
-  gem 'activerecord', '~> 4.2.0', require: 'active_record'
-  gem 'activesupport', '~> 4.2.0', require: 'active_support/all'
-  gem 'actionpack', '~> 4.2.0', require: 'action_pack'
-  gem 'nokogiri', '~> 1.6.8', require: 'nokogiri' # TODO: fix for ruby 2.0.0
-
-  gemfile.platforms :jruby do
-    gem 'activerecord-jdbcsqlite3-adapter', '~> 1.3.24'
-    gem 'jdbc-sqlite3'
-    gem 'jdbc-postgres'
-  end
-
-  gemfile.platforms :ruby, :mswin, :mingw do
-    gem 'sqlite3', '~> 1.3.0'
-    gem 'pg', '~> 0.21'
-  end
-end
-
 appraise 'activerecord_5.0.2' do
   gem 'activerecord', '~> 5.0.2', require: 'active_record'
   gem 'activesupport', '~> 5.0.2', require: 'active_support/all'
@@ -28,8 +10,8 @@ appraise 'activerecord_5.0.2' do
   end
 
   gemfile.platforms :ruby, :mswin, :mingw do
+    gem 'pg', '~> 1.3.4'
     gem 'sqlite3', '~> 1.3.0'
-    gem 'pg', '~> 0.21'
   end
 end
 
@@ -45,8 +27,8 @@ appraise 'activerecord_5.1.0' do
   end
 
   gemfile.platforms :ruby, :mswin, :mingw do
-    gem 'sqlite3', '~> 1.3.0'
-    gem 'pg', '~> 0.21'
+    gem 'pg', '~> 1.3.4'
+    gem 'sqlite3', '~> 1.4.2'
   end
 end
 
@@ -62,15 +44,15 @@ appraise 'activerecord_5.2.2' do
   end
 
   gemfile.platforms :ruby, :mswin, :mingw do
-    gem 'sqlite3', '~> 1.3.0'
-    gem 'pg', '~> 0.21'
+    gem 'pg', '~> 1.3.4'
+    gem 'sqlite3', '~> 1.4.2'
   end
 end
 
 appraise 'activerecord_6.0.0' do
-  gem 'actionpack', '~> 6.0.0.beta3', require: 'action_pack'
-  gem 'activerecord', '~> 6.0.0.beta3', require: 'active_record'
-  gem 'activesupport', '~> 6.0.0.beta3', require: 'active_support/all'
+  gem 'actionpack', '~> 6.0.0', require: 'action_pack'
+  gem 'activerecord', '~> 6.0.0', require: 'active_record'
+  gem 'activesupport', '~> 6.0.0', require: 'active_support/all'
 
   platforms :jruby do
     gem 'activerecord-jdbcsqlite3-adapter'
@@ -79,7 +61,60 @@ appraise 'activerecord_6.0.0' do
   end
 
   platforms :ruby, :mswin, :mingw do
-    gem 'pg', '~> 1.1.4'
-    gem 'sqlite3', '~> 1.3.0'
+    gem 'pg', '~> 1.3.4'
+    gem 'sqlite3', '~> 1.4.2'
+  end
+end
+
+appraise 'activerecord_6.1.0' do
+  gem 'actionpack', '~> 6.1.0', require: 'action_pack'
+  gem 'activerecord', '~> 6.1.0', require: 'active_record'
+  gem 'activesupport', '~> 6.1.0', require: 'active_support/all'
+
+  platforms :jruby do
+    gem 'activerecord-jdbcsqlite3-adapter'
+    gem 'jdbc-sqlite3'
+    gem 'jdbc-postgres'
+  end
+
+  platforms :ruby, :mswin, :mingw do
+    gem 'pg', '~> 1.3.4'
+    gem 'sqlite3', '~> 1.4.2'
+  end
+end
+
+appraise 'activerecord_7.0.0' do
+  gem 'actionpack', '~> 7.0.0', require: 'action_pack'
+  gem 'activerecord', '~> 7.0.0', require: 'active_record'
+  gem 'activesupport', '~> 7.0.0', require: 'active_support/all'
+
+  platforms :jruby do
+    gem 'activerecord-jdbcsqlite3-adapter'
+    gem 'jdbc-sqlite3'
+    gem 'jdbc-postgres'
+  end
+
+  platforms :ruby, :mswin, :mingw do
+    gem 'pg', '~> 1.3.4'
+    gem 'sqlite3', '~> 1.4.2'
+  end
+end
+
+appraise 'activerecord_main' do
+  git 'https://github.com/rails/rails', branch: 'main' do
+    gem 'actionpack', require: 'action_pack'
+    gem 'activerecord', require: 'active_record'
+    gem 'activesupport', require: 'active_support/all'
+  end
+
+  platforms :jruby do
+    gem 'activerecord-jdbcsqlite3-adapter'
+    gem 'jdbc-sqlite3'
+    gem 'jdbc-postgres'
+  end
+
+  platforms :ruby, :mswin, :mingw do
+    gem 'pg', '~> 1.3.4'
+    gem 'sqlite3', '~> 1.4.2'
   end
 end
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e5bac24..265e7f5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,10 +1,60 @@
+## 3.5.0
+
+* [#653](https://github.com/CanCanCommunity/cancancan/pull/653): Add support for using an nil relation as a condition. ([@ghiculescu][])
+* [#702](https://github.com/CanCanCommunity/cancancan/pull/702): Support scopes of STI classes as ability conditions. ([@honigc][])
+* [#798](https://github.com/CanCanCommunity/cancancan/pull/798): Allow disabling of rules compressor via `CanCan.rules_compressor_enabled = false`. ([@coorasse][])
+* [#814](https://github.com/CanCanCommunity/cancancan/pull/814): Fix issue with polymorphic associations. ([@WriterZephos][])
+
+## 3.4.0
+
+* [#691](https://github.com/CanCanCommunity/cancancan/pull/691): Add two new subquery strategies: `joined_alias_exists_subquery`, `joined_alias_each_rule_as_exists_subquery`. ([@kaspernj][])
+* [#767](https://github.com/CanCanCommunity/cancancan/pull/767): Improve ability checks with nested resources (hash checks)vim. ([@Juleffel][])
+* [#772](https://github.com/CanCanCommunity/cancancan/pull/772): Support non-hash conditions in ability definitions. ([@Juleffel][])
+* [#773](https://github.com/CanCanCommunity/cancancan/pull/773): Drop support for ruby 2.4 and 2.5. ([@coorasse][])
+* [#778](https://github.com/CanCanCommunity/cancancan/pull/778): Drop support for ActiveRecord 4. ([@coorasse][])
+
+## 3.3.0
+
+* [#675](https://github.com/CanCanCommunity/cancancan/pull/675): Support modifying the `accessible_by` querying strategy on a per-query basis. ([@ghiculescu][])
+* [#714](https://github.com/CanCanCommunity/cancancan/pull/714): Don't hold unnecessary references to subjects in @rules_index. ([@mtoneil][])
+
+## 3.2.2
+
+* Added funding metadata to Gemspec. ([@coorasse][])
+
+## 3.2.1
+
+* [#674](https://github.com/CanCanCommunity/cancancan/pull/674): Fix accidental dependency on ActiveRecord in 3.2.0. ([@ghiculescu][])
+
+## 3.2.0
+
+* [#649](https://github.com/CanCanCommunity/cancancan/pull/649): Add support for Single Table Inheritance. ([@Liberatys][])
+* [#640](https://github.com/CanCanCommunity/cancancan/pull/640): Simplify implementation of new model adapters. ([@ghiculescu][])
+* [#650](https://github.com/CanCanCommunity/cancancan/pull/650): Support associations in rules. ([@Liberatys][])
+* [#657](https://github.com/CanCanCommunity/cancancan/pull/657): Support for Rails 6.1. ([@ghiculescu][])
+* [#655](https://github.com/CanCanCommunity/cancancan/pull/655): Add option for `accessible_by` querying strategy. ([@ghiculescu][])
+
+## 3.1.0
+
+* [#605](https://github.com/CanCanCommunity/cancancan/pull/605): Generate inner queries instead of join+distinct. ([@fsateler][])
+* [#608](https://github.com/CanCanCommunity/cancancan/pull/608): Spec for json column regression. ([@aleksejleonov][])
+* [#571](https://github.com/CanCanCommunity/cancancan/pull/571): Allows to check ability even the object implements `#to_a`. ([@mtsmfm][])
+* [#612](https://github.com/CanCanCommunity/cancancan/pull/612): Suppress keyword arguments warning for Ruby 2.7.0. ([@koic][])
+* [#569](https://github.com/CanCanCommunity/cancancan/pull/569): Fix accessible_by fires query for rules using association as condition. ([@albb0920][])
+* [#594](https://github.com/CanCanCommunity/cancancan/pull/594): Support translation of action name. ([@ayumu838][])
+
+## 3.0.2
+
+* [#590](https://github.com/CanCanCommunity/cancancan/pull/590): Fix Rule#inspect when rule is created through a SQL array. ([@frostblooded][])
+* [#592](https://github.com/CanCanCommunity/cancancan/pull/592): Prevent normalization of through polymorphic associations.([@eloyesp][])
+
 ## 3.0.1
 
 * [#583](https://github.com/CanCanCommunity/cancancan/pull/583): Fix regression when using a method reference block. ([@coorasse][])
 
 ## 3.0.0
 
-Please read the [guide on migrating from CanCanCan 2.x to 3.0](https://github.com/CanCanCommunity/cancancan/wiki/Migrating-from-CanCanCan-2.x-to-3.0)
+Please read the [guide on migrating from CanCanCan 2.x to 3.0](https://github.com/CanCanCommunity/cancancan/blob/develop/docs/migrating.md#from-2x-to-3x)
 
 * [#560](https://github.com/CanCanCommunity/cancancan/pull/560): Add support for Rails 6.0. ([@coorasse][])
 * [#489](https://github.com/CanCanCommunity/cancancan/pull/489): Drop support for actions without a subject. ([@andrew-aladev][])
@@ -131,7 +181,7 @@ Please read the [guide on migrating from CanCanCan 2.x to 3.0](https://github.co
 
 ## 1.9.0 (July 20th, 2014)
 
-* Fix cancancan#59 - Parameters are automatically detected and santitized for all actions, not just create and update ([@bryanrite][]).
+* Fix cancancan#59 - Parameters are automatically detected and sanitized for all actions, not just create and update ([@bryanrite][]).
 
 * Fix cancancan#97, 72, 40, 39, 26 - Support Active Record 4 properly with references on nested permissions (scpike, tdg5, Crystark).
 
@@ -164,7 +214,7 @@ Please read the [guide on migrating from CanCanCan 2.x to 3.0](https://github.co
 
 * Feature cancancan#3 - Permit "can?" check multiple subjects (cefigueiredo).
 
-* Feature cancancan#29 - Add ability to use a String that will get instance_eval'd or a Proc that will get called as the parameter method option for strong_parameter santization (svoop).
+* Feature cancancan#29 - Add ability to use a String that will get instance_eval'd or a Proc that will get called as the parameter method option for strong_parameter sanitization (svoop).
 
 * Feature cancancan#48 - Define a CanCanCan module. Even though it is not used, it is standard practice to define the module, and helpful for determining between CanCanCan and CanCan for external libraries.
 
@@ -643,3 +693,17 @@ Please read the [guide on migrating from CanCanCan 2.x to 3.0](https://github.co
 [@andrew-aladev]: https://github.com/andrew-aladev
 [@phaedryx]: https://github.com/phaedryx
 [@kaspernj]: https://github.com/kaspernj
+[@frostblooded]: https://github.com/frostblooded
+[@eloyesp]: https://github.com/eloyesp
+[@mtsmfm]: https://github.com/mtsmfm
+[@koic]: https://github.com/koic
+[@fsateler]: https://github.com/fsateler
+[@aleksejleonov]: https://github.com/aleksejleonov
+[@albb0920]: https://github.com/albb0920
+[@ayumu838]: https://github.com/ayumu838
+[@Liberatys]: https://github.com/Liberatys
+[@ghiculescu]: https://github.com/ghiculescu
+[@mtoneil]: https://github.com/mtoneil
+[@Juleffel]: https://github.com/Juleffel
+[@honigc]: https://github.com/honigc
+[@WriterZephos]: https://github.com/WriterZephos
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5df12cc..7252e2f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,8 +2,8 @@
 
 ### Reporting an Issue
 
-1. If you have any questions about CanCanCan, search the [Wiki](https://github.com/cancancommunity/cancancan/wiki) or 
-use [Stack Overflow](http://stackoverflow.com/questions/tagged/cancancan). 
+1. If you have any questions about CanCanCan, search the [Wiki](https://github.com/cancancommunity/cancancan/wiki) or
+use [Stack Overflow](http://stackoverflow.com/questions/tagged/cancancan).
 Do not post questions here.
 
 1. If you find a security bug, **DO NOT** submit an issue here. Please send an e-mail to the [current maintainer](https://github.com/coorasse) instead.
@@ -17,7 +17,7 @@ That's it! The more information you give, the more easy it becomes for us to tra
 ### Adding new Features or Bugfixes
 
 CanCanCan uses a [git-flow](http://nvie.com/posts/a-successful-git-branching-model/) development model.
-The latest "released" version of CanCanCan, the latest gem version, can always be found on `master`, 
+The latest "released" version of CanCanCan, the latest gem version, can always be found on `main`,
 while the next version or nightly is on `develop`.
 
 Please make sure you have test coverage for anything you add or fix!
diff --git a/README.md b/README.md
index cdf0bfa..04fa2b6 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,17 @@
 # CanCanCan
 
-![CanCanCan Logo](/logo/cancancan.jpg)
+<img src="./logo/cancancan.png" width="200" />
 
 [![Gem Version](https://badge.fury.io/rb/cancancan.svg)](http://badge.fury.io/rb/cancancan)
-[![Travis badge](https://travis-ci.org/CanCanCommunity/cancancan.svg?branch=develop)](https://travis-ci.org/CanCanCommunity/cancancan)
+[![Github Actions badge](https://github.com/CanCanCommunity/cancancan/actions/workflows/test.yml/badge.svg)](https://github.com/CanCanCommunity/cancancan/actions/workflows/test.yml/badge.svg)
 [![Code Climate Badge](https://codeclimate.com/github/CanCanCommunity/cancancan.svg)](https://codeclimate.com/github/CanCanCommunity/cancancan)
 
-[Wiki](https://github.com/CanCanCommunity/cancancan/wiki) |
-[RDocs](http://rdoc.info/projects/CanCanCommunity/cancancan) |
+[Developer guide](./docs/README.md) |
+[RDocs](https://www.rubydoc.info/github/CanCanCommunity/cancancan) |
 [Screencast 1](http://railscasts.com/episodes/192-authorization-with-cancan) |
 [Screencast 2](https://www.youtube.com/watch?v=cTYu-OjUgDw)
 
-CanCanCan is an authorization library for Ruby >= 2.2.0 and Ruby on Rails >= 4.2 which restricts what
+CanCanCan is an authorization library for Ruby and Ruby on Rails which restricts what
 resources a given user is allowed to access.
 
 All permissions can be defined in one or multiple ability files and not duplicated across controllers, views,
@@ -24,6 +24,55 @@ and provides helpers to check for those permissions.
 2. **Rails helpers** to simplify the code in Rails Controllers by performing the loading and checking of permissions
 of models automatically and reduce duplicated code.
 
+## Our sponsors
+<br/>
+<a href="https://www.renuo.ch" target="_blank">
+  <img src="./logo/renuo.png" alt="Renuo AG" height="50"/>
+</a>
+<br/>
+<br/>
+<br/>
+<a href="https://www.moderntreasury.com" target="_blank"  style="display:inline">
+  <img src="./logo/modern_treasury.svg" alt="Modern Treasury" height="40"/>
+</a>
+<br/>
+<br/>
+<br/>
+<a href="https://bullettrain.co" target="_blank">
+  <img src="./logo/bullet_train.png" alt="Bullet Train" height="50"/>
+</a>
+<br/>
+<br/>
+<br/>
+<a href="https://jobs.goboony.com/o/full-stack-ruby-on-rails-engineer" target="_blank">
+  <img src="./logo/goboony.png" alt="Goboony" height="50"/>
+</a>
+<br />
+<br />
+<br />
+<a href="https://newrelic.com/" target="_blank">
+  <img src="./logo/new_relic.png" alt="NewRelic" height="45"/>
+</a>
+<br />
+<br />
+<br />
+<a href="https://www.ontra.ai" target="_blank">
+  <img src="./logo/ontra.png" alt="Ontra" height="70"/>
+</a>
+<br />
+<br />
+<br />
+<a href="https://www.honeybadger.io/" target="_blank">
+  <img src="./logo/honeybadger.svg" alt="Honeybadger" height="45"/>
+</a>
+<br />
+<br />
+
+Do you want to sponsor CanCanCan and show your logo here?
+Check our [Sponsors Page](https://github.com/sponsors/coorasse).
+
+Head to our complete [Developer Guide](./docs/README.md) to learn how to use CanCanCan in details.
+
 ## Installation
 
 Add this to your Gemfile:
@@ -46,21 +95,15 @@ class Ability
   def initialize(user)
     can :read, Post, public: true
 
-    if user.present?  # additional permissions for logged in users (they can read their own posts)
-      can :read, Post, user_id: user.id
+    return unless user.present?  # additional permissions for logged in users (they can read their own posts)
+    can :read, Post, user: user
 
-      if user.admin?  # additional permissions for administrators
-        can :read, post
-      end
-    end
+    return unless user.admin?  # additional permissions for administrators
+    can :read, Post
   end
 end
 ```
 
-See [Defining Abilities](https://github.com/CanCanCommunity/cancancan/wiki/defining-abilities) for details on how to
-define your rules.
-
-
 ## Check Abilities
 
 The current user's permissions can then be checked using the `can?` and `cannot?` methods in views and controllers.
@@ -71,9 +114,6 @@ The current user's permissions can then be checked using the `can?` and `cannot?
 <% end %>
 ```
 
-See [Checking Abilities](https://github.com/CanCanCommunity/cancancan/wiki/checking-abilities) for more information
-on how you can use these helpers.
-
 ## Fetching records
 
 One of the key features of CanCanCan, compared to other authorization libraries,
@@ -81,20 +121,13 @@ is the possibility to retrieve all the objects that the user is authorized to ac
 The following:
 
 ```ruby
-  Post.accessible_by(current_ability)
+  @posts = Post.accessible_by(current_ability)
 ```
 
 will use your rules to ensure that the user retrieves only a list of posts that can be read.
-See [Fetching records](https://github.com/CanCanCommunity/cancancan/wiki/Fetching-Records) for details.
 
 ## Controller helpers
 
-CanCanCan expects a `current_user` method to exist in the controller.
-First, set up some authentication (such as [Devise](https://github.com/plataformatec/devise) or [Authlogic](https://github.com/binarylogic/authlogic)).
-See [Changing Defaults](https://github.com/CanCanCommunity/cancancan/wiki/changing-defaults) if you need a different behavior.
-
-### 3.1 Authorizations
-
 The `authorize!` method in the controller will raise an exception if the user is not able to perform the given action.
 
 ```ruby
@@ -104,8 +137,6 @@ def show
 end
 ```
 
-### 3.2 Loaders
-
 Setting this for every action can be tedious, therefore the `load_and_authorize_resource` method is provided to
 automatically authorize all actions in a RESTful style resource controller.
 It will use a before action to load the resource into an instance variable and authorize it for every action.
@@ -124,129 +155,14 @@ class PostsController < ApplicationController
 end
 ```
 
-See [Authorizing Controller Actions](https://github.com/CanCanCommunity/cancancan/wiki/authorizing-controller-actions)
-for more information.
-
-
-### 3.3 Strong Parameters
-
-You have to sanitize inputs before saving the record, in actions such as `:create` and `:update`.
-
-For the `:update` action, CanCanCan will load and authorize the resource but *not* change it automatically, so the typical usage would be something like:
-
-```ruby
-def update
-  if @post.update(post_params)
-    # hurray
-  else
-    render :edit
-  end
-end
-...
-
-def post_params
-  params.require(:post).permit(:body)
-end
-```
-
-For the `:create` action, CanCanCan will try to initialize a new instance with sanitized input by seeing if your
-controller will respond to the following methods (in order):
-
-1. `create_params`
-2. `<model_name>_params` such as `post_params` (this is the default convention in rails for naming your param method)
-3. `resource_params` (a generically named method you could specify in each controller)
-
-Additionally, `load_and_authorize_resource` can now take a `param_method` option to specify a custom method in the controller to run to sanitize input.
-
-You can associate the `param_method` option with a symbol corresponding to the name of a method that will get called:
-
-```ruby
-class PostsController < ApplicationController
-  load_and_authorize_resource param_method: :my_sanitizer
-
-  def create
-    if @post.save
-      # hurray
-    else
-      render :new
-    end
-  end
-
-  private
-
-  def my_sanitizer
-    params.require(:post).permit(:name)
-  end
-end
-```
-
-You can also use a string that will be evaluated in the context of the controller using `instance_eval` and needs to contain valid Ruby code.
-
-    load_and_authorize_resource param_method: 'permitted_params.post'
-
-Finally, it's possible to associate `param_method` with a Proc object which will be called with the controller as the only argument:
-
-    load_and_authorize_resource param_method: Proc.new { |c| c.params.require(:post).permit(:name) }
-
-See [Strong Parameters](https://github.com/CanCanCommunity/cancancan/wiki/Strong-Parameters) for more information.
-
-## Handle Unauthorized Access
-
-If the user authorization fails, a `CanCan::AccessDenied` exception will be raised.
-You can catch this and modify its behavior in the `ApplicationController`.
-
-```ruby
-class ApplicationController < ActionController::Base
-  rescue_from CanCan::AccessDenied do |exception|
-    respond_to do |format|
-      format.json { head :forbidden, content_type: 'text/html' }
-      format.html { redirect_to main_app.root_url, notice: exception.message }
-      format.js   { head :forbidden, content_type: 'text/html' }
-    end
-  end
-end
-```
-
-See [Exception Handling](https://github.com/CanCanCommunity/cancancan/wiki/exception-handling) for more information.
-
-
-## Lock It Down
-
-If you want to ensure authorization happens on every action in your application, add `check_authorization` to your `ApplicationController`.
-
-```ruby
-class ApplicationController < ActionController::Base
-  check_authorization
-end
-```
-
-This will raise an exception if authorization is not performed in an action.
-If you want to skip this, add `skip_authorization_check` to a controller subclass.
-See [Ensure Authorization](https://github.com/CanCanCommunity/cancancan/wiki/Ensure-Authorization) for more information.
-
-## Wiki Docs
-
-* [Defining Abilities](https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities)
-* [Checking Abilities](https://github.com/CanCanCommunity/cancancan/wiki/Checking-Abilities)
-* [Authorizing Controller Actions](https://github.com/CanCanCommunity/cancancan/wiki/Authorizing-Controller-Actions)
-* [Exception Handling](https://github.com/CanCanCommunity/cancancan/wiki/Exception-Handling)
-* [Changing Defaults](https://github.com/CanCanCommunity/cancancan/wiki/Changing-Defaults)
-* [See more](https://github.com/CanCanCommunity/cancancan/wiki)
-
-## Mission
-
-This repo is a continuation of the dead [CanCan](https://github.com/ryanb/cancan) project.
-Our mission is to keep CanCan alive and moving forward, with maintenance fixes and new features.
-Pull Requests are welcome!
-
-Any help is greatly appreciated, feel free to submit pull-requests or open issues.
+## Documentation
 
+Head to our complete [Developer Guide](./docs/README.md) to learn how to use CanCanCan in details.
 
 ## Questions?
 
 If you have any question or doubt regarding CanCanCan which you cannot find the solution to in the
-[documentation](https://github.com/CanCanCommunity/cancancan/wiki) or our
-[mailing list](http://groups.google.com/group/cancancan), please
+[documentation](./docs/README.md), please
 [open a question on Stackoverflow](http://stackoverflow.com/questions/ask?tags=cancancan) with tag
 [cancancan](http://stackoverflow.com/questions/tagged/cancancan)
 
@@ -254,30 +170,23 @@ If you have any question or doubt regarding CanCanCan which you cannot find the
 
 If you find a bug please add an [issue on GitHub](https://github.com/CanCanCommunity/cancancan/issues) or fork the project and send a pull request.
 
-
 ## Development
 
 CanCanCan uses [appraisals](https://github.com/thoughtbot/appraisal) to test the code base against multiple versions
 of Rails, as well as the different model adapters.
 
-When first developing, you need to run `bundle install` and then `appraisal install`, to install the different sets.
-
-You can then run all appraisal files (like CI does), with `appraisal rake` or just run a specific set `appraisal activerecord_5.0 rake`.
-
-See the [CONTRIBUTING](https://github.com/CanCanCommunity/cancancan/blob/develop/CONTRIBUTING.md) and
-[spec/README](https://github.com/CanCanCommunity/cancancan/blob/master/spec/README.rdoc) for more information.
+When first developing, you need to run `bundle install` and then `bundle exec appraisal install`, to install the different sets.
 
+You can then run all appraisal files (like CI does), with `appraisal rake` or just run a specific set `DB='sqlite' bundle exec appraisal activerecord_5.2.2 rake`.
 
-## Special Thanks
-
-[![Renuo AG](/logo/renuo.png)](https://www.renuo.ch)
+If you'd like to run a specific set of tests within a specific file or folder you can use `DB='sqlite' SPEC=path/to/file/or/folder bundle exec appraisal activerecord_5.2.2 rake`.
 
-Thanks to [Renuo AG](https://www.renuo.ch) for currently maintaining and supporting the project.
+If you use RubyMine, you can run RSpec tests by configuring the RSpec configuration template like this:
+![rubymine_rspec.png](rubymine_rspec.png)
 
-Also many thanks to the [CanCanCan contributors](https://github.com/CanCanCommunity/cancancan/contributors).
-See the [CHANGELOG](https://github.com/CanCanCommunity/cancancan/blob/master/CHANGELOG.md) for the full list.
-
-CanCanCan was inspired by [declarative_authorization](https://github.com/stffn/declarative_authorization/) and
-[aegis](https://github.com/makandra/aegis).
+See the [CONTRIBUTING](./CONTRIBUTING.md) for more information.
 
+## Special Thanks
 
+Thanks to our Sponsors and to all the [CanCanCan contributors](https://github.com/CanCanCommunity/cancancan/contributors).
+See the [CHANGELOG](https://github.com/CanCanCommunity/cancancan/blob/main/CHANGELOG.md) for the full list.
diff --git a/cancancan.gemspec b/cancancan.gemspec
index 15d9e70..2302e1f 100644
--- a/cancancan.gemspec
+++ b/cancancan.gemspec
@@ -10,6 +10,7 @@ Gem::Specification.new do |s|
   s.authors     = ['Alessandro Rodi (Renuo AG)', 'Bryan Rite', 'Ryan Bates', 'Richard Wilson']
   s.email       = 'alessandro.rodi@renuo.ch'
   s.homepage    = 'https://github.com/CanCanCommunity/cancancan'
+  s.metadata = { 'funding_uri' => 'https://github.com/sponsors/coorasse' }
   s.summary     = 'Simple authorization solution for Rails.'
   s.description = 'Simple authorization solution for Rails. All permissions are stored in a single location.'
   s.platform    = Gem::Platform::RUBY
@@ -24,5 +25,5 @@ Gem::Specification.new do |s|
   s.add_development_dependency 'bundler', '~> 2.0'
   s.add_development_dependency 'rake', '~> 10.1', '>= 10.1.1'
   s.add_development_dependency 'rspec', '~> 3.2', '>= 3.2.0'
-  s.add_development_dependency 'rubocop', '~> 0.63.1'
+  s.add_development_dependency 'rubocop', '~> 1.31.1'
 end
diff --git a/debian/changelog b/debian/changelog
index b996c2e..53632f1 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+ruby-cancancan (3.5.0-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Sat, 03 Jun 2023 10:15:14 -0000
+
 ruby-cancancan (3.0.1+gh-1) unstable; urgency=medium
 
   * Team upload
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000..9fbbeb8
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,44 @@
+# CanCanCan - Developer guide
+
+This is the official guide to CanCanCan.
+
+It will advance chapter by chapter and go more and more into details, advanced usages, and special cases.
+
+We will start by introducing basic concepts and features, and then dig deeper into configurations and implementation details in later chapters.
+
+You can skip the [Introduction](./introduction.md) where there's just some history and blablabla and go directly to [Installation](./installation.md) to start fast :rocket:.
+
+## Summary
+
+1. [Introduction](./introduction.md)
+1. [Installation](./installation.md)
+1. [Define and check abilities](./define_check_abilities.md)
+1. [Controller helpers](./controller_helpers.md)
+1. [Fetching records](./fetching_records.md)
+1. [Cannot](./cannot.md)
+1. [Hash of conditions](./hash_of_conditions.md)
+1. [Combine Abilities](./combine_abilities.md)
+1. [Check abilities - avoid mistakes](./check_abilities_mistakes.md)
+1. [Handling CanCan::AccessDenied](./handling_access_denied.md)
+1. [Customize controller helpers](./changing_defaults.md)
+1. [Accessing request data](./accessing_request_data.md)
+1. [SQL strategies](./sql_strategies.md)
+1. [Accessible attributes](./accessible_attributes.md)
+1. [Testing](./testing.md)
+1. [Internationalization](./internationalization.md)
+
+## Further topics
+
+In these topics, you will learn some best practices, but also how to solve specific integration issues with other libraries or how to extend CanCanCan.
+
+1. [Migrating](./migrating.md)
+1. [Debugging Abilities](./debugging.md)
+1. [Split your ability file](./split_ability.md)
+1. [Define Abilities - best practices](./define_abilities_best_practices.md)
+1. [Abilities in database](./abilities_in_database.md)
+1. [Role-based Authorization](./role_based_authorization.md)
+1. [Model Adapter](./model_adapter.md)
+1. [Rules compression](./rules_compression.md)
+1. [Inherited Resources](./inherited_resources.md)
+1. [Devise](./devise.md)
+1. [FriendlyId](./friendly_id.md)
diff --git a/docs/abilities_in_database.md b/docs/abilities_in_database.md
new file mode 100644
index 0000000..b1890ad
--- /dev/null
+++ b/docs/abilities_in_database.md
@@ -0,0 +1,125 @@
+# Abilities in Database
+
+What if you or a client, wants to change permissions without having to re-deploy the application?
+In that case, it may be best to store the permission logic in a database: it is very easy to use the database records when defining abilities.
+
+We will need a model called `Permission`.
+
+Each user `has_many :permissions`, and each permission has `action`, `subject_class` and `subject_id` columns. The last of which is optional.
+
+```ruby
+class Ability
+  include CanCan::Ability
+
+  def initialize(user)
+    can do |action, subject_class, subject|
+      user.permissions.where(action: aliases_for_action(action)).any? do |permission|
+        permission.subject_class == subject_class.to_s &&
+          (subject.nil? || permission.subject_id.nil? || permission.subject_id == subject.id)
+      end
+    end
+  end
+end
+```
+
+An alternative approach is to define a separate `can` ability for each permission.
+
+```ruby
+def initialize(user)
+  user.permissions.each do |permission|
+    if permission.subject_id.nil?
+      can permission.action.to_sym, permission.subject_class.constantize
+    else
+      can permission.action.to_sym, permission.subject_class.constantize, id: permission.subject_id
+    end
+  end
+end
+```
+
+The actual details will depend largely on your application requirements, but hopefully, you can see how it's possible to define permissions in the database and use them with CanCanCan.
+
+You can mix-and-match this with defining permissions in the code as well. This way you can keep the more complex logic in the code so you don't need to shoe-horn every kind of permission rule into an overly-abstract database.
+
+You can also create a `Permission` model containing all possible permissions in your app. Use that code to create a rake task that fills a `Permission` table:
+(The code below is not fully tested)
+
+To use the following code, the permissions table should have such fields :name, :user_id, :subject_class, :subject_id, :action, and :description.You can generate the permission model by the command: `rails g model Permission user_id:integer name:string subject_class:string subject_id:integer action:string description:text`.
+
+```ruby
+class ApplicationController < ActionController::Base
+  ...
+  protected
+
+  # Derive the model name from the controller. UsersController will return User
+  def self.permission
+    return name = controller_name.classify.constantize
+  end
+end
+```
+
+```ruby
+def setup_actions_controllers_db
+
+  write_permission("all", "manage", "Everything", "All operations", true)
+
+  controllers = Dir.new("#{Rails.root}/app/controllers").entries
+  controllers.each do |controller|
+    if controller =~ /_controller/
+      foo_bar = controller.camelize.gsub(".rb","").constantize.new
+    end
+  end
+  # You can change ApplicationController for a super-class used by your restricted controllers
+  ApplicationController.subclasses.each do |controller|
+    if controller.respond_to?(:permission)
+      klass, description = controller.permission
+      write_permission(klass, "manage", description, "All operations")
+      controller.action_methods.each do |action|
+        if action.to_s.index("_callback").nil?
+          action_desc, cancan_action = eval_cancan_action(action)
+          write_permission(klass, cancan_action, description, action_desc)
+        end
+      end
+    end
+  end
+
+end
+
+
+def eval_cancan_action(action)
+  case action.to_s
+  when "index", "show", "search"
+    cancan_action = "read"
+    action_desc = I18n.t :read
+  when "create", "new"
+    cancan_action = "create"
+    action_desc = I18n.t :create
+  when "edit", "update"
+    cancan_action = "update"
+    action_desc = I18n.t :edit
+  when "delete", "destroy"
+    cancan_action = "delete"
+    action_desc = I18n.t :delete
+  else
+    cancan_action = action.to_s
+    action_desc = "Other: " << cancan_action
+  end
+  return action_desc, cancan_action
+end
+
+def write_permission(class_name, cancan_action, name, description, force_id_1 = false)
+  permission = Permission.find(:first, :conditions => ["subject_class = ? and action = ?", class_name, cancan_action])
+  if not permission
+    permission = Permission.new
+    permission.id = 1 if force_id_1
+    permission.subject_class = class_name
+    permission.action = cancan_action
+    permission.name = name
+    permission.description = description
+    permission.save
+  else
+    permission.name = name
+    permission.description = description
+    permission.save
+  end
+end
+```
diff --git a/docs/accessible_attributes.md b/docs/accessible_attributes.md
new file mode 100644
index 0000000..4ee96dc
--- /dev/null
+++ b/docs/accessible_attributes.md
@@ -0,0 +1,37 @@
+# Accessible attributes
+
+CanCanCan gives you the possibility to define actions on single instances' attributes.
+
+Given you want users to only read a user first name and last name you can define:
+
+```ruby
+can :read, User, [:first_name, :last_name]
+```
+
+and check it with:
+
+```ruby
+can? :read, @user, :first_name
+```
+
+You can also ask for all the allowed attributes:
+
+```ruby
+current_ability.permitted_attributes(:read, @user)
+#=> [:first_name, :last_name]
+```
+
+This can be used, for example, to display a form:
+
+```ruby
+current_ability.permitted_attributes(:read, @book).each do |attr|
+  = form.input attr
+```
+
+or in Strong Parameters:
+
+```ruby
+params
+  .require(:book)
+  .permit(current_ability.permitted_attributes(:read, @book))
+```
diff --git a/docs/accessing_request_data.md b/docs/accessing_request_data.md
new file mode 100644
index 0000000..238389d
--- /dev/null
+++ b/docs/accessing_request_data.md
@@ -0,0 +1,27 @@
+# Accessing request data
+
+What if you need to modify the permissions based on something outside of the User object? For example, let's say you want to forbid certain IP addresses from creating comments. The IP address is accessible through request.remote_ip but the Ability class does not have access to this. It's easy to modify what you pass to the Ability object by overriding the current_ability method in ApplicationController.
+
+```ruby
+class ApplicationController < ActionController::Base
+  #...
+
+  private
+
+  def current_ability
+    @current_ability ||= Ability.new(current_user, request.remote_ip)
+  end
+end
+```
+
+```ruby
+class Ability
+  include CanCan::Ability
+
+  def initialize(user, ip_address=nil)
+    can :create, Comment unless DENYLIST_IPS.include? ip_address
+  end
+end
+```
+
+This concept can apply to session and cookies as well.
diff --git a/docs/cannot.md b/docs/cannot.md
new file mode 100644
index 0000000..5d1fc4d
--- /dev/null
+++ b/docs/cannot.md
@@ -0,0 +1,14 @@
+# Cannot
+
+Yes, sometimes you might need to **remove** permissions. Even if we said that CanCanCan assumes that by default no one has access to any resource, there are situations where you might need to remove an ability.
+
+The `cannot` method takes the same arguments as `can` and defines which actions the user is unable to perform. This is normally done after a more generic `can` call.
+
+```ruby
+can :manage, Project
+cannot :destroy, Project
+```
+
+will allow the user to do **any** action but destroy the project.
+
+Of course, there's a `cannot?` method to check abilities that is a simple alias for `!can?`.
diff --git a/docs/changing_defaults.md b/docs/changing_defaults.md
new file mode 100644
index 0000000..5420b4c
--- /dev/null
+++ b/docs/changing_defaults.md
@@ -0,0 +1,287 @@
+# Customize the controller helpers
+
+We now dig deeper in the customizations and options we have when working with [controller helpers](./controller_helpers.md)
+
+## current_ability and current_user
+
+CanCanCan makes two assumptions about your application:
+
+- You have an `Ability` class which defines the permissions.
+- You have a `current_user` method in the controller which returns the current user model.
+
+You can override both of these by defining the `current_ability` method in your `ApplicationController`. The default method looks like this.
+
+```ruby
+def current_ability
+  @current_ability ||= Ability.new(current_user)
+end
+```
+
+The `Ability` class and `current_user` method can easily be changed to something else.
+
+```ruby
+# in ApplicationController
+def current_ability
+  @current_ability ||= AccountAbility.new(current_account)
+end
+```
+
+Sometimes you might have a gem in your project which provides its own Rails engine which also uses CanCanCan, in this case the current_ability override in the ApplicationController can also be useful.
+
+```ruby
+# in ApplicationController
+def current_ability
+  if request.fullpath =~ /\/rails_admin/
+    @current_ability ||= RailsAdmin::Ability.new(current_user)
+  else
+    @current_ability ||= Ability.new(current_user)
+  end
+end
+```
+
+If your method that returns the currently logged in user just has another name than `current_user`, it may be the easiest solution to simply alias the method in your ApplicationController like this:
+
+```ruby
+class ApplicationController < ActionController::Base
+  alias_method :current_user, :name_of_your_method # Could be :current_member or :logged_in_user
+end
+```
+
+## Strong parameters
+
+If your parameters sanitization method does not follow the naming convention, `load_and_authorize_resource` takes a `param_method` option to specify a custom method in the controller to run to sanitize input.
+
+You can associate the `param_method` option with a symbol corresponding to the name of a method that will get called:
+
+```ruby
+class ArticlesController < ApplicationController
+  load_and_authorize_resource param_method: :my_sanitizer
+
+  def create
+    @article.save
+  end
+
+  private
+
+  def my_sanitizer
+    params.require(:article).permit(:name)
+  end
+end
+```
+
+You can also use a string that will be evaluated in the context of the controller using `instance_eval` and needs to contain valid Ruby code.
+
+```ruby
+load_and_authorize_resource param_method: 'permitted_params.post'
+```
+
+Finally, it's possible to associate `param_method` with a Proc object which will be called with the controller as the only argument:
+
+```ruby
+load_and_authorize_resource param_method: -> { |c| c.params.require(:article).permit(:name) }
+```
+
+If your model name and controller name differ, you can specify a `class` option.
+
+> Note that the method will still be `articles_params` and not `post_params`, since we are in `ArticlesController`.
+
+```ruby
+class ArticlesController < ApplicationController
+  load_and_authorize_resource class: 'Post'
+
+  def create
+    @article.save
+  end
+
+  private
+
+  def article_params
+    params.require(:article).permit(:name)
+  end
+end
+```
+
+## Non RESTful controllers
+
+You can use CanCanCan with controllers that do not follow the traditional REST actions, however you should not use the `load_and_authorize_resource` method since there is no resource to load. Instead you can call `authorize!` in each action separately.
+
+For example, let's say we have a controller which does some miscellaneous administration tasks such as rolling log files. We can use the `authorize!` method here.
+
+```ruby
+class AdminController < ActionController::Base
+  def roll_logs
+    authorize! :roll, :logs
+    # roll the logs here
+  end
+end
+```
+
+And then authorize that in the `Ability` class.
+
+```ruby
+can :roll, :logs if user.admin?
+```
+
+Notice you can pass a symbol as the second argument to both `authorize!` and `can`. It doesn't have to be a model class or instance.
+
+Alternatively you can use the `authorize_resource` and specify that there's no class. This way it will pass the resource symbol instead. This is good if you still have a Resource-like controller but no model class backing it.
+
+```ruby
+class ToolsController < ApplicationController
+  authorize_resource class: false
+
+  def show
+    # automatically calls authorize!(:show, :tool)
+  end
+end
+```
+
+## skip load and authorize
+
+You can use the `skip_load_and_authorize_resource`, `skip_load_resource` or `skip_authorize_resource` methods to skip any of the applied behavior and specify specific actions like in a before filter. For example:
+
+```ruby
+class ProductsController < ActionController::Base
+  load_and_authorize_resource
+  skip_authorize_resource only: :new
+end
+```
+
+### Custom class name
+
+If the model is named differently than the controller, then you may explicitly name the model that should be loaded; however, you must specify that it is not a parent in a nested routing situation, ie:
+
+```ruby
+class ArticlesController < ApplicationController
+  load_and_authorize_resource :post, parent: false
+end
+```
+
+If the model class is namespaced differently than the controller you will need to specify the `:class` option.
+
+```ruby
+class ProductsController < ApplicationController
+  load_and_authorize_resource class: "Store::Product"
+end
+```
+
+### Custom find
+
+If you want to fetch a resource by something other than `id` it can be done so using the `find_by` option.
+
+```ruby
+load_resource find_by: => :permalink # will use find_by!(permalink: params[:id])
+authorize_resource
+```
+
+### Override loading
+
+The resource will only be loaded into an instance variable if it hasn't been already. This allows you to easily override how the loading happens in a separate `before_action`.
+
+```ruby
+class BooksController < ApplicationController
+  before_action :find_published_book, only: :show
+  load_and_authorize_resource
+
+  private
+
+  def find_published_book
+    @book = Book.released.find(params[:id])
+  end
+end
+```
+
+## check_authorization
+
+If you want to be certain authorization is not forgotten in some controller action, add `check_authorization` to your `ApplicationController`.
+
+```ruby
+class ApplicationController < ActionController::Base
+  check_authorization
+end
+```
+
+This will add an `after_action` to ensure authorization takes place in every inherited controller action. If no authorization happens it will raise a `CanCan::AuthorizationNotPerformed` exception. You can skip this check by adding `skip_authorization_check` to that controller. Both of these methods take the same arguments as `before_action` so you can exclude certain actions with `:only` and `:except`.
+
+```ruby
+class UsersController < ApplicationController
+  skip_authorization_check :only => [:new, :create]
+  # ...
+end
+```
+
+The `check_authorization` method supports `:if` and `:unless` options. Either one takes a method name as a symbol. This method will be called to determine if the authorization check will be performed. This makes it very easy to skip this check on all Devise controllers since they provide a `devise_controller?` method.
+
+```ruby
+class ApplicationController < ActionController::Base
+  check_authorization unless: :devise_controller?
+end
+```
+
+Here's another example where authorization is only ensured for the admin subdomain.
+
+```ruby
+class ApplicationController < ActionController::Base
+  check_authorization if: :admin_subdomain?
+
+  private
+
+  def admin_subdomain?
+    request.subdomain == "admin"
+  end
+end
+```
+
+> Note: The `check_authorization` only ensures that authorization is performed. If you have `authorize_resource` the authorization will still be performed no matter what is returned here.
+
+The default operation for CanCanCan is to authorize based on user and the object identified in `load_resource`. So if you have a `WidgetsController` and also an `Admin::WidgetsController`, you can use some different approaches.
+
+# Overriding authorizations for Namespaced controllers
+
+You can create differing authorization rules that depend on the controller namespace.
+
+In this case, just override the `current_ability` method in `ApplicationController` to include the controller namespace, and create an `Ability` class that knows what to do with it.
+
+```ruby
+class Admin::WidgetsController < ActionController::Base
+  #...
+
+  private
+
+  def current_ability
+    # I am sure there is a slicker way to capture the controller namespace
+    controller_name_segments = params[:controller].split('/')
+    controller_name_segments.pop
+    controller_namespace = controller_name_segments.join('/').camelize
+    @current_ability ||= Ability.new(current_user, controller_namespace)
+  end
+end
+
+
+class Ability
+  include CanCan::Ability
+
+  def initialize(user, controller_namespace)
+    case controller_namespace
+      when 'Admin'
+        can :manage, :all if user.has_role? 'admin'
+      else
+        # rules for non-admin controllers here
+    end
+  end
+end
+```
+
+Another way to achieve the same is to use a completely different Ability class in this controller:
+
+```ruby
+class Admin::WidgetsController < ActionController::Base
+  #...
+
+  private
+
+  def current_ability
+    @current_ability ||= AdminAbility.new(current_user)
+  end
+end
+```
diff --git a/docs/check_abilities_mistakes.md b/docs/check_abilities_mistakes.md
new file mode 100644
index 0000000..a9118ea
--- /dev/null
+++ b/docs/check_abilities_mistakes.md
@@ -0,0 +1,48 @@
+# Check abilities - Avoid common mistakes
+
+You now know that you can use the `can?` method in the controller or view to check the user's permission for a given action and object.
+
+```ruby
+can? :destroy, @article
+```
+
+and also that the `cannot?` method is for convenience and performs the opposite check of `can?`
+
+```ruby
+cannot? :destroy, @article
+```
+
+What we want to explain you in this chapter is that you can also pass the class instead of a single instance:
+
+```rhtml
+<% if can? :create, Project %>
+  <%= link_to "New Project", new_project_path %>
+<% end %>
+```
+
+It's important to note here that if a block or hash of conditions exist they will be ignored when checking on a class, and it will return `true`. For example:
+
+```ruby
+can :read, Project, priority: 3
+can? :read, Project # returns true
+```
+
+It is impossible to answer this `can?` question completely because not enough detail is given. Here the class does not have a `priority` attribute to check on.
+
+Think of it as asking
+
+> can the current user read **a** project?"
+
+The user can read a project, so this returns `true`. However it depends on which specific project you're talking about.
+
+If you are doing a class check, it is important you do another check once an instance becomes available so the hash of conditions can be used.
+
+The reason for this behavior is because of the controller `index` action. Since the `authorize_resource` before filter has no instance to check on, it will use the `Project` class. If the authorization failed at that point then it would be impossible to filter the results later when [Fetching Records](./fetching_records.md).
+
+That is why passing a class to `can?` will return `true`.
+
+The code answering the question "can the user update all the articles?" would be something like:
+
+```ruby
+Article.accessible_by(current_ability).count == Article.count
+```
diff --git a/docs/combine_abilities.md b/docs/combine_abilities.md
new file mode 100644
index 0000000..204341d
--- /dev/null
+++ b/docs/combine_abilities.md
@@ -0,0 +1,59 @@
+# Combine abilities
+
+It is possible to define multiple abilities for the same resource. Here the user will be able to read projects which are released OR available for preview.
+
+```ruby
+can :read, Project, released: true
+can :read, Project, preview: true
+```
+
+The `cannot` method takes the same arguments as `can` and defines which actions the user is unable to perform. This is normally done after a more generic `can` call.
+
+```ruby
+can :manage, Project
+cannot :destroy, Project
+```
+
+The order of these calls is important.
+
+## Abilities precedence
+
+An ability rule will override a previous one.
+
+For example, let's say we want the user to be able to do everything to projects except destroy them.
+
+This is the correct way:
+
+```ruby
+can :manage, Project
+cannot :destroy, Project
+```
+
+It is important that the `cannot :destroy` line comes after the `can :manage` line. If they were reversed, `cannot :destroy` would be overridden by `can :manage`.
+
+Adding `can` rules does not override prior rules, but instead are logically or'ed.
+
+```ruby
+can :manage, Project, user: user
+can :update, Project, locked: false
+```
+
+For the above, `can? :update, @project` will return true if project owner is the user, even if the project is locked.
+
+This is also important when dealing with roles which have inherited behavior. For example, let's say we have two roles, moderator and admin. We want the admin to inherit the moderator's behavior.
+
+```ruby
+if user.moderator?
+  can :manage, Project
+  cannot :destroy, Project
+  can :manage, Comment
+end
+
+if user.admin?
+  can :destroy, Project
+end
+```
+
+Here it is important for the admin permissions to be defined after the moderator ones, so it can override the `cannot` behavior to give the admin more permissions.
+
+Let's now check at a different way of defining abilities: [blocks](./define_abilities_with_blocks.md).
diff --git a/docs/controller_helpers.md b/docs/controller_helpers.md
new file mode 100644
index 0000000..ec00a8c
--- /dev/null
+++ b/docs/controller_helpers.md
@@ -0,0 +1,176 @@
+# Controller helpers
+
+As mentioned in the chapter [Define and check abilities](./define_check_abilities.md), the `can?` method works at its best in Rails controllers and views.
+This of course doesn't mean that it cannot be used everywhere.
+
+We know already that in order to check if the user is allowed to perform a certain action we need to have a `current_user` method available and we can check the permission with `can? :update, @article`.
+
+We can easily protect the `edit` and `update` actions of our controller by checking for the permission. Here is a very simple example:
+
+```ruby
+class ArticlesController < ApplicationController
+  def edit
+    @article = Article.find(params[:id])
+    if can? :edit, @article
+      render :edit
+    else
+      head :forbidden
+    end
+  end
+end
+```
+
+## authorize!
+
+CanCanCan provides us a `authorize!` helper that allows us to simplify the code above:
+
+```ruby
+def edit
+  @article = Article.find(params[:id])
+  authorize! :edit, @article
+  render :edit
+end
+```
+
+`authorize!` will raise a `CanCan::AccessDenied` if the action is not permitted.
+
+You can have a global configuration on how to react to this exception in `config/application.rb`:
+
+```ruby
+config.action_dispatch.rescue_responses.merge!('CanCan::AccessDenied' => :unauthorized)
+```
+
+The [Handling CanCan::AccessDenied Exception](./handling_access_denied.md) chapter digs deeper on how to handle the exception raised by `authorize!`.
+
+> `:unauthorized` might not be your favourite return status if you don't want to reveal to the user that the article exists. In such cases, `:not_found` would be a better http status.
+
+## authorize_resource, load_resource, load_and_authorize_resource
+
+In a RESTful controller, calling `authorize! action` for every action can be tedious. Here we will show you, step by step, how to improve the code above.
+
+Add `authorize_resource` in your controller, to call automatically `authorize! action_name, @article` for every action.
+The code above can be refactored like this:
+
+```ruby
+class ArticlesController < ApplicationController
+  before_action :load_article
+  authorize_resource
+
+  def edit;  end
+
+  protected
+
+  def load_article
+    @article = Article.find(params[:id])
+  end
+end
+```
+
+the second helper method is `load_resource` that will perform the loading of the model automatically based on the name of the controller. The code above can be refactored like that:
+
+```ruby
+class ArticlesController < ApplicationController
+  load_resource
+  authorize_resource
+
+  def edit;  end
+end
+```
+
+and, clearly, `load_and_authorize_resource` allows to do the following:
+
+```ruby
+class ArticlesController < ApplicationController
+  load_and_authorize_resource
+
+  def edit; end
+end
+```
+
+this means that a completely authorized `ArticlesController` would look as follow:
+
+```ruby
+class ArticlesController < ApplicationController
+  load_and_authorize_resource
+
+  def index
+    # @articles are already loaded...see details in later chapter
+  end
+
+  def show
+    # the @article to show is already loaded and authorized
+  end
+
+  def create
+    # the @article to create is already loaded, authorized, and params set from article_params
+    @article.create
+  end
+
+  def edit
+    # the @article to edit is already loaded and authorized
+  end
+
+  def update
+    # the @article to update is already loaded and authorized
+    @article.update(article_params)
+  end
+
+  def destroy
+    # the @article to destroy is already loaded and authorized
+    @article.destroy
+  end
+
+  protected
+
+  def article_params
+    params.require(:article).permit(:body)
+  end
+end
+```
+
+## Strong parameters
+
+You have to sanitize inputs before saving the record, in actions such as `:create` and `:update`.
+
+For the `:update` action, CanCanCan will load and authorize the resource but **not** change it automatically, so the typical usage would be something like:
+
+```ruby
+def update
+  if @article.update(article_params)
+    # hurray
+  else
+    render :edit
+  end
+end
+...
+
+def article_params
+  params.require(:article).permit(:body)
+end
+```
+
+For the `:create` action, CanCanCan will try to initialize a new instance with sanitized input by seeing if your controller will respond to the following methods (in order):
+
+1. `create_params`
+2. `<model_name>_params` such as `article_params` (this is the default convention in Rails for naming your param method)
+3. `resource_params` (a generic named method you could specify in each controller)
+
+The typical usage will then be the following:
+
+```ruby
+def create
+  if @article.save
+    # hurray
+  else
+    render :new
+  end
+end
+```
+
+> If you specify a `create_params` or `update_params` method, CanCan will run that method depending on the action you are performing.
+
+In the chapter dedicated to [Customize controller helpers](./changing_defaults.md) we will see more details and customizations for controllers.
+
+There's a dedicated chapter to [Nested resources](./nested_resources.md).
+
+Now that we know how Rails controllers should be protected, we can learn about the most powerful CanCanCan feature: [fetching records](./fetching_records.md).
diff --git a/docs/debugging.md b/docs/debugging.md
new file mode 100644
index 0000000..ad56594
--- /dev/null
+++ b/docs/debugging.md
@@ -0,0 +1,57 @@
+# Debugging Abilities
+
+What do you do when permissions you defined in the Ability class don't seem to be working properly?
+
+Have you already read the [Testing](./testing.md) section? You can now try to reproduce this problem in the `rails console`.
+
+## Debugging Member Actions
+
+```ruby
+# in rails console or test
+user = User.first # fetch any user you want to test abilities on
+project = Project.first # any model you want to test against
+ability = Ability.new(user)
+ability.can?(:create, project) # see if it returns the expected behavior for that action
+```
+
+Note: this assumes that the model instance is being loaded properly. If you are only using `authorize_resource` it will not have an instance to work with so it will use the class.
+
+```ruby
+ability.can?(:create, Project)
+```
+
+## Debugging `index` Action
+
+```ruby
+# in rails console or test
+user = User.first # fetch any user you want to test abilities on
+ability = Ability.new(user)
+ability.can?(:index, Project) # see if user can access the class
+Project.accessible_by(ability) # see if returns the records the user can access
+Project.accessible_by(ability).to_sql # see what the generated SQL looks like to help determine why it's not fetching the records you want
+```
+
+If you find it is fetching the wrong records in complex cases, you may need to use an SQL condition instead of a hash inside the Ability class.
+
+```ruby
+can :update, Project, ["priority < ?", 3] do |project|
+  project.priority < 3
+end
+```
+
+## Logging AccessDenied Exception
+
+If you think the `CanCan::AccessDenied` exception is being raised and you are not sure why, you can log this behavior to help debug what is triggering it.
+
+```ruby
+# in ApplicationController
+rescue_from CanCan::AccessDenied do |exception|
+  Rails.logger.debug "Access denied on #{exception.action} #{exception.subject.inspect}"
+  # ...
+end
+```
+
+## Issue Tracker
+
+If you are still unable to resolve the issue, [open a question on Stackoverflow](https://stackoverflow.com/questions/ask?tags=cancancan) with tag
+[cancancan](https://stackoverflow.com/questions/tagged/cancancan).
diff --git a/docs/define_abilities_best_practices.md b/docs/define_abilities_best_practices.md
new file mode 100644
index 0000000..eeed9a9
--- /dev/null
+++ b/docs/define_abilities_best_practices.md
@@ -0,0 +1,80 @@
+# Defining Abilities: Best Practices
+
+## Use hash conditions as much as possible
+
+### Although scopes are fine for fetching, they pose a problem when authorizing a discrete action.
+
+For example, this declaration in Ability:
+
+```ruby
+can :read, Article, Article.is_published
+```
+
+causes this `CanCan::Error`:
+
+```
+The can? and cannot? call cannot be used with a raw sql 'can' definition.
+The checking code cannot be determined for :read #<Article ..>.
+```
+
+A better way to define the same is:
+
+```ruby
+can :read, Article, is_published: true
+```
+
+### Hash conditions are DRYer.
+
+By using hashes instead of blocks for all actions, you won't have to worry about translating blocks used for member controller actions (`:create`, `:destroy`, `:update`) to equivalent blocks for collection actions (`:index`, `:show`)—which require hashes anyway!
+
+### Hash conditions are OR'd in SQL, giving you maximum flexibility.
+
+Every time you define an ability with `can`, each `can` chains together with OR in the final SQL query for that model.
+
+So if, in addition to the `is_published` condition above, we want to allow authors to see their drafts:
+
+```ruby
+can :read, Article, author_id: @user.id, is_published: false
+```
+
+Then the final SQL would be:
+
+```sql
+SELECT `articles`.*
+FROM   `articles`
+WHERE  `articles`.`is_published` = 1
+OR ( `articles`.`author_id` = 97 AND `articles`.`is_published` = 0 )
+```
+
+### For complex object graphs, hash conditions accommodate `joins` easily.
+
+See [Hash of Conditions Chapter](./hash_of_conditions.md).
+
+### Give permissions, don't take them away
+
+As suggested in this [topic on Reddit](https://www.reddit.com/r/ruby/comments/6ytka8/refactoring_cancancan_abilities_brewing_bits/) you should, when possible, give increasing permissions to your users.
+
+CanCanCan increases permissions: it starts by giving no permissions to nobody and then increases those permissions depending on the user.
+
+A properly written `ability.rb` looks like that:
+
+```ruby
+class Ability
+  include CanCan::Ability
+
+  def initialize(user)
+    can :read, Post  # start by defining rules for all users, also not logged ones
+    return unless user.present?
+    can :manage, Post, user_id: user.id # if the user is logged in can manage it's own posts
+    can :create, Comment # logged in users can also create comments
+    return unless user.manager? # if the user is a manager we give additional permissions
+    can :manage, Comment # like managing all comments in the website
+    return unless user.admin?
+    can :manage, :all # finally we give all remaining permissions only to the admins
+  end
+end
+```
+
+following this good practice will help you to keep your permissions clean and more readable.
+
+The risk of giving wrong permissions to the wrong users is also decreased.
diff --git a/docs/define_abilities_with_blocks.md b/docs/define_abilities_with_blocks.md
new file mode 100644
index 0000000..60b2548
--- /dev/null
+++ b/docs/define_abilities_with_blocks.md
@@ -0,0 +1,60 @@
+# Define abilities with blocks
+
+If your conditions are too complex to define in a [hash of conditions](./hash_of_conditions.md), you can use a block to define them in Ruby.
+
+```ruby
+can :update, Project do |project|
+  project.priority < 3
+end
+```
+
+Note that if you pass a block to a `can` or `cannot`, the block only executes if an instance of a class is passed to `can?` or `cannot?` calls.
+
+If you define a `can` or `cannot` with a block and an object is not passed, the check will pass.
+
+```ruby
+can :update, Project do |project|
+  false
+end
+```
+
+```ruby
+can? :update, Project # returns true!
+```
+
+## Fetching Records
+
+A block's conditions are only executable through Ruby. If you are [Fetching Records](./fetching_records.md) using `accessible_by` it will raise an exception.
+
+To fetch records from the database you need to supply an SQL string representing the condition. The SQL will go in the `WHERE` clause:
+
+```ruby
+can :update, Project, ["priority < ?", 3] do |project|
+  project.priority < 3
+end
+```
+
+> If you are using `load_resource` and don't supply this SQL argument, the instance variable will not be set for the `index` action since they cannot be translated to a database query.
+
+## Block Conditions with ActiveRecord Scopes
+
+It's also possible to pass a scope instead of an SQL string when using a block in an ability.
+
+```ruby
+can :read, Article, Article.published do |article|
+  article.published_at <= Time.now
+end
+```
+
+This is really useful if you have complex conditions which require `joins`. A couple of caveats:
+
+- You cannot use this with multiple `can` definitions that match the same action and model since it is not possible to combine them. An exception will be raised when that is the case.
+- If you use this with `cannot`, the scope needs to be the inverse since it's passed directly through. For example, if you don't want someone to read discontinued products the scope will need to fetch non discontinued ones:
+
+```ruby
+cannot :read, Product, Product.where(discontinued: false) do |product|
+  product.discontinued?
+end
+```
+
+It is only recommended to use scopes if a situation is too complex for a hash condition.
diff --git a/docs/define_check_abilities.md b/docs/define_check_abilities.md
new file mode 100644
index 0000000..476b9e1
--- /dev/null
+++ b/docs/define_check_abilities.md
@@ -0,0 +1,232 @@
+# Define and Check abilities
+
+CanCanCan is an authorization library and therefore the first and most interesting thing to learn is how to define and check abilities. During the [installation](./installation.md) you generated an `ability.rb` file but you don't know yet how to use it.
+
+There are two basic methods in CanCanCan that you will use:
+
+```ruby
+can actions, subjects, conditions
+# without the question mark
+```
+
+is how you define who can **perform** certain `actions` on certain `subjects`.
+
+```ruby
+can? action, subject
+```
+
+will be the method that you will use to **check** if the user is authorized to perform a certain `action` on a certain `subject`.
+
+We don't want to be too abstract here so let's start with a very concrete example.
+
+> We have a blog with articles and the first thing you want to control is "who can edit an article?"
+
+```ruby
+class Article
+  belongs_to :user
+end
+```
+
+The answer to this question is that
+
+> "only the author can edit an article."
+
+We can define the permissions in the `ability.rb`:
+
+```ruby
+class Ability
+  include CanCan::Ability
+
+  def initialize(user)
+    can :update, Article, user: user
+  end
+end
+
+# from here on we will skip the ability.rb file structure
+```
+
+And we can easily check with the following call:
+
+```ruby
+@article = Article.find(params[:id])
+
+can? :update, @article # => true
+```
+
+But how does CanCanCan know who is the `user`?
+When you use the `can?` method in a Rails controller or view, CanCanCan expects that there's a `current_user` method defined. So if you are using something like [devise](https://github.com/heartcombo/devise) for your authentication, you don't need to do anything special.
+
+By default, CanCanCan assumes no permissions: no one can do any action on any object.
+
+`can :update, Article, user: user` is stating that the user can update an article, if it is its author.
+
+Regarding the `Article` there are actually more permissions to check:
+
+- who can read them?
+- what can the administrator do?
+
+A complete example looks like the following:
+
+```ruby
+can :read, Article, published: true
+
+return unless user.present?
+
+can :read, Article, user: user
+can :update, Article, user: user
+
+return unless user.admin?
+
+can :read, Article
+can :update, Article
+```
+
+The code above is stating the following:
+
+- users that are not logged in, can read published articles
+- logged in users can **also** read and update their own articles
+- administrators can read and update all the articles.
+
+> CanCanCan works, at its best, when defining increasing permissions.
+
+The code above can be simplified like this:
+
+```ruby
+can :read, Article, published: true
+
+return unless user.present?
+
+can [:read, :update], Article, user: user
+
+return unless user.admin?
+
+can [:read, :update], Article
+```
+
+Now that we know the basics of defining and checking abilities, let's check what are the possible actions.
+
+## Can Actions
+
+CanCanCan offers four aliases: `:read`, `:create`, `:update`, `:destroy` for the actions. These aren't the same as the seven Restful actions in Rails. CanCanCan automatically adds some convenient aliases for mapping the controller actions.
+
+```ruby
+read: [:index, :show]
+create: [:new, :create]
+update: [:edit, :update]
+destroy: [:destroy]
+```
+
+this means that when you define `can :read, Article`, you can also check:
+
+```ruby
+can? :show, @article
+```
+
+when you define `can :update, Article`, you can also check:
+
+```ruby
+can? :edit, @article
+```
+
+This will be very convenient when we will authorize the Rails Controller actions.
+
+For now, what you need to know, is that these four will be your most used, basic actions.
+
+One last action is `manage`. This action means that you have full permissions on the subject and you can perform any possible action. Knowing that, we can now rewrite our ability.rb example:
+
+```ruby
+can :read, Article, published: true
+
+return unless user.present?
+
+can [:read, :update], Article, user: user
+
+return unless user.admin?
+
+can :manage, Article
+```
+
+and say that the administrators are able to perform any action on the articles.
+
+```ruby
+can? :edit, @article # => true
+can? :destroy, @article # => true
+```
+
+Now that we learned about actions and their aliases let's see what we can do with the subjects
+
+## Can subjects
+
+The subject of an action is usually a Ruby class. Most of the times you want to define your permissions on specific classes, but this is not your only option.
+
+You can actually use any subject, and one of the most common cases is to just use a symbol.
+An admin dashboard could be protected by defining:
+
+```ruby
+can :read, :admin_dashboard
+```
+
+and checked with `can? :read, :admin_dashboard`.
+
+One special symbol is `:all`. All will allow an action on all possible subjects.
+
+In our example, it would not be uncommon to see the following:
+
+```ruby
+can :read, Article, published: true
+
+return unless user.present?
+
+can [:read, :update], Article, user: user
+
+return unless user.admin?
+
+can :manage, :all
+```
+
+and give all possible permissions to the administrator.
+
+Note that the code above allows the administrator to also `:read, :admin_dashboard`. `:manage` means literally **any** action, not only CRUD ones.
+
+> You **must and should** always check for specific permissions, but you don't need to define all of them if not needed.
+
+If at some point you have a new page reserved to the administrators, where they can translate articles, you should check for `can? :translate, @article`, but you don't need to define the ability, since the administrators can already do any action. It will be easy in the future to give the possibility for authors to translate their own articles by changing your permissions file:
+
+```ruby
+can :read, Article, published: true
+
+return unless user.present?
+
+can [:read, :update, :translate], Article, user: user
+
+return unless user.admin?
+
+can :manage, :all
+```
+
+## Checking other users abilities
+
+What if you want to determine the abilities of a `User` record that is not the `current_user`? Maybe we want to see if another user can update an article.
+
+```ruby
+Ability.new(some_user).can? :update, @article
+```
+
+You can also add an `ability` method in the `User` model and delegate the `can?` method:
+
+```ruby
+# app/models/user.rb
+class User
+  delegate :can?, :cannot?, to: :ability
+
+  def ability
+    @ability ||= Ability.new(self)
+  end
+end
+
+some_user.can? :update, @article
+```
+
+That's everything you know about defining and checking abilities. The DSL is very easy but yet very powerful. There's still a lot you need/should learn about defining abilities. You can [dig deeper](./hash_of_conditions.md) now, but we would suggest to stop, digest it, and proceed on a more Rails-specific topic: [Controller helpers](./controller_helpers.md) where you will learn how to secure your Rails application.
+
+Or you could already take a look at the session about [testing](./testing.md).
diff --git a/docs/devise.md b/docs/devise.md
new file mode 100644
index 0000000..3083951
--- /dev/null
+++ b/docs/devise.md
@@ -0,0 +1,28 @@
+# Devise
+
+You should bypass CanCanCan's authorization for Devise controllers:
+
+```ruby
+class ApplicationController < ActionController::Base
+  protect_from_forgery
+
+  check_authorization unless: :devise_controller?
+end
+```
+
+It may be a good idea to specify the rescue from action:
+
+```ruby
+rescue_from CanCan::AccessDenied do |exception|
+  if current_user.nil?
+    session[:next] = request.fullpath
+    redirect_to login_url, alert: 'You have to log in to continue.'
+  else
+    respond_to do |format|
+      format.json { render nothing: true, status: :not_found }
+      format.html { redirect_to main_app.root_url, alert: exception.message }
+      format.js   { render nothing: true, status: :not_found }
+    end
+  end
+end
+```
diff --git a/docs/fetching_records.md b/docs/fetching_records.md
new file mode 100644
index 0000000..56ff7f6
--- /dev/null
+++ b/docs/fetching_records.md
@@ -0,0 +1,78 @@
+# Fetching records
+
+One of the key features of CanCanCan, compared to other authorization libraries, is the possibility to retrieve all the objects that the user is authorized to access. The following:
+
+```ruby
+Article.accessible_by(current_ability)
+```
+
+will use the rules you already defined to ensure that the users retrieve only a list of articles that they can read.
+
+This tool is very powerful and magic at the same time.
+
+Given the following ability file:
+
+```ruby
+can :read, Article, published: true
+
+return unless user.present?
+
+can :read, Article, user: user
+
+return unless user.admin?
+
+can :manage, :all
+```
+
+you will not only be able to check if the user `can? :read, @article` on a single article, but also to limit the articles fetched from the database, to only the ones that they can read.
+
+In an `index` action the following will just work:
+
+```ruby
+@articles = Article.accessible_by(current_ability)
+```
+
+`current_ability` is already made available by CanCanCan in your controller and the default action of `accessible_by` is `:index`, which is aliased by `:read`.
+
+You can change the action by passing it as the second argument. Here we find only the records the user has permission to update.
+
+```ruby
+@articles = Article.accessible_by(current_ability, :update)
+```
+
+And this is just an ActiveRecord scope so other scopes and pagination can be chained onto it.
+
+## Under the hood
+
+The call to accessible_by in the example above will generate the proper SQL to limit the records fetched.
+
+This works also with multiple `can` definitions, which allows you to define complex permission logic and have it translated properly to SQL.
+
+Given the definition:
+
+```ruby
+class Ability
+  can :read, Article, public: true
+  cannot :read, Article, self_managed: true
+  can :read, Article, user: user
+end
+```
+
+a call to `Article.accessible_by(current_ability)` generates the following SQL
+
+```sql
+SELECT *
+FROM articles
+WHERE (user_id = 1) OR (not (self_managed = 'true') AND (public = 'true'))
+```
+
+The generation of the SQL query is a very complex task and probably the most powerful feature of CanCanCan.
+
+Even if the default behaviour will suffice at the beginning, larger databases or more complex rules, might lead to very complex SQL queries. 
+This might result in a slow fetching of records. This is why it is possible to use different strategies to generate the SQL.
+You will see that in one of the last chapters: [SQL strategies](./sql_strategies.md)
+
+## Blocks
+
+We haven't spoken about block abilities yet, but the SQL generation will not be possible if you have even a single rule that is defined using just a block.
+You can define SQL fragments in addition to block to fix that. But we'll see that in the [Define Abilities with Blocks](./define_abilities_with_blocks.md) chapter.
diff --git a/docs/friendly_id.md b/docs/friendly_id.md
new file mode 100644
index 0000000..b20c03e
--- /dev/null
+++ b/docs/friendly_id.md
@@ -0,0 +1,33 @@
+# FriendlyId
+
+If you are using [FriendlyId](https://github.com/norman/friendly_id) you will probably like something to make CanCanCan compatible with it.
+
+You do not have to write `find_by :slug` or something like that, that is always error prone.
+
+You just need to create a `config/initializers/cancancan.rb` file with:
+
+```ruby
+if defined?(CanCanCan)
+  class Object
+    def metaclass
+      class << self; self; end
+    end
+  end
+
+  module CanCan
+    module ModelAdapters
+      class ActiveRecord4Adapter < AbstractAdapter
+        @@friendly_support = {}
+
+        def self.find(model_class, id)
+          klass =
+          model_class.metaclass.ancestors.include?(ActiveRecord::Associations::CollectionProxy) ?
+            model_class.klass : model_class
+          @@friendly_support[klass]||=klass.metaclass.ancestors.include?(FriendlyId)
+          @@friendly_support[klass] == true ? model_class.friendly.find(id) : model_class.find(id)
+        end
+      end
+    end
+  end
+end
+```
diff --git a/docs/handling_access_denied.md b/docs/handling_access_denied.md
new file mode 100644
index 0000000..e5c6a32
--- /dev/null
+++ b/docs/handling_access_denied.md
@@ -0,0 +1,106 @@
+# Handling CanCan::AccessDenied
+
+In the [Controller helpers](./controller_helpers.md) chapter, we saw that when a resource is not authorized, a `CanCan::AccessDenied` exception is raised, and we offered a basic handling through `config/application.rb`. Let's now see what else we can do.
+
+The `CanCan::AccessDenied` exception is raised when calling `authorize!` in the controller and the user is not able to perform the given action.
+
+A message can optionally be provided.
+
+```ruby
+authorize! :read, Article, :message => "Unable to read this article."
+```
+
+This exception can also be raised manually if you want more custom behavior.
+
+```ruby
+raise CanCan::AccessDenied.new("Not authorized!", :read, Article)
+```
+
+The message can also be customized through internationalization.
+
+```yaml
+# in config/locales/en.yml
+en:
+  unauthorized:
+    manage:
+      all: "Not authorized to %{action} %{subject}."
+      user: "Not allowed to manage other user accounts."
+    update:
+      project: "Not allowed to update this project."
+    action_name:
+      model_name: "..."
+```
+
+Notice `manage` and `all` can be used to generalize the subject and actions. Also `%{action}` and `%{subject}` can be used as interpolated variables in the message.
+
+You can catch the exception and modify its behavior in the `ApplicationController`. The behavior may vary depending on the request format. For example here we set the error message to a flash and redirect to the home page for HTML requests and return `403 Forbidden` for JSON requests.
+
+```ruby
+class ApplicationController < ActionController::Base
+  rescue_from CanCan::AccessDenied do |exception|
+    respond_to do |format|
+      format.json { head :forbidden }
+      format.html { redirect_to root_path, alert: exception.message }
+    end
+  end
+end
+```
+
+The action and subject can be retrieved through the exception to customize the behavior further.
+
+```ruby
+exception.action # => :read
+exception.subject # => Article
+```
+
+The default error message can also be customized through the exception. This will be used if no message was provided.
+
+```ruby
+exception.default_message = "Default error message"
+exception.message # => "Default error message"
+```
+
+## Rescuing exceptions for XML responses
+
+If your web application provides a web service which returns XML or JSON responses then you will likely want to handle Authorization properly with a 403 response. You can do so by rendering a response when rescuing from the exception.
+
+```ruby
+rescue_from CanCan::AccessDenied do |exception|
+  respond_to do |format|
+    format.json { render nothing: true, status: :forbidden }
+    format.xml { render xml: '...', status: :forbidden }
+    format.html { redirect_to main_app.root_url, alert: exception.message }
+  end
+end
+```
+
+## Danger of exposing sensible information
+
+Please read [this thread](https://github.com/CanCanCommunity/cancancan/issues/437) for more information.
+
+In a Rails application, if a record is not found during `load_and_authorize_resource` it raises `ActiveRecord::NotFound` before it checks _authentication_ in the `authorize` step.
+
+This means that secured routes can have their resources discovered without even being signed in:
+
+```
+$ curl -I https://app.example.com/restricted_resource/does-not-exist
+HTTP/1.1 404 Not Found
+
+$ curl -I https://app.example.com/restricted_resource/does-exist-but-not-permitted
+HTTP/1.1 302 Found
+Location: https://app.example.com/sessions/new
+```
+
+A more secure approach is to **always** return a 404 status instead of 302:
+
+```ruby
+class ApplicationController < ActionController::Base
+  rescue_from CanCan::AccessDenied do |exception|
+    respond_to do |format|
+      format.json { render nothing: true, status: :not_found }
+      format.html { redirect_to main_app.root_url, notice: exception.message, status: :not_found }
+      format.js   { render nothing: true, status: :not_found }
+    end
+  end
+end
+```
diff --git a/docs/hash_of_conditions.md b/docs/hash_of_conditions.md
new file mode 100644
index 0000000..7375d81
--- /dev/null
+++ b/docs/hash_of_conditions.md
@@ -0,0 +1,85 @@
+# Defining abilities - Hash of conditions
+
+Let's start our journey into the abilities definition by explaining the CanCanCan Hash of conditions mechanism.
+
+In the chapter [Define and Check Abilities](./define_check_abilities.md) we defined
+
+```ruby
+can :update, @article, user: user
+```
+
+to say that an Article can be updated only by it's author. But how does it work?
+
+The third argument of the `can` method (`{ user: user }`) is the hash of conditions for this rule.
+
+A hash of conditions can be passed to further restrict which records this permission applies to.
+
+In the example below the user will only have permission to read active projects which they own.
+
+```ruby
+can :read, Project, active: true, user_id: user.id
+```
+
+When defining a condition, the key should always be either a database column of the model, or the association name. In the example above, if the Project has defined
+
+```ruby
+belongs_to :owner, class_name: 'User', foreign_key: :user_id
+```
+
+the rule can also be written as:
+
+```ruby
+can :read, Project, active: true, owner: user
+```
+
+so by using the association `owner` instead of the database column `user_id`.
+
+You can nest conditions associations. Here the project can only be read if the category it belongs to is visible.
+
+```ruby
+can :read, Project, category: { visible: true }
+```
+
+An array or range can be passed to match multiple values. Here the user can only read projects of priority 1 through 3.
+
+```ruby
+can :read, Project, priority: 1..3
+```
+
+If you want to a negative match, you can pass in `nil`.
+
+```ruby
+# Can read projects that don't have any members.
+can :read, Project, members: { id: nil }
+```
+
+Almost anything that you can pass to a hash of conditions in ActiveRecord will work here as well.
+
+## Traverse associations
+
+All associations can be traversed when defining a rule.
+
+```ruby
+class User
+  belongs_to :account
+end
+
+class Account
+  has_one :user
+  has_many :services
+end
+
+class Service
+  belongs_to :account
+  has_many :parts
+end
+
+class Part
+  belongs_to :service
+end
+
+# Ability
+can :manage, Part, service: { account: { user: user } }
+```
+
+Let's now quickly see how to [Combine Abilities](./combine_abilities.md)
diff --git a/docs/inherited_resources.md b/docs/inherited_resources.md
new file mode 100644
index 0000000..b65c53a
--- /dev/null
+++ b/docs/inherited_resources.md
@@ -0,0 +1,47 @@
+# Inherited Resources
+
+**This guide is for cancancan < 2.0 only.
+If you want to use Inherited Resources and cancancan 2.0 please check for extensions like [cancan-inherited_resources](https://github.com/TylerRick/cancan-inherited_resources).
+
+The `load_and_authorize_resource` call will automatically detect if you are using [Inherited Resources](https://github.com/activeadmin/inherited_resources) and load the resource through that. The `load` part in CanCan is still necessary since Inherited Resources does lazy loading. This will also ensure the behavior is identical to normal loading.
+
+```ruby
+class ProjectsController < InheritedResources::Base
+  load_and_authorize_resource
+end
+```
+
+if you are doing nesting you will need to mention it in both Inherited Resources and CanCan.
+
+```ruby
+class TasksController < InheritedResources::Base
+  belongs_to :project
+  load_and_authorize_resource :project
+  load_and_authorize_resource :task, :through => :project
+end
+```
+
+<i>Please note that even for a `has_many :tasks` association, the `load_and_authorize_resource` needs the singular name of the associated model...</i>
+
+**Warning**: when overwriting the `collection` method in a controller the `load` part of a `load_and_authorize_resource` call will not work correctly. See <https://github.com/ryanb/cancan/issues/274> for the discussions.
+
+In this case you can override collection like
+
+```ruby
+skip_load_and_authorize_resource :only => :index
+
+def collection
+  @products ||= end_of_association_chain.accessible_by(current_ability).paginate(:page => params[:page], :per_page => 10)
+end
+```
+
+## Mongoid
+
+With mongoid it is necessary to reference `:project_id` instead of just `:project`
+
+```ruby
+class TasksController < InheritedResources::Base
+  ...
+  load_and_authorize_resource :task, :through => :project_id
+end
+```
diff --git a/docs/installation.md b/docs/installation.md
new file mode 100644
index 0000000..78c06f0
--- /dev/null
+++ b/docs/installation.md
@@ -0,0 +1,35 @@
+# Installation
+
+Add this to your Gemfile:
+
+```ruby
+gem 'cancancan'
+```
+
+and run the `bundle install` command.
+
+Use the provided command to generate a template for your abilities file:
+
+```bash
+rails generate cancan:ability
+```
+
+This will generate the following file:
+
+```ruby
+# /app/models/ability.rb
+
+class Ability
+  include CanCan::Ability
+
+  def initialize(user)
+  end
+end
+```
+
+This is everything you need to start. :boom:
+
+All the permissions will be defined in this file.
+You can of course split it into multiple files if your application grows, but we'll cover that in a [later chapter](./split_ability.md).
+
+Let's now start with the basic concepts: [define and check abilities](./define_check_abilities.md).
diff --git a/docs/internationalization.md b/docs/internationalization.md
new file mode 100644
index 0000000..578f930
--- /dev/null
+++ b/docs/internationalization.md
@@ -0,0 +1,61 @@
+# Internationalization
+
+To use translations in your app define some yaml like this:
+
+```yaml
+# en.yml
+en:
+  unauthorized:
+    manage:
+      all: "You have no access to this resource"
+```
+
+## Translation for individual abilities
+
+If you want to customize messages for some model or even for some ability, define translation like this:
+
+```ruby
+# models/ability.rb
+...
+can :create, Article
+...
+```
+
+```yaml
+# en.yml
+en:
+  unauthorized:
+    create:
+      article: "Only an admin can create an article"
+```
+
+### Translating custom abilities
+
+Also translations is available for your custom abilities:
+
+```ruby
+# models/ability.rb
+...
+can :vote, Article
+...
+```
+
+```yaml
+# en.yml
+en:
+  unauthorized:
+    vote:
+      article: "Only users which have one or more article can vote"
+```
+
+## Variables for translations
+
+Finally you may use `action`(which contain ability like 'create') and `subject`(for example 'article') variables in your translation:
+
+```yaml
+# en.yml
+en:
+  unauthorized:
+    manage:
+      all: "You do not have access to %{action} %{subject}!"
+```
diff --git a/docs/introduction.md b/docs/introduction.md
new file mode 100644
index 0000000..74ee761
--- /dev/null
+++ b/docs/introduction.md
@@ -0,0 +1,31 @@
+# Introduction
+
+Hi everyone :wave:,<br>
+I am [Alessandro](https://github.com/coorasse), the maintainer of CanCanCan since 2015. I want to present you this new guide for developers.
+
+Since I took over the CanCanCan project in 2015, I felt one of the most urgent things to do was rewriting the documentation:
+the previous documentation was in a Wiki, but giving free access to everyone to edit it, ended up in a big mess after so many years.
+**Information was all there, but in a very unstructured way.**
+
+One of the first things I did was to block the Wiki from free editing and after that, I moved all the wiki within the git repository, so that every update had to go through a Pull Request and code review.
+
+**I think documentation is as important as the source code, and therefore should follow the same approval process of the code.**
+
+After one year from this decision, I realised that this was not sufficient: I still didn't like the status of the documentation,
+and I had to explain too often to my colleagues at [Renuo](https://renuo.ch) the basic mechanisms of this library.
+And all my colleagues are all very smart people!!
+So I understood that we were still missing a well structured developer documentation.
+
+I hope you'll appreciate this work and you'll now be able to use CanCanCan at it's maximum potential.
+
+I suggest also experienced CanCanCan developers to read it, since you might find out interesting features that have been recently introduced and you might not be aware of, since they were not previously documented.
+
+Thanks again to all my sponsors, who allowed me to take the time to properly write this documentation, and thanks to all the contributors for your help with this wonderful library :heart:.
+
+If you'd like to sponsor this library, head to <https://github.com/sponsors/coorasse>. It means a lot.
+
+Thank you,
+
+Alessandro Rodi
+
+Head to the [Installation](./installation.md) chapter.
diff --git a/docs/migrating.md b/docs/migrating.md
new file mode 100644
index 0000000..a669947
--- /dev/null
+++ b/docs/migrating.md
@@ -0,0 +1,19 @@
+# Migration Guide
+
+## From 2.x to 3.x
+
+### Breaking changes
+
+- **Defining abilities without a subject is not allowed anymore.**
+  For example, `can :dashboard` is not going to be accepted anymore and will raise an exception.
+  All these kind of rules need to be rethought in terms of `can action, subject`. `can :read, :dashboard` for example.
+
+- **Eager loading is not automatic.** If you relied on CanCanCan to avoid N+1 queries, this will not be the case anymore.
+  From now on, all necessary `includes`, `preload` or `eager_load` need to be explicitly written. We strongly suggest to have
+  `bullet` gem installed to identify your possible N+1 issues.
+
+- **Use of distinct.** Uniqueness of the results is guaranteed by using the `distinct` clause in the final query.
+  This may cause issues with some existing queries when using clauses like `group by` or `order` on associations.
+  Adding a custom `select` may be necessary in these cases.
+
+- **aliases are now merged.** When using the method to merge different Ability files, the aliases are now also merged. This might cause some incompatibility issues.
diff --git a/docs/model_adapter.md b/docs/model_adapter.md
new file mode 100644
index 0000000..0ebd36a
--- /dev/null
+++ b/docs/model_adapter.md
@@ -0,0 +1,210 @@
+# Model Adapter
+
+CanCanCan includes a model adapter system that allows developers to add their own adapters for handling behaviour depending on the model used.
+
+CanCanCan provides maintained adapters for the following model types:
+
+- ActiveRecord (native in `cancancan` gem)
+  - ActiveRecord 4
+  - ActiveRecord 5
+- [Mongoid](https://github.com/CanCanCommunity/cancancan-mongoid)
+
+## Creating a Model Adapter
+
+Due to its flexible and extendable system of adapters, it is easy to implement a custom adapter if the currently provided adapters do not suffice.
+
+To facilitate an easy implementation of a new adapter CanCanCan provides you with an [Abstract Adapter](https://github.com/CanCanCommunity/cancancan/blob/develop/lib/cancan/model_adapters/abstract_adapter.rb) you can extend and build upon. This design allows for dynamic adapter handling and a decoupled handling of information.
+
+### The Abstract Adapter
+
+The abstract adapter has multiple methods that one has to overwrite in order to match the behaviour that is expected. It is used by the system to delegate the handling of fetching entries base on defined rules and conditions.
+
+#### for_class
+
+The `for_class?` method is a static method on the abstract adapter that has to be overwritten in your adapter.
+
+This method is used to determine whether a model should be passed to the adapter or not.
+
+If your `for_class?` implementation returns true, the adapter will be provided with the model to build and match the rules defined.
+
+Otherwise the adapter will be skipped and the other subclasses of the abstract adapter will be checked.
+
+#### database_records
+
+Used to implement the loading of entries from the database, by a developer-defined handling of the given rules for a model.
+
+### Dependencies
+
+Because cancancan wants to provide an easy method of writing and testing your own adapters it uses appraisals to test the code against different versions of dependencies.
+
+[Appraisals](https://github.com/thoughtbot/appraisal)
+
+Thus you can add your own entry for your gems and dependencies.
+
+An example could look like:
+
+cancancan/Appraisals
+
+```ruby
+
+appraise 'cancancan_custom_adapter' do
+  gem 'activerecord', '~> 5.0.2', require: 'active_record'
+
+  gemfile.platforms :jruby do
+    gem 'jdbc-postgres'
+  end
+
+  gemfile.platforms :ruby, :mswin, :mingw do
+    gem 'pg', '~> 0.21'
+  end
+end
+```
+
+You would have to replace the dependencies with ones that fit your custom adapter.
+
+After creating your dependency definition, run
+
+```bash
+bundle exec appraisal install
+```
+
+to install dependencies for your adapter.
+
+### The Specs
+
+To illustrate what a test for an adapter could look like, we will use [Mongoid](https://github.com/CanCanCommunity/cancancan-mongoid) as an example.
+
+In good TDD fashion we create a spec / test for the new adapter to later confirm our implementation.
+
+```ruby
+
+RSpec.describe CanCan::ModelAdapters::MongoidAdapter do
+
+  it 'is for only Mongoid classes' do
+    expect(CanCan::ModelAdapters::MongoidAdapter).not_to be_for_class(Object)
+    expect(CanCan::ModelAdapters::MongoidAdapter).to be_for_class(MongoidProject)
+  end
+
+  it 'finds record' do
+      project = MongoidProject.create
+      expect(CanCan::ModelAdapters::MongoidAdapter.find(MongoidProject, project.id)).to eq(project)
+  end
+
+  it "should return the correct records based on the defined ability" do
+        @ability.can :read, MongoidProject, :title => "Sir"
+        sir   = MongoidProject.create(:title => 'Sir')
+        lord  = MongoidProject.create(:title => 'Lord')
+        MongoidProject.accessible_by(@ability, :read).entries.should == [sir]
+  end
+
+end
+```
+
+In this case `MongoidProject` is a descendant of `MongoidDocument`. The implementation of this class will not be shown as it only acts as an example.
+
+### Running tests
+
+You can run tests for the project by running
+
+```bash
+bundle exec appraisal rake
+```
+
+or you can run tests only for your adapter with
+
+```bash
+bundle exec appraisal adapter_name rake
+```
+
+File specific tests can be run with:
+
+```shell
+bundle exec appraisal adapter_name rspec spec/cancan/model_adapters/adapter_name.rb
+```
+
+**Because we haven't implemented any functionality yet, the tests will fail.**
+
+### The Implementation
+
+First add a line to `lib/cancan.rb` to include the adapter if a condition is met. In this case we check if Mongoid is present.
+
+```ruby
+require 'cancan/model_adapters/mongoid_adapter' if defined? Mongoid
+```
+
+And after that, create a new adapter in `model_adapters`:
+
+```ruby
+module CanCan
+  module ModelAdapters
+    class MongoidAdapter < AbstractAdapter
+      def self.for_class?(model_class)
+        model_class <= Mongoid::Document
+      end
+
+      def database_records
+        if @rules.size == 0
+          @model_class.where(:_id => {'$exists' => false, '$type' => 7}) # return no records in Mongoid
+        else
+          @rules.inject(@model_class.all) do |records, rule|
+            if rule.base_behavior
+              records.or(rule.conditions)
+            else
+              records.excludes(rule.conditions)
+            end
+          end
+        end
+      end
+    end
+  end
+end
+
+module Mongoid::Document::ClassMethods
+  include CanCan::ModelAdditions::ClassMethods
+end
+```
+
+As mentioned before, there are methods that have to be overwritten in order to pass as a valid adapter.
+
+In this case we overwrite the `for_class?` method to validate that the given model is a descendant of MongoidDocument. The adapter will only be used if `for_class?` evalues to true.
+
+And in `database_records` we define the way data is loaded from the storage device. This message is used in `accessible_by`. In this example we fetch all entries for a model that match a given rule.
+
+**If no rules for an object are defined, a query will be run that returns no results.**
+
+If rules are present, we apply each of the rule conditions to them. The `rule.base_behavior` defines whether the rule should be additive or subtractive. It will result in false for `:cannot` and true for `:can`.
+
+Some model types add additional features to the conditions hash. With Mongoid, for example, you can do something like `:age.gt => 13`.
+Because the abstract adapter has no knowledge of this, we have to overwrite the provided methods in the new adapter.
+
+```ruby
+def self.override_conditions_hash_matching?(subject, conditions)
+  conditions.any? { |k,v| !k.kind_of?(Symbol) }
+end
+
+def self.matches_conditions_hash?(subject, conditions)
+  subject.matches? subject.class.where(conditions).selector
+end
+```
+
+### Additional Examples
+
+Eventhough CanCanCan tries to make the implementation of custom adapters easy and flexible, it can be hard task.
+
+Thus you'd probably be best served with inspecting the actual implementation of the `activerecord` adapter to get a better overview how a battle tested adapter is structured and implemented.
+
+#### Implementation
+
+- [ActiveRecord Base](../lib/cancan/model_adapters/active_record_adapter.rb)
+- [ActiveRecord 4](../lib/cancan/model_adapters/active_record_4_adapter.rb)
+- [ActiveRecord 5](../lib/cancan/model_adapters/active_record_5_adapter.rb)
+
+#### Tests / Specs
+
+- [ActiveRecord Base](../spec/cancan/model_adapters/active_record_adapter_spec.rb)
+- [ActiveRecord 4](../spec/cancan/model_adapters/active_record_4_adapter_spec.rb)
+- [ActiveRecord 5](../spec/cancan/model_adapters/active_record_5_adapter_spec.rb)
+
+**Mongoid, the adapter used in this entry as an example, can be found at:**
+
+- [Mongoid](https://github.com/CanCanCommunity/cancancan-mongoid)
diff --git a/docs/nested_resources.md b/docs/nested_resources.md
new file mode 100644
index 0000000..114280a
--- /dev/null
+++ b/docs/nested_resources.md
@@ -0,0 +1,202 @@
+# Nested Resources
+
+Let's say we have nested resources set up in our routes.
+
+```ruby
+resources :projects do
+  resources :tasks
+end
+```
+
+We can then tell CanCanCan to load the project and then load the task through that.
+
+```ruby
+class TasksController < ApplicationController
+  load_and_authorize_resource :project
+  load_and_authorize_resource :task, through: :project
+end
+```
+
+This will fetch the project using `Project.find(params[:project_id])` on every controller action, save it in the `@project` instance variable, and authorize it using the `:read` action to ensure the user has the ability to access that project. If you don't want to do the authorization you can simply use `load_resource`, but calling just `authorize_resource` for the parent object is insufficient. The task is then loaded through the `@project.tasks` association.
+
+If the name of the association doesn't match the resource name, for instance `has_many :issues, class_name: 'Task'`, you can specify the association name using `:through_association`.
+
+```ruby
+  class TasksController < ApplicationController
+    load_and_authorize_resource :project
+    load_and_authorize_resource :task, through: :project, through_association: :issues
+  end
+```
+
+If the resource name (`:project` in this case) does not match the controller, then it will be considered a parent resource. You can manually specify parent/child resources using the `parent: false` option.
+
+## Securing `through` changes
+
+If you are using `through`, you need to be wary of potential changes to the parent model. For example, consider this controller:
+
+```ruby
+class TasksController < ApplicationController
+  load_and_authorize_resource :project
+  load_and_authorize_resource :task, through: :project
+
+  def update
+    @task.update(task_params)
+  end
+
+  private
+
+  def task_params
+    params.require(:task).permit(:project_id)
+  end
+end
+```
+
+Now consider a request to `/projects/1/tasks/42` with params `{ task: { project_id: 2 } }`.
+
+- `load_and_authorize_resource :project` will load project 1 and authorize it.
+- `load_and_authorize_resource :task, through: :project` will load task 42 from project 1, and authorize it.
+- `@task.update(task_params)` will change the task's project ID from 1, to 2.
+- Project 2 is never authorized! An attacker could inject a project belonging to another customer here.
+
+How you handle this depends on your intended behavior.
+
+- If you don't want a task's project ID to ever change, don't permit it as a param.
+- If you allow tasks to be moved between projects, manually verify the ID change and avoid mass assigning it.
+
+```ruby
+  def update
+    @task.project = Project.find(task_params[:project_id])
+    authorize!(@task)
+    @task.assign(task_params.except(:project_id))
+  end
+```
+
+## Nested through method
+
+It's also possible to nest through a method, this is commonly the `current_user` method.
+
+```ruby
+class ProjectsController < ApplicationController
+  load_and_authorize_resource through: :current_user
+end
+```
+
+Here everything will be loaded through the `current_user.projects` association.
+
+## Shallow nesting
+
+The parent resource is required to be present and it will raise an exception if the parent is ever `nil`. If you want it to be optional (such as with shallow routes), add the `shallow: true` option to the child.
+
+```ruby
+class TasksController < ApplicationController
+  load_and_authorize_resource :project
+  load_and_authorize_resource :task, through: :project, shallow: true
+end
+```
+
+## Singleton resource
+
+What if each project only had one task through a `has_one` association? To set up singleton resources you can use the `:singleton` option.
+
+```ruby
+class TasksController < ApplicationController
+  load_and_authorize_resource :project
+  load_and_authorize_resource :task, through: :project, singleton: true
+end
+```
+
+It will then use the `@project.task` and `@project.build_task` methods for fetching and building respectively.
+
+## Polymorphic associations
+
+Let's say tasks can either be assigned to a Project or an Event through a polymorphic association. An array can be passed into the `:through` option and it will use the first one it finds.
+
+```ruby
+load_resource :project
+load_resource :event
+load_and_authorize_resource :task, through: [:project, :event]
+```
+
+Here it will check both the `@project` and `@event` variables and fetch the task through whichever one exists. Note that this is only loading the parent model, if you want to authorize the parent you will need to do it through a before_action because there is special logic involved.
+
+```ruby
+before_action :authorize_parent
+
+private
+
+def authorize_parent
+  authorize! :read, (@event || @project)
+end
+```
+
+## Accessing parent in ability
+
+Sometimes the child permissions are closely tied to the parent resource. For example, if there is a `user_id` column on Project, one may want to only allow access to tasks if the user owns their project.
+
+This will happen automatically due to the `@project` instance being authorized in the nesting. However it's still a good idea to restrict the tasks separately. You can do so by going through the project association.
+
+```ruby
+# in Ability
+can :manage, Task, project: { user_id: user.id }
+```
+
+This means you will need to have a project tied to the tasks which you pass into here. For example, if you are checking if the user has permission to create a new task, do that by building it through the project.
+
+```ruby
+can? :create, @project.tasks.build
+```
+
+It's also possible to check permission through an association like this.
+
+```ruby
+can? :read, @project => Task
+```
+
+This will use the above `:project` hash conditions and ensure `@project` meets those conditions.
+
+## Has_many through associations
+
+How to load and authorize resources with a `has_many :through` association?
+
+Given that situation:
+
+```ruby
+class User < ActiveRecord::Base
+  has_many :groups_users
+  has_many :groups, through: :groups_users
+end
+```
+
+```ruby
+class Group < ActiveRecord::Base
+  has_many :groups_users
+  has_many :users, through: :groups_users
+end
+```
+
+```ruby
+class GroupsUsers < ActiveRecord::Base
+  belongs_to :group, inverse_of: :groups_users
+  belongs_to :user, inverse_of: :groups_users
+end
+```
+
+and in the controller:
+
+```ruby
+class UsersController < ApplicationController
+  load_and_authorize_resource :group
+  load_and_authorize_resource through: :group
+```
+
+in ability.rb
+
+```ruby
+can :create, User, groups_users: { group: { CONDITION_ON_GROUP } }
+```
+
+Don't forget the **inverse_of** option, it is the trick to make it work correctly.
+
+Remember to define the ability through the **groups_users** model (i.e. don't write `can :create, User, groups: { CONDITION_ON_GROUP }`)
+
+You will be able to persist the association just calling `@user.save` instead of `@group.save`.
diff --git a/docs/role_based_authorization.md b/docs/role_based_authorization.md
new file mode 100644
index 0000000..b83f030
--- /dev/null
+++ b/docs/role_based_authorization.md
@@ -0,0 +1,177 @@
+# Role-based Authorization
+
+CanCanCan is decoupled from how you implement roles in the User model, but how might one set up basic role-based authorization? The pros and cons are described [here](https://github.com/kristianmandrup/cantango/wiki/CanCan-vs-CanTango).
+
+The following approach allows you to simply define the role abilities in Ruby and does not need a role model. Alternatively, [[Separate Role Model]] describes how to define the roles and mappings in a database.
+
+Since there is such a tight coupling between the list of roles and abilities, I recommend keeping the list of roles in Ruby. You can do so in a constant under the User class.
+
+```ruby
+class User < ActiveRecord::Base
+  ROLES = %i[admin moderator author banned]
+end
+```
+
+But now, how do you set up the association between the user and the roles? You'll need to decide if the user can have many roles or just one.
+
+## One role per user
+
+If a user can have only one role, it's as simple as adding a `role` string column to the `users` table.
+
+```bash
+rails generate migration add_role_to_users role:string
+rake db:migrate
+```
+
+In your `users_controller.rb` add `:role` to the list of permitted parameters.
+
+```ruby
+def user_params
+  params.require(:user).permit(:name, :email, :password, :password_confirmation, :role)
+end
+```
+
+If you're using ActiveAdmin don't forget to add `role` to the `user.rb` list of parameters as well
+
+```ruby
+  permit_params :name, :email, :role
+```
+
+Now you can provide a select-menu for choosing the roles in the view.
+
+```rhtml
+<!-- in users/_form.html.erb -->
+<%= f.collection_select(:role, User::ROLES, :to_s, lambda{|i| i.to_s.humanize}) %>
+```
+
+You may not have considered using `collection_select` when you aren't working with an association, but it will work perfectly. In this case the user will see the humanized name of the role, and the simple lower-cased version will be passed in as the value when the form is submitted.
+
+It's then very simple to determine the role of the user in the Ability class.
+
+```ruby
+can :manage, :all if user.role == "admin"
+```
+
+## Many roles per user
+
+It is possible to assign multiple roles to a user and store it into a single integer column using a [bitmask](<http://en.wikipedia.org/wiki/Mask_(computing)>). First add a `roles_mask` integer column to your `users` table.
+
+```bash
+rails generate migration add_roles_mask_to_users roles_mask:integer
+rake db:migrate
+```
+
+Next you'll need to add the following code to the User model for getting and setting the list of roles a user belongs to. This will perform the necessary bitwise operations to translate an array of roles into the integer field.
+
+```ruby
+# in models/user.rb
+def roles=(roles)
+  roles = [*roles].map { |r| r.to_sym }
+  self.roles_mask = (roles & ROLES).map { |r| 2**ROLES.index(r) }.inject(0, :+)
+end
+
+def roles
+  ROLES.reject do |r|
+    ((roles_mask.to_i || 0) & 2**ROLES.index(r)).zero?
+  end
+end
+```
+
+If you're using devise, don't forget to add `attr_accessible :roles` to your user model or add following to application_controller.rb
+
+```ruby
+  before_action :configure_permitted_parameters, if: :devise_controller?
+  protected
+  def configure_permitted_parameters
+    devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:email, :password, :password_confirmation, roles: []) }
+  end
+```
+
+You can use checkboxes in the view for setting these roles.
+
+```rhtml
+<% for role in User::ROLES %>
+  <%= check_box_tag "user[roles][#{role}]", role, @user.roles.include?(role), {:name => "user[roles][]"}%>
+  <%= label_tag "user_roles_#{role}", role.to_s.humanize %><br />
+<% end %>
+<%= hidden_field_tag "user[roles][]", "" %>
+```
+
+Finally, you can then add a convenient way to check the user's roles in the Ability class.
+
+```ruby
+# in models/user.rb
+def has_role?(role)
+  roles.include?(role)
+end
+
+# in models/ability.rb
+can :manage, :all if user.has_role? :admin
+```
+
+See [[Custom Actions]] for a way to restrict which users can assign roles to other users.
+
+This functionality has also been extracted into a little gem called [role_model](http://rubygems.org/gems/role_model) ([code & howto](http://github.com/martinrehfeld/role_model)).
+
+If you do not like this bitmask solution, see [[Separate Role Model]] for an alternative way to handle this.
+
+## Role Inheritance
+
+Sometimes you want one role to inherit the behavior of another role. For example, let's say there are three roles: moderator, admin, superadmin and you want each one to inherit the abilities of the one before. There is also a "role" string column in the User model. You should create a method in the User model which has the inheritance logic.
+
+```ruby
+# in User
+ROLES = %w[moderator admin superadmin]
+def role?(base_role)
+  ROLES.index(base_role.to_s) <= ROLES.index(role)
+end
+```
+
+You then use this in the Ability class.
+
+```ruby
+# in Ability#initialize
+if user.role? :moderator
+  can :manage, Post
+end
+if user.role? :admin
+  can :manage, ForumThread
+end
+if user.role? :superadmin
+  can :manage, Forum
+end
+```
+
+Here a superadmin will be able to manage all three classes but a moderator can only manage the one. Of course you can change the role logic to fit your needs. You can add complex logic so certain roles only inherit from others. And if a given user can have multiple roles you can decide whether the lowest role takes priority or the highest one does. Or use other attributes on the user model such as a "banned", "activated", or "admin" column.
+
+This functionality has been extracted into a gem called [canard](http://rubygems.org/gems/canard) ([code & howto](http://github.com/james2m/canard)).
+
+## Alternative Role Inheritance
+
+If you would like to keep the inheritance rules in the Ability class instead of the User model it is easy to do so like this.
+
+```ruby
+class Ability
+  include CanCan::Ability
+
+  def initialize(user)
+    @user = user || User.new # for guest
+    @user.roles.each { |role| send(role.name_to_symbol) }
+
+    if @user.roles.size == 0
+      can :read, :all #for guest without roles
+    end
+  end
+
+  def manager
+    can :manage, Employee
+  end
+
+  def admin
+    manager
+    can :manage, Bill
+  end
+end
+```
+
+Here each role is a separate method which is called. You can call one role inside another to define inheritance. This assumes you have a `User#roles` method which returns an array of all roles for that user.
diff --git a/docs/rules_compression.md b/docs/rules_compression.md
new file mode 100644
index 0000000..a535dbf
--- /dev/null
+++ b/docs/rules_compression.md
@@ -0,0 +1,55 @@
+# Rules compressions
+
+Database are great on optimizing queries, but sometimes cancancan builds `joins` that might lead to slow performance.
+This is why your rules are optimized automatically at runtime.
+There are a set of "rules" to optimize your rules definition and they are implemented in the `RulesCompressor` class.
+You can always disable the rules compressor by setting `CanCan.rules_compressor_enabled = false` in your initializer.
+You can also enable/disable it on a specific check by using: `with_rules_compressor_enabled(false) { ... }`
+
+Here you can see how this works:
+
+A rule without conditions is defined as `catch_all`.
+
+## A catch_all rule, eliminates all previous rules and all subsequent rules of the same type
+
+```ruby
+can :read, Book, author_id: user.id
+cannot :read, Book, private: true
+can :read, Book
+can :read, Book, id: 1
+cannot :read, Book, private: true
+```
+
+becomes
+
+```ruby
+can :read, Book
+cannot :read, Book, private: true
+```
+
+### If a catch_all cannot rule is first, it can be removed
+
+```ruby
+cannot :read, Book
+can :read, Book, author_id: user.id
+```
+
+becomes
+
+```ruby
+can :read, Book, author_id: user.id
+```
+
+### If all rules are cannot rules, this is equivalent to no rules
+
+```ruby
+cannot :read, Book, private: true
+```
+
+becomes
+
+```ruby
+# nothing
+```
+
+These optimizations allow you to follow the strategy of ["Give Permissions, don't take them"](https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities%3A-Best-Practices#give-permissions-dont-take-them-away) and automatically ignore previous rules when they are not needed.
diff --git a/docs/split_ability.md b/docs/split_ability.md
new file mode 100644
index 0000000..5fb0b7b
--- /dev/null
+++ b/docs/split_ability.md
@@ -0,0 +1,78 @@
+# Split the ability file
+
+When the application becomes more complex and many abilities are defined, you might want to start splitting your ability file into multiple files.
+
+We will show here an example on how to split your ability file on a “per-model” basis.
+
+Imagine the following scenario:
+
+```ruby
+# app/models/ability.rb
+class Ability
+  include CanCan::Ability
+  def initialize(user)
+    can :edit, User, id: user.id
+    can :read, Book, published: true
+    can :edit, Book, user_id: user.id
+    can :manage, Comment, user_id: user.id
+  end
+end
+```
+
+This is, of course, not too complicated, and in a real world application we would not split this file, but for didactic reasons we want to split this file “per-model”.
+
+We suggest to have an app/abilities folder and create a separate file for each model (exactly as you would do with Pundit).
+
+```ruby
+# app/abilities/user_ability.rb
+class UserAbility
+  include CanCan::Ability
+  def initialize(user)
+    can :edit, User, id: user.id
+  end
+end
+
+# app/abilities/comment_ability.rb
+class CommentAbility
+  include CanCan::Ability
+  def initialize(user)
+    can :manage, Comment, user_id: user.id
+  end
+end
+
+# app/abilities/book_ability.rb
+class BookAbility
+  include CanCan::Ability
+  def initialize(user)
+    can :read, Book, published: true
+    can :edit, Book, user_id: user.id
+  end
+end
+```
+
+Now you can override the `current_ability` method in you controller. For example:
+
+```ruby
+# app/controllers/books_controller.rb
+class BooksController
+  def current_ability
+    @current_ability ||= BookAbility.new(current_user)
+  end
+end
+```
+
+Using this technique you have all the power of CanCanCan ability files, that allows you define your permissions with hash of conditions. This means you can check permissions on a single instance of a model, but also retrieve automatically all the instances where you are authorized to perform a certain action.
+
+You can call `can? :read, @book` but also `Book.accessible_by(current_ability, :read)` that will return all the books you can read.
+
+When your controller is executed, it will read only the ability file that you need, saving time and memory.
+
+## Merge ability files
+
+Abilities files can always be merged together, so if you need two of them in one Controller, you can simply:
+
+```ruby
+def current_ability
+  @current_ability ||= ReadAbility.new(current_user).merge(WriteAbility.new(current_user))
+end
+```
diff --git a/docs/sql_strategies.md b/docs/sql_strategies.md
new file mode 100644
index 0000000..5786568
--- /dev/null
+++ b/docs/sql_strategies.md
@@ -0,0 +1,67 @@
+# SQL Strategies
+
+When [fetching records](./fetching_records.md) from the database, CanCanCan generates the SQL for you.
+
+The generated SQL, although correct, might not be performant.
+
+In the history of CanCanCan we had many issues with different versions of the generated SQL and we finally reached to the conclusion that there's no single solutions that fits all the needs.
+
+That's why in the latest versions of CanCanCan, you are given the possibility to customize how the SQL is generated and choose from multiple options.
+
+You can customize the SQL strategy globally with:
+
+```ruby
+# config/initializers/cancancan.rb
+
+CanCan.accessible_by_strategy = :subquery # :left_join is the default
+```
+
+or on a single `accessible_by` call:
+
+```ruby
+Article.accessible_by(current_ability, strategy: :subquery) # :left_join is, again, the default
+```
+
+or on a group of queries:
+
+```ruby
+CanCan.with_accessible_by_strategy(:subquery) do
+  Article.accessible_by(current_ability)
+  # ...
+end
+```
+
+Here is a complete list of the available strategies, explained by examples.
+
+Given the following permissions:
+
+```ruby
+can :read, Article, mentions: { user: { name: u.name } }
+```
+
+## :left_join
+
+Note that in the default strategy, we use the `DISTINCT` clause which might cause performance issues.
+
+```sql
+SELECT DISTINCT "articles".*
+FROM "articles"
+LEFT OUTER JOIN "mentions" ON "mentions"."article_id" = "articles"."id"
+LEFT OUTER JOIN "users" ON "users"."id" = "mentions"."user_id"
+WHERE "users"."name" = 'pippo'
+```
+
+## :subquery
+
+By using the `:subquery` strategy, the `DISTINCT` clause can be removed.
+
+```sql
+SELECT "articles".*
+FROM "articles"
+WHERE "articles"."id" IN
+  (SELECT "articles"."id"
+   FROM "articles"
+   LEFT OUTER JOIN "mentions" ON "mentions"."article_id" = "articles"."id"
+   LEFT OUTER JOIN "users" ON "users"."id" = "legacy_mentions"."user_id"
+   WHERE "users"."name" = 'pippo')
+```
diff --git a/docs/testing.md b/docs/testing.md
new file mode 100644
index 0000000..af1516a
--- /dev/null
+++ b/docs/testing.md
@@ -0,0 +1,83 @@
+# Testing
+
+This is an authorization library. Testing the permissions you defined is not important, **is essential**.
+
+Be really careful when defining your abilities, and be even more careful when testing them.
+
+It can be difficult to thoroughly test user permissions at the functional/integration level because there are often many branching possibilities. Since CanCanCan handles all permission logic in `Ability` classes this makes it easy to have a solid set of unit test for complete coverage.
+
+The `can?` method can be called directly on any `Ability` (like you would in the controller or view) so it is easy to test permission logic.
+
+```ruby
+test "user can only destroy projects which they own" do
+  user = User.create!
+  project = Project.new(user: user)
+  ability = Ability.new(user)
+  assert ability.can?(:destroy, project)
+  assert ability.cannot?(:destroy, Project.new)
+end
+```
+
+## RSpec
+
+If you are testing the `Ability` class through RSpec there is a `be_able_to` matcher available. This checks if the `can?` method returns `true`.
+
+```ruby
+require "cancan/matchers"
+ability = Ability.new(user)
+expect(ability).to be_able_to(:destroy, Project.new(user: user))
+expect(ability).not_to be_able_to(:destroy, Project.new)
+```
+
+Pro way 😉
+
+```ruby
+require "cancan/matchers"
+
+describe "User" do
+  describe "abilities" do
+    subject(:ability) { Ability.new(user) }
+    let(:user) { nil }
+
+    context "when is an account manager" do
+      let(:user) { create(:account_manager) }
+
+      it { is_expected.to be_able_to(:manage, Account.new) }
+    end
+  end
+end
+```
+
+## Cucumber
+
+By default, Cucumber will ignore the `rescue_from` call in the `ApplicationController` and report the `CanCan::AccessDenied` exception when running the features. If you want full integration testing you can change this behavior so the exception is caught by Rails. You can do so by setting this in the `env.rb` file.
+
+```ruby
+# in features/support/env.rb
+ActionController::Base.allow_rescue = true
+```
+
+Alternatively, if you don't want to allow rescue on everything, you can tag individual scenarios with `@allow-rescue` tag.
+
+```ruby
+@allow-rescue
+Scenario: Update Article
+```
+
+Here the `rescue_from` block will take effect only in this scenario.
+
+## Request Testing
+
+If you want to test authorization functionality at the request level, one option is to log-in the user who has the appropriate permissions.
+
+```ruby
+user = User.create!(admin: true)
+article = Article.create!
+login user, as: :user # in devise
+get article_path(article)
+expect(response).to have_http_status(:ok)
+```
+
+If you have very complex permissions it can lead to many branching possibilities. If these are all tested in the request layer then it can lead to slow and bloated tests.
+
+Instead we recommend keeping request authorization tests light and testing the authorization functionality more thoroughly in the Ability model through unit tests as shown at the top.
diff --git a/gemfiles/activerecord_4.2.0.gemfile b/gemfiles/activerecord_4.2.0.gemfile
deleted file mode 100644
index f564b76..0000000
--- a/gemfiles/activerecord_4.2.0.gemfile
+++ /dev/null
@@ -1,21 +0,0 @@
-# This file was generated by Appraisal
-
-source "https://rubygems.org"
-
-gem "activerecord", "~> 4.2.0", require: "active_record"
-gem "activesupport", "~> 4.2.0", require: "active_support/all"
-gem "actionpack", "~> 4.2.0", require: "action_pack"
-gem "nokogiri", "~> 1.6.8", require: "nokogiri"
-
-platforms :jruby do
-  gem "activerecord-jdbcsqlite3-adapter", "~> 1.3.24"
-  gem "jdbc-sqlite3"
-  gem "jdbc-postgres"
-end
-
-platforms :ruby, :mswin, :mingw do
-  gem "sqlite3", "~> 1.3.0"
-  gem "pg", "~> 0.21"
-end
-
-gemspec path: "../"
diff --git a/gemfiles/activerecord_5.0.2.gemfile b/gemfiles/activerecord_5.0.2.gemfile
index bec9ffc..8f0cb56 100644
--- a/gemfiles/activerecord_5.0.2.gemfile
+++ b/gemfiles/activerecord_5.0.2.gemfile
@@ -13,8 +13,8 @@ platforms :jruby do
 end
 
 platforms :ruby, :mswin, :mingw do
+  gem "pg", "~> 1.3.4"
   gem "sqlite3", "~> 1.3.0"
-  gem "pg", "~> 0.21"
 end
 
 gemspec path: "../"
diff --git a/gemfiles/activerecord_5.1.0.gemfile b/gemfiles/activerecord_5.1.0.gemfile
index ff8f083..fe5514f 100644
--- a/gemfiles/activerecord_5.1.0.gemfile
+++ b/gemfiles/activerecord_5.1.0.gemfile
@@ -13,8 +13,8 @@ platforms :jruby do
 end
 
 platforms :ruby, :mswin, :mingw do
-  gem "sqlite3", "~> 1.3.0"
-  gem "pg", "~> 0.21"
+  gem "pg", "~> 1.3.4"
+  gem "sqlite3", "~> 1.4.2"
 end
 
 gemspec path: "../"
diff --git a/gemfiles/activerecord_5.2.2.gemfile b/gemfiles/activerecord_5.2.2.gemfile
index 1389b1b..63ce61b 100644
--- a/gemfiles/activerecord_5.2.2.gemfile
+++ b/gemfiles/activerecord_5.2.2.gemfile
@@ -13,8 +13,8 @@ platforms :jruby do
 end
 
 platforms :ruby, :mswin, :mingw do
-  gem "sqlite3", "~> 1.3.0"
-  gem "pg", "~> 0.21"
+  gem "pg", "~> 1.3.4"
+  gem "sqlite3", "~> 1.4.2"
 end
 
 gemspec path: "../"
diff --git a/gemfiles/activerecord_6.0.0.gemfile b/gemfiles/activerecord_6.0.0.gemfile
index 4132648..e9f11dd 100644
--- a/gemfiles/activerecord_6.0.0.gemfile
+++ b/gemfiles/activerecord_6.0.0.gemfile
@@ -2,9 +2,9 @@
 
 source "https://rubygems.org"
 
-gem "actionpack", "~> 6.0.0.beta3", require: "action_pack"
-gem "activerecord", "~> 6.0.0.beta3", require: "active_record"
-gem "activesupport", "~> 6.0.0.beta3", require: "active_support/all"
+gem "actionpack", "~> 6.0.0", require: "action_pack"
+gem "activerecord", "~> 6.0.0", require: "active_record"
+gem "activesupport", "~> 6.0.0", require: "active_support/all"
 
 platforms :jruby do
   gem "activerecord-jdbcsqlite3-adapter"
@@ -13,8 +13,8 @@ platforms :jruby do
 end
 
 platforms :ruby, :mswin, :mingw do
-  gem "pg", "~> 1.1.4"
-  gem "sqlite3", "~> 1.3.0"
+  gem "pg", "~> 1.3.4"
+  gem "sqlite3", "~> 1.4.2"
 end
 
 gemspec path: "../"
diff --git a/gemfiles/activerecord_6.1.0.gemfile b/gemfiles/activerecord_6.1.0.gemfile
new file mode 100644
index 0000000..88580ff
--- /dev/null
+++ b/gemfiles/activerecord_6.1.0.gemfile
@@ -0,0 +1,20 @@
+# This file was generated by Appraisal
+
+source "https://rubygems.org"
+
+gem "actionpack", "~> 6.1.0", require: "action_pack"
+gem "activerecord", "~> 6.1.0", require: "active_record"
+gem "activesupport", "~> 6.1.0", require: "active_support/all"
+
+platforms :jruby do
+  gem "activerecord-jdbcsqlite3-adapter"
+  gem "jdbc-sqlite3"
+  gem "jdbc-postgres"
+end
+
+platforms :ruby, :mswin, :mingw do
+  gem "pg", "~> 1.3.4"
+  gem "sqlite3", "~> 1.4.2"
+end
+
+gemspec path: "../"
diff --git a/gemfiles/activerecord_7.0.0.gemfile b/gemfiles/activerecord_7.0.0.gemfile
new file mode 100644
index 0000000..1f0c169
--- /dev/null
+++ b/gemfiles/activerecord_7.0.0.gemfile
@@ -0,0 +1,20 @@
+# This file was generated by Appraisal
+
+source "https://rubygems.org"
+
+gem "actionpack", "~> 7.0.0", require: "action_pack"
+gem "activerecord", "~> 7.0.0", require: "active_record"
+gem "activesupport", "~> 7.0.0", require: "active_support/all"
+
+platforms :jruby do
+  gem "activerecord-jdbcsqlite3-adapter"
+  gem "jdbc-sqlite3"
+  gem "jdbc-postgres"
+end
+
+platforms :ruby, :mswin, :mingw do
+  gem "pg", "~> 1.3.4"
+  gem "sqlite3", "~> 1.4.2"
+end
+
+gemspec path: "../"
diff --git a/gemfiles/activerecord_main.gemfile b/gemfiles/activerecord_main.gemfile
new file mode 100644
index 0000000..ba43821
--- /dev/null
+++ b/gemfiles/activerecord_main.gemfile
@@ -0,0 +1,22 @@
+# This file was generated by Appraisal
+
+source "https://rubygems.org"
+
+git "https://github.com/rails/rails", branch: "main" do
+  gem "actionpack", require: "action_pack"
+  gem "activerecord", require: "active_record"
+  gem "activesupport", require: "active_support/all"
+end
+
+platforms :jruby do
+  gem "activerecord-jdbcsqlite3-adapter"
+  gem "jdbc-sqlite3"
+  gem "jdbc-postgres"
+end
+
+platforms :ruby, :mswin, :mingw do
+  gem "pg", "~> 1.3.4"
+  gem "sqlite3", "~> 1.4.2"
+end
+
+gemspec path: "../"
diff --git a/lib/cancan.rb b/lib/cancan.rb
index 9461754..c4f92c9 100644
--- a/lib/cancan.rb
+++ b/lib/cancan.rb
@@ -1,6 +1,7 @@
 # frozen_string_literal: true
 
 require 'cancan/version'
+require 'cancan/config'
 require 'cancan/parameter_validators'
 require 'cancan/ability'
 require 'cancan/rule'
@@ -16,7 +17,13 @@ require 'cancan/rules_compressor'
 if defined? ActiveRecord
   require 'cancan/model_adapters/conditions_extractor'
   require 'cancan/model_adapters/conditions_normalizer'
+  require 'cancan/model_adapters/sti_normalizer'
   require 'cancan/model_adapters/active_record_adapter'
   require 'cancan/model_adapters/active_record_4_adapter'
   require 'cancan/model_adapters/active_record_5_adapter'
+  require 'cancan/model_adapters/strategies/base'
+  require 'cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery'
+  require 'cancan/model_adapters/strategies/joined_alias_exists_subquery'
+  require 'cancan/model_adapters/strategies/left_join'
+  require 'cancan/model_adapters/strategies/subquery'
 end
diff --git a/lib/cancan/ability.rb b/lib/cancan/ability.rb
index 8b40c99..679ffc9 100644
--- a/lib/cancan/ability.rb
+++ b/lib/cancan/ability.rb
@@ -302,7 +302,11 @@ module CanCan
 
     def alternative_subjects(subject)
       subject = subject.class unless subject.is_a?(Module)
-      [:all, *subject.ancestors, subject.class.to_s]
+      if subject.respond_to?(:subclasses) && defined?(ActiveRecord::Base) && subject < ActiveRecord::Base
+        [:all, *(subject.ancestors + subject.subclasses), subject.class.to_s]
+      else
+        [:all, *subject.ancestors, subject.class.to_s]
+      end
     end
   end
 end
diff --git a/lib/cancan/ability/rules.rb b/lib/cancan/ability/rules.rb
index 0d1297b..8bee574 100644
--- a/lib/cancan/ability/rules.rb
+++ b/lib/cancan/ability/rules.rb
@@ -19,12 +19,13 @@ module CanCan
       end
 
       def add_rule_to_index(rule, position)
-        @rules_index ||= Hash.new { |h, k| h[k] = [] }
+        @rules_index ||= {}
 
         subjects = rule.subjects.compact
         subjects << :all if subjects.empty?
 
         subjects.each do |subject|
+          @rules_index[subject] ||= []
           @rules_index[subject] << position
         end
       end
@@ -48,7 +49,9 @@ module CanCan
           rules
         else
           positions = @rules_index.values_at(subject, *alternative_subjects(subject))
-          positions.flatten!.sort!
+          positions.compact!
+          positions.flatten!
+          positions.sort!
           positions.map { |i| @rules[i] }
         end
       end
@@ -58,8 +61,8 @@ module CanCan
           next unless rule.only_raw_sql?
 
           raise Error,
-                "The can? and cannot? call cannot be used with a raw sql 'can' definition."\
-                " The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
+                "The can? and cannot? call cannot be used with a raw sql 'can' definition. " \
+                "The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
         end
       end
 
@@ -69,7 +72,7 @@ module CanCan
           rule.base_behavior == false && rule.attributes.present?
         end
         if rules.any?(&:only_block?)
-          raise Error, "The accessible_by call cannot be used with a block 'can' definition."\
+          raise Error, "The accessible_by call cannot be used with a block 'can' definition." \
             "The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
         end
         rules
diff --git a/lib/cancan/class_matcher.rb b/lib/cancan/class_matcher.rb
new file mode 100644
index 0000000..65efcca
--- /dev/null
+++ b/lib/cancan/class_matcher.rb
@@ -0,0 +1,30 @@
+require_relative 'sti_detector'
+
+# This class is responsible for matching classes and their subclasses as well as
+# upmatching classes to their ancestors.
+# This is used to generate sti connections
+class SubjectClassMatcher
+  def self.matches_subject_class?(subjects, subject)
+    subjects.any? do |sub|
+      has_subclasses = subject.respond_to?(:subclasses)
+      matching_class_check(subject, sub, has_subclasses)
+    end
+  end
+
+  def self.matching_class_check(subject, sub, has_subclasses)
+    matches = matches_class_or_is_related(subject, sub)
+    if has_subclasses
+      return matches unless StiDetector.sti_class?(sub)
+
+      matches || subject.subclasses.include?(sub)
+    else
+      matches
+    end
+  end
+
+  def self.matches_class_or_is_related(subject, sub)
+    sub.is_a?(Module) && (subject.is_a?(sub) ||
+        subject.class.to_s == sub.to_s ||
+        (subject.is_a?(Module) && subject.ancestors.include?(sub)))
+  end
+end
diff --git a/lib/cancan/conditions_matcher.rb b/lib/cancan/conditions_matcher.rb
index c8611ae..c1a97b9 100644
--- a/lib/cancan/conditions_matcher.rb
+++ b/lib/cancan/conditions_matcher.rb
@@ -18,10 +18,14 @@ module CanCan
       [Class, Module].include? klass
     end
 
-    def matches_block_conditions(subject, *extra_args)
+    def matches_block_conditions(subject, attribute, *extra_args)
       return @base_behavior if subject_class?(subject)
 
-      @block.call(subject, *extra_args.compact)
+      if attribute
+        @block.call(subject, attribute, *extra_args)
+      else
+        @block.call(subject, *extra_args)
+      end
     end
 
     def matches_non_block_conditions(subject)
@@ -33,16 +37,26 @@ module CanCan
     end
 
     def nested_subject_matches_conditions?(subject_hash)
-      parent, _child = subject_hash.first
-      matches_conditions_hash?(parent, @conditions[parent.class.name.downcase.to_sym] || {})
+      parent, child = subject_hash.first
+
+      adapter = model_adapter(parent)
+
+      parent_condition_name = adapter.parent_condition_name(parent, child)
+
+      matches_base_parent_conditions = matches_conditions_hash?(parent,
+                                                                @conditions[parent_condition_name] || {})
+
+      matches_base_parent_conditions &&
+        (!adapter.override_nested_subject_conditions_matching?(parent, child, @conditions) ||
+          adapter.nested_subject_matches_conditions?(parent, child, @conditions))
     end
 
     # Checks if the given subject matches the given conditions hash.
-    # This behavior can be overriden by a model adapter by defining two class methods:
+    # This behavior can be overridden by a model adapter by defining two class methods:
     # override_matching_for_conditions?(subject, conditions) and
     # matches_conditions_hash?(subject, conditions)
     def matches_conditions_hash?(subject, conditions = @conditions)
-      return true if conditions.empty?
+      return true if conditions.is_a?(Hash) && conditions.empty?
 
       adapter = model_adapter(subject)
 
@@ -50,10 +64,20 @@ module CanCan
         return adapter.matches_conditions_hash?(subject, conditions)
       end
 
-      matches_all_conditions?(adapter, conditions, subject)
+      matches_all_conditions?(adapter, subject, conditions)
     end
 
-    def matches_all_conditions?(adapter, conditions, subject)
+    def matches_all_conditions?(adapter, subject, conditions)
+      if conditions.is_a?(Hash)
+        matches_hash_conditions?(adapter, subject, conditions)
+      elsif conditions.respond_to?(:include?)
+        conditions.include?(subject)
+      else
+        subject == conditions
+      end
+    end
+
+    def matches_hash_conditions?(adapter, subject, conditions)
       conditions.all? do |name, value|
         if adapter.override_condition_matching?(subject, name, value)
           adapter.matches_condition?(subject, name, value)
@@ -78,12 +102,29 @@ module CanCan
 
     def hash_condition_match?(attribute, value)
       if attribute.is_a?(Array) || (defined?(ActiveRecord) && attribute.is_a?(ActiveRecord::Relation))
-        attribute.any? { |element| matches_conditions_hash?(element, value) }
+        array_like_matches_condition_hash?(attribute, value)
       else
         attribute && matches_conditions_hash?(attribute, value)
       end
     end
 
+    def array_like_matches_condition_hash?(attribute, value)
+      if attribute.any?
+        attribute.any? { |element| matches_conditions_hash?(element, value) }
+      else
+        # you can use `nil`s in your ability definition to tell cancancan to find
+        # objects that *don't* have any children in a has_many relationship.
+        #
+        # for example, given ability:
+        # => can :read, Article, comments: { id: nil }
+        # cancancan will return articles where `article.comments == []`
+        #
+        # this is implemented here. `attribute` is `article.comments`, and it's an empty array.
+        # the expression below returns true if this was expected.
+        !value.values.empty? && value.values.all?(&:nil?)
+      end
+    end
+
     def call_block_with_all(action, subject, *extra_args)
       if subject.class == Class
         @block.call(action, subject, nil, *extra_args)
@@ -97,7 +138,10 @@ module CanCan
     end
 
     def conditions_empty?
-      @conditions == {} || @conditions.nil?
+      # @conditions might be an ActiveRecord::Associations::CollectionProxy
+      # which it's `==` implementation will fetch all records for comparison
+
+      (@conditions.is_a?(Hash) && @conditions == {}) || @conditions.nil?
     end
   end
 end
diff --git a/lib/cancan/config.rb b/lib/cancan/config.rb
new file mode 100644
index 0000000..0fd8893
--- /dev/null
+++ b/lib/cancan/config.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+module CanCan
+  def self.valid_accessible_by_strategies
+    strategies = [:left_join]
+
+    unless does_not_support_subquery_strategy?
+      strategies.push(:joined_alias_exists_subquery, :joined_alias_each_rule_as_exists_subquery, :subquery)
+    end
+
+    strategies
+  end
+
+  # You can disable the rules compressor if it's causing unexpected issues.
+  def self.rules_compressor_enabled
+    return @rules_compressor_enabled if defined?(@rules_compressor_enabled)
+
+    @rules_compressor_enabled = true
+  end
+
+  def self.rules_compressor_enabled=(value)
+    @rules_compressor_enabled = value
+  end
+
+  def self.with_rules_compressor_enabled(value)
+    return yield if value == rules_compressor_enabled
+
+    begin
+      rules_compressor_enabled_was = rules_compressor_enabled
+      @rules_compressor_enabled = value
+      yield
+    ensure
+      @rules_compressor_enabled = rules_compressor_enabled_was
+    end
+  end
+
+  # Determines how CanCan should build queries when calling accessible_by,
+  # if the query will contain a join. The default strategy is `:subquery`.
+  #
+  #   # config/initializers/cancan.rb
+  #   CanCan.accessible_by_strategy = :subquery
+  #
+  # Valid strategies are:
+  # - :subquery - Creates a nested query with all joins, wrapped by a
+  #               WHERE IN query.
+  # - :left_join - Calls the joins directly using `left_joins`, and
+  #                ensures records are unique using `distinct`. Note that
+  #                `distinct` is not reliable in some cases. See
+  #                https://github.com/CanCanCommunity/cancancan/pull/605
+  def self.accessible_by_strategy
+    return @accessible_by_strategy if @accessible_by_strategy
+
+    @accessible_by_strategy = default_accessible_by_strategy
+  end
+
+  def self.default_accessible_by_strategy
+    if does_not_support_subquery_strategy?
+      # see https://github.com/CanCanCommunity/cancancan/pull/655 for where this was added
+      # the `subquery` strategy (from https://github.com/CanCanCommunity/cancancan/pull/619
+      # only works in Rails 5 and higher
+      :left_join
+    else
+      :subquery
+    end
+  end
+
+  def self.accessible_by_strategy=(value)
+    validate_accessible_by_strategy!(value)
+
+    if value == :subquery && does_not_support_subquery_strategy?
+      raise ArgumentError, 'accessible_by_strategy = :subquery requires ActiveRecord 5 or newer'
+    end
+
+    @accessible_by_strategy = value
+  end
+
+  def self.with_accessible_by_strategy(value)
+    return yield if value == accessible_by_strategy
+
+    validate_accessible_by_strategy!(value)
+
+    begin
+      strategy_was = accessible_by_strategy
+      @accessible_by_strategy = value
+      yield
+    ensure
+      @accessible_by_strategy = strategy_was
+    end
+  end
+
+  def self.validate_accessible_by_strategy!(value)
+    return if valid_accessible_by_strategies.include?(value)
+
+    raise ArgumentError, "accessible_by_strategy must be one of #{valid_accessible_by_strategies.join(', ')}"
+  end
+
+  def self.does_not_support_subquery_strategy?
+    !defined?(CanCan::ModelAdapters::ActiveRecordAdapter) ||
+      CanCan::ModelAdapters::ActiveRecordAdapter.version_lower?('5.0.0')
+  end
+end
diff --git a/lib/cancan/controller_additions.rb b/lib/cancan/controller_additions.rb
index 68f949d..0c84f83 100644
--- a/lib/cancan/controller_additions.rb
+++ b/lib/cancan/controller_additions.rb
@@ -264,7 +264,7 @@ module CanCan
           next if options[:unless] && controller.send(options[:unless])
 
           raise AuthorizationNotPerformed,
-                'This action failed the check_authorization because it does not authorize_resource. '\
+                'This action failed the check_authorization because it does not authorize_resource. ' \
                 'Add skip_authorization_check to bypass this check.'
         end
 
diff --git a/lib/cancan/controller_resource.rb b/lib/cancan/controller_resource.rb
index d9f753a..c99fd2d 100644
--- a/lib/cancan/controller_resource.rb
+++ b/lib/cancan/controller_resource.rb
@@ -54,7 +54,7 @@ module CanCan
 
     protected
 
-    # Returns the class used for this resource. This can be overriden by the :class option.
+    # Returns the class used for this resource. This can be overridden by the :class option.
     # If +false+ is passed in it will use the resource name as a symbol in which case it should
     # only be used for authorization, not loading since there's no class to load through.
     def resource_class
diff --git a/lib/cancan/exceptions.rb b/lib/cancan/exceptions.rb
index d66c5eb..11417ba 100644
--- a/lib/cancan/exceptions.rb
+++ b/lib/cancan/exceptions.rb
@@ -58,5 +58,13 @@ module CanCan
     def to_s
       @message || @default_message
     end
+
+    def inspect
+      details = %i[action subject conditions message].map do |attribute|
+        value = instance_variable_get "@#{attribute}"
+        "#{attribute}: #{value.inspect}" if value.present?
+      end.compact.join(', ')
+      "#<#{self.class.name} #{details}>"
+    end
   end
 end
diff --git a/lib/cancan/matchers.rb b/lib/cancan/matchers.rb
index 7e5b206..ccc8e0b 100644
--- a/lib/cancan/matchers.rb
+++ b/lib/cancan/matchers.rb
@@ -13,9 +13,11 @@ Kernel.const_get(rspec_module)::Matchers.define :be_able_to do |*args|
   match do |ability|
     actions = args.first
     if actions.is_a? Array
-      break false if actions.empty?
-
-      actions.all? { |action| ability.can?(action, *args[1..-1]) }
+      if actions.empty?
+        false
+      else
+        actions.all? { |action| ability.can?(action, *args[1..-1]) }
+      end
     else
       ability.can?(*args)
     end
diff --git a/lib/cancan/model_adapters/abstract_adapter.rb b/lib/cancan/model_adapters/abstract_adapter.rb
index 06cfd6c..d934b59 100644
--- a/lib/cancan/model_adapters/abstract_adapter.rb
+++ b/lib/cancan/model_adapters/abstract_adapter.rb
@@ -3,9 +3,11 @@
 module CanCan
   module ModelAdapters
     class AbstractAdapter
+      attr_reader :model_class
+
       def self.inherited(subclass)
         @subclasses ||= []
-        @subclasses << subclass
+        @subclasses.insert(0, subclass)
       end
 
       def self.adapter_class(model_class)
@@ -33,6 +35,23 @@ module CanCan
         raise NotImplemented, 'This model adapter does not support matching on a conditions hash.'
       end
 
+      # Override if parent condition could be under a different key in conditions
+      def self.parent_condition_name(parent, _child)
+        parent.class.name.downcase.to_sym
+      end
+
+      # Used above override_conditions_hash_matching to determine if this model adapter will override the
+      # matching behavior for nested subject.
+      # If this returns true then nested_subject_matches_conditions? will be called.
+      def self.override_nested_subject_conditions_matching?(_parent, _child, _all_conditions)
+        false
+      end
+
+      # Override if override_nested_subject_conditions_matching? returns true
+      def self.nested_subject_matches_conditions?(_parent, _child, _all_conditions)
+        raise NotImplemented, 'This model adapter does not support matching on a nested subject.'
+      end
+
       # Used to determine if this model adapter will override the matching behavior for a specific condition.
       # If this returns true then matches_condition? will be called. See Rule#matches_conditions_hash
       def self.override_condition_matching?(_subject, _name, _value)
diff --git a/lib/cancan/model_adapters/active_record_4_adapter.rb b/lib/cancan/model_adapters/active_record_4_adapter.rb
index 4755240..b2e9185 100644
--- a/lib/cancan/model_adapters/active_record_4_adapter.rb
+++ b/lib/cancan/model_adapters/active_record_4_adapter.rb
@@ -34,10 +34,8 @@ module CanCan
       # look inside the where clause to decide to outer join tables
       # you're using in the where. Instead, `references()` is required
       # in addition to `includes()` to force the outer join.
-      def build_relation(*where_conditions)
-        relation = @model_class.where(*where_conditions)
-        relation = relation.includes(joins).references(joins) if joins.present?
-        relation
+      def build_joins_relation(relation, *_where_conditions)
+        relation.includes(joins).references(joins)
       end
 
       # Rails 4.2 deprecates `sanitize_sql_hash_for_conditions`
diff --git a/lib/cancan/model_adapters/active_record_5_adapter.rb b/lib/cancan/model_adapters/active_record_5_adapter.rb
index cac1ac4..68f1142 100644
--- a/lib/cancan/model_adapters/active_record_5_adapter.rb
+++ b/lib/cancan/model_adapters/active_record_5_adapter.rb
@@ -21,13 +21,15 @@ module CanCan
 
       private
 
-      def build_relation(*where_conditions)
-        relation = @model_class.where(*where_conditions)
-        relation = relation.left_joins(joins).distinct if joins.present?
-        relation
+      def build_joins_relation(relation, *where_conditions)
+        strategy_class.new(adapter: self, relation: relation, where_conditions: where_conditions).execute!
+      end
+
+      def strategy_class
+        strategy_class_name = CanCan.accessible_by_strategy.to_s.camelize
+        CanCan::ModelAdapters::Strategies.const_get(strategy_class_name)
       end
 
-      # Rails 4.2 deprecates `sanitize_sql_hash_for_conditions`
       def sanitize_sql(conditions)
         if conditions.is_a?(Hash)
           sanitize_sql_activerecord5(conditions)
@@ -41,11 +43,7 @@ module CanCan
         table_metadata = ActiveRecord::TableMetadata.new(@model_class, table)
         predicate_builder = ActiveRecord::PredicateBuilder.new(table_metadata)
 
-        conditions = predicate_builder.resolve_column_aliases(conditions)
-
-        conditions.stringify_keys!
-
-        predicate_builder.build_from_hash(conditions).map { |b| visit_nodes(b) }.join(' AND ')
+        predicate_builder.build_from_hash(conditions.stringify_keys).map { |b| visit_nodes(b) }.join(' AND ')
       end
 
       def visit_nodes(node)
diff --git a/lib/cancan/model_adapters/active_record_adapter.rb b/lib/cancan/model_adapters/active_record_adapter.rb
index 41c1057..6d17f86 100644
--- a/lib/cancan/model_adapters/active_record_adapter.rb
+++ b/lib/cancan/model_adapters/active_record_adapter.rb
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-require_relative 'conditions_extractor.rb'
-require 'cancan/rules_compressor'
 module CanCan
   module ModelAdapters
     class ActiveRecordAdapter < AbstractAdapter
@@ -13,12 +11,86 @@ module CanCan
         Gem::Version.new(ActiveRecord.version).release < Gem::Version.new(version)
       end
 
+      attr_reader :compressed_rules
+
       def initialize(model_class, rules)
         super
-        @compressed_rules = RulesCompressor.new(@rules.reverse).rules_collapsed.reverse
+        @compressed_rules = if CanCan.rules_compressor_enabled
+                              RulesCompressor.new(@rules.reverse).rules_collapsed.reverse
+                            else
+                              @rules
+                            end
+        StiNormalizer.normalize(@compressed_rules)
         ConditionsNormalizer.normalize(model_class, @compressed_rules)
       end
 
+      class << self
+        # When belongs_to parent_id is a condition for a model,
+        # we want to check the parent when testing ability for a hash {parent => model}
+        def override_nested_subject_conditions_matching?(parent, child, all_conditions)
+          parent_child_conditions(parent, child, all_conditions).present?
+        end
+
+        # parent_id condition can be an array of integer or one integer, we check the parent against this
+        def nested_subject_matches_conditions?(parent, child, all_conditions)
+          id_condition = parent_child_conditions(parent, child, all_conditions)
+          return id_condition.include?(parent.id) if id_condition.is_a? Array
+          return id_condition == parent.id if id_condition.is_a? Integer
+
+          false
+        end
+
+        def parent_child_conditions(parent, child, all_conditions)
+          child_class = child.is_a?(Class) ? child : child.class
+          parent_class = parent.is_a?(Class) ? parent : parent.class
+
+          foreign_key = child_class.reflect_on_all_associations(:belongs_to).find do |association|
+            # Do not match on polymorphic associations or it will throw an error (klass cannot be determined)
+            !association.polymorphic? && association.klass == parent.class
+          end&.foreign_key&.to_sym
+
+          # Search again in case of polymorphic associations, this time matching on the :has_many side
+          # via the :as option, as well as klass
+          foreign_key ||= parent_class.reflect_on_all_associations(:has_many).find do |has_many_assoc|
+            !matching_parent_child_polymorphic_association(has_many_assoc, child_class).nil?
+          end&.foreign_key&.to_sym
+
+          foreign_key.nil? ? nil : all_conditions[foreign_key]
+        end
+
+        def matching_parent_child_polymorphic_association(parent_assoc, child_class)
+          return nil unless parent_assoc.klass == child_class
+          return nil if parent_assoc&.options[:as].nil?
+  
+          child_class.reflect_on_all_associations(:belongs_to).find do |child_assoc|
+            # Only match this way for polymorphic associations
+            child_assoc.polymorphic? && child_assoc.name == parent_assoc.options[:as]
+          end
+        end
+
+        def child_association_to_parent(parent, child)
+          child_class = child.is_a?(Class) ? child : child.class
+          parent_class = parent.is_a?(Class) ? parent : parent.class
+
+          association = child_class.reflect_on_all_associations(:belongs_to).find do |association|
+            # Do not match on polymorphic associations or it will throw an error (klass cannot be determined)
+            !association.polymorphic? && association.klass == parent.class
+          end
+
+          return association unless association.nil?
+
+          parent_class.reflect_on_all_associations(:has_many).each do |has_many_assoc|
+            association ||= matching_parent_child_polymorphic_association(has_many_assoc, child_class)
+          end
+
+          association
+        end
+
+        def parent_condition_name(parent, child)
+          child_association_to_parent(parent, child)&.name || parent.class.name.downcase.to_sym
+        end
+      end
+
       # Returns conditions intended to be used inside a database query. Normally you will not call this
       # method directly, but instead go through ModelAdditions#accessible_by.
       #
@@ -60,6 +132,14 @@ module CanCan
         end
       end
 
+      def build_relation(*where_conditions)
+        relation = @model_class.where(*where_conditions)
+        return relation unless joins.present?
+
+        # subclasses must implement `build_joins_relation`
+        build_joins_relation(relation, *where_conditions)
+      end
+
       # Returns the associations used in conditions for the :joins option of a search.
       # See ModelAdditions#accessible_by
       def joins
@@ -99,7 +179,7 @@ module CanCan
       def raise_override_scope_error
         rule_found = @compressed_rules.detect { |rule| rule.conditions.is_a?(ActiveRecord::Relation) }
         raise Error,
-              'Unable to merge an Active Record scope with other conditions. '\
+              'Unable to merge an Active Record scope with other conditions. ' \
               "Instead use a hash or SQL for #{rule_found.actions.first} #{rule_found.subjects.first} ability."
       end
 
diff --git a/lib/cancan/model_adapters/conditions_extractor.rb b/lib/cancan/model_adapters/conditions_extractor.rb
index 47d66b5..05b418c 100644
--- a/lib/cancan/model_adapters/conditions_extractor.rb
+++ b/lib/cancan/model_adapters/conditions_extractor.rb
@@ -3,7 +3,7 @@
 # this class is responsible of converting the hash of conditions
 # in "where conditions" to generate the sql query
 # it consists of a names_cache that helps calculating the next name given to the association
-# it tries to reflect the bahavior of ActiveRecord when generating aliases for tables.
+# it tries to reflect the behavior of ActiveRecord when generating aliases for tables.
 module CanCan
   module ModelAdapters
     class ConditionsExtractor
@@ -50,18 +50,18 @@ module CanCan
       def generate_table_alias(model_class, relation_name, path_to_key)
         table_alias = model_class.reflect_on_association(relation_name).table_name.to_sym
 
-        if alredy_used?(table_alias, relation_name, path_to_key)
+        if already_used?(table_alias, relation_name, path_to_key)
           table_alias = "#{relation_name.to_s.pluralize}_#{model_class.table_name}".to_sym
 
           index = 1
-          while alredy_used?(table_alias, relation_name, path_to_key)
+          while already_used?(table_alias, relation_name, path_to_key)
             table_alias = "#{table_alias}_#{index += 1}".to_sym
           end
         end
         add_to_cache(table_alias, relation_name, path_to_key)
       end
 
-      def alredy_used?(table_alias, relation_name, path_to_key)
+      def already_used?(table_alias, relation_name, path_to_key)
         @names_cache[table_alias].try(:exclude?, "#{path_to_key}_#{relation_name}")
       end
 
diff --git a/lib/cancan/model_adapters/conditions_normalizer.rb b/lib/cancan/model_adapters/conditions_normalizer.rb
index 46ceab7..2b12a80 100644
--- a/lib/cancan/model_adapters/conditions_normalizer.rb
+++ b/lib/cancan/model_adapters/conditions_normalizer.rb
@@ -1,6 +1,6 @@
 # this class is responsible of normalizing the hash of conditions
 # by exploding has_many through associations
-# when a condition is defined with an has_many thorugh association this is exploded in all its parts
+# when a condition is defined with an has_many through association this is exploded in all its parts
 # TODO: it could identify STI and normalize it
 module CanCan
   module ModelAdapters
@@ -31,7 +31,7 @@ module CanCan
             raise WrongAssociationName, "Association '#{key}' not defined in model '#{model_class.name}'"
           end
 
-          if reflection.options[:through].present?
+          if normalizable_association? reflection
             key = reflection.options[:through]
             value = { reflection.source_reflection_name => value }
             reflection = model_class.reflect_on_association(key)
@@ -39,6 +39,10 @@ module CanCan
 
           { key => normalize_conditions(reflection.klass.name.constantize, value) }
         end
+
+        def normalizable_association?(reflection)
+          reflection.options[:through].present? && !reflection.options[:source_type].present?
+        end
       end
     end
   end
diff --git a/lib/cancan/model_adapters/sti_normalizer.rb b/lib/cancan/model_adapters/sti_normalizer.rb
new file mode 100644
index 0000000..e6cb314
--- /dev/null
+++ b/lib/cancan/model_adapters/sti_normalizer.rb
@@ -0,0 +1,47 @@
+require_relative '../sti_detector'
+
+# this class is responsible for detecting sti classes and creating new rules for the
+# relevant subclasses, using the inheritance_column as a merger
+module CanCan
+  module ModelAdapters
+    class StiNormalizer
+      class << self
+        def normalize(rules)
+          rules_cache = []
+          return unless defined?(ActiveRecord::Base)
+
+          rules.delete_if do |rule|
+            subjects = rule.subjects.select do |subject|
+              update_rule(subject, rule, rules_cache)
+            end
+            subjects.length == rule.subjects.length
+          end
+          rules_cache.each { |rule| rules.push(rule) }
+        end
+
+        private
+
+        def update_rule(subject, rule, rules_cache)
+          return false unless StiDetector.sti_class?(subject)
+
+          rules_cache.push(build_rule_for_subclass(rule, subject))
+          true
+        end
+
+        # create a new rule for the subclasses that links on the inheritance_column
+        def build_rule_for_subclass(rule, subject)
+          sti_conditions = { subject.inheritance_column => subject.sti_name }
+          new_rule_conditions =
+            if rule.with_scope?
+              rule.conditions.where(sti_conditions)
+            else
+              rule.conditions.merge(sti_conditions)
+            end
+
+          CanCan::Rule.new(rule.base_behavior, rule.actions, subject.superclass,
+                           new_rule_conditions, rule.block)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/cancan/model_adapters/strategies/base.rb b/lib/cancan/model_adapters/strategies/base.rb
new file mode 100644
index 0000000..005b3db
--- /dev/null
+++ b/lib/cancan/model_adapters/strategies/base.rb
@@ -0,0 +1,40 @@
+module CanCan
+  module ModelAdapters
+    class Strategies
+      class Base
+        attr_reader :adapter, :relation, :where_conditions
+
+        delegate(
+          :compressed_rules,
+          :extract_multiple_conditions,
+          :joins,
+          :model_class,
+          :quoted_primary_key,
+          :quoted_aliased_table_name,
+          :quoted_table_name,
+          to: :adapter
+        )
+        delegate :connection, :quoted_primary_key, to: :model_class
+        delegate :quote_table_name, to: :connection
+
+        def initialize(adapter:, relation:, where_conditions:)
+          @adapter = adapter
+          @relation = relation
+          @where_conditions = where_conditions
+        end
+
+        def aliased_table_name
+          @aliased_table_name ||= "#{model_class.table_name}_alias"
+        end
+
+        def quoted_aliased_table_name
+          @quoted_aliased_table_name ||= quote_table_name(aliased_table_name)
+        end
+
+        def quoted_table_name
+          @quoted_table_name ||= quote_table_name(model_class.table_name)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery.rb b/lib/cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery.rb
new file mode 100644
index 0000000..b27721a
--- /dev/null
+++ b/lib/cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: false
+
+module CanCan
+  module ModelAdapters
+    class Strategies
+      class JoinedAliasEachRuleAsExistsSubquery < Base
+        def execute!
+          model_class
+            .joins(
+              "JOIN #{quoted_table_name} AS #{quoted_aliased_table_name} ON " \
+              "#{quoted_aliased_table_name}.#{quoted_primary_key} = #{quoted_table_name}.#{quoted_primary_key}"
+            )
+            .where(double_exists_sql)
+        end
+
+        def double_exists_sql
+          double_exists_sql = ''
+
+          compressed_rules.each_with_index do |rule, index|
+            double_exists_sql << ' OR ' if index.positive?
+            double_exists_sql << "EXISTS (#{sub_query_for_rule(rule).to_sql})"
+          end
+
+          double_exists_sql
+        end
+
+        def sub_query_for_rule(rule)
+          conditions_extractor = ConditionsExtractor.new(model_class)
+          rule_where_conditions = extract_multiple_conditions(conditions_extractor, [rule])
+          joins_hash, left_joins_hash = extract_joins_from_rule(rule)
+          sub_query_for_rules_and_join_hashes(rule_where_conditions, joins_hash, left_joins_hash)
+        end
+
+        def sub_query_for_rules_and_join_hashes(rule_where_conditions, joins_hash, left_joins_hash)
+          model_class
+            .select('1')
+            .joins(joins_hash)
+            .left_joins(left_joins_hash)
+            .where(
+              "#{quoted_table_name}.#{quoted_primary_key} = " \
+              "#{quoted_aliased_table_name}.#{quoted_primary_key}"
+            )
+            .where(rule_where_conditions)
+            .limit(1)
+        end
+
+        def extract_joins_from_rule(rule)
+          joins = {}
+          left_joins = {}
+
+          extra_joins_recursive([], rule.conditions, joins, left_joins)
+          [joins, left_joins]
+        end
+
+        def extra_joins_recursive(current_path, conditions, joins, left_joins)
+          conditions.each do |key, value|
+            if value.is_a?(Hash)
+              current_path << key
+              extra_joins_recursive(current_path, value, joins, left_joins)
+              current_path.pop
+            else
+              extra_joins_recursive_merge_joins(current_path, value, joins, left_joins)
+            end
+          end
+        end
+
+        def extra_joins_recursive_merge_joins(current_path, value, joins, left_joins)
+          hash_joins = current_path_to_hash(current_path)
+
+          if value.nil?
+            left_joins.deep_merge!(hash_joins)
+          else
+            joins.deep_merge!(hash_joins)
+          end
+        end
+
+        # Converts an array like [:child, :grand_child] into a hash like {child: {grand_child: {}}
+        def current_path_to_hash(current_path)
+          hash_joins = {}
+          current_hash_joins = hash_joins
+
+          current_path.each do |path_part|
+            new_hash = {}
+            current_hash_joins[path_part] = new_hash
+            current_hash_joins = new_hash
+          end
+
+          hash_joins
+        end
+      end
+    end
+  end
+end
diff --git a/lib/cancan/model_adapters/strategies/joined_alias_exists_subquery.rb b/lib/cancan/model_adapters/strategies/joined_alias_exists_subquery.rb
new file mode 100644
index 0000000..a95592a
--- /dev/null
+++ b/lib/cancan/model_adapters/strategies/joined_alias_exists_subquery.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module CanCan
+  module ModelAdapters
+    class Strategies
+      class JoinedAliasExistsSubquery < Base
+        def execute!
+          model_class
+            .joins(
+              "JOIN #{quoted_table_name} AS #{quoted_aliased_table_name} ON " \
+              "#{quoted_aliased_table_name}.#{quoted_primary_key} = #{quoted_table_name}.#{quoted_primary_key}"
+            )
+            .where("EXISTS (#{joined_alias_exists_subquery_inner_query.to_sql})")
+        end
+
+        def joined_alias_exists_subquery_inner_query
+          model_class
+            .unscoped
+            .select('1')
+            .left_joins(joins)
+            .where(*where_conditions)
+            .where(
+              "#{quoted_table_name}.#{quoted_primary_key} = " \
+              "#{quoted_aliased_table_name}.#{quoted_primary_key}"
+            )
+            .limit(1)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/cancan/model_adapters/strategies/left_join.rb b/lib/cancan/model_adapters/strategies/left_join.rb
new file mode 100644
index 0000000..3c11896
--- /dev/null
+++ b/lib/cancan/model_adapters/strategies/left_join.rb
@@ -0,0 +1,11 @@
+module CanCan
+  module ModelAdapters
+    class Strategies
+      class LeftJoin < Base
+        def execute!
+          relation.left_joins(joins).distinct
+        end
+      end
+    end
+  end
+end
diff --git a/lib/cancan/model_adapters/strategies/subquery.rb b/lib/cancan/model_adapters/strategies/subquery.rb
new file mode 100644
index 0000000..cc7019a
--- /dev/null
+++ b/lib/cancan/model_adapters/strategies/subquery.rb
@@ -0,0 +1,18 @@
+module CanCan
+  module ModelAdapters
+    class Strategies
+      class Subquery < Base
+        def execute!
+          build_joins_relation_subquery(where_conditions)
+        end
+
+        def build_joins_relation_subquery(where_conditions)
+          inner = model_class.unscoped do
+            model_class.left_joins(joins).where(*where_conditions)
+          end
+          model_class.where(model_class.primary_key => inner)
+        end
+      end
+    end
+  end
+end
diff --git a/lib/cancan/model_additions.rb b/lib/cancan/model_additions.rb
index 9673213..ef2d26e 100644
--- a/lib/cancan/model_additions.rb
+++ b/lib/cancan/model_additions.rb
@@ -20,8 +20,10 @@ module CanCan
       #   @articles = Article.accessible_by(current_ability, :update)
       #
       # Here only the articles which the user can update are returned.
-      def accessible_by(ability, action = :index)
-        ability.model_adapter(self, action).database_records
+      def accessible_by(ability, action = :index, strategy: CanCan.accessible_by_strategy)
+        CanCan.with_accessible_by_strategy(strategy) do
+          ability.model_adapter(self, action).database_records
+        end
       end
     end
 
diff --git a/lib/cancan/relevant.rb b/lib/cancan/relevant.rb
new file mode 100644
index 0000000..8cad5f5
--- /dev/null
+++ b/lib/cancan/relevant.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module CanCan
+  module Relevant
+    # Matches both the action, subject, and attribute, not necessarily the conditions
+    def relevant?(action, subject)
+      subject = subject.values.first if subject.class == Hash
+      @match_all || (matches_action?(action) && matches_subject?(subject))
+    end
+
+    private
+
+    def matches_action?(action)
+      @expanded_actions.include?(:manage) || @expanded_actions.include?(action)
+    end
+
+    def matches_subject?(subject)
+      @subjects.include?(:all) || @subjects.include?(subject) || matches_subject_class?(subject)
+    end
+
+    def matches_subject_class?(subject)
+      @subjects.any? do |sub|
+        sub.is_a?(Module) && (subject.is_a?(sub) ||
+            subject.class.to_s == sub.to_s ||
+            (subject.is_a?(Module) && subject.ancestors.include?(sub)))
+      end
+    end
+  end
+end
diff --git a/lib/cancan/rule.rb b/lib/cancan/rule.rb
index 6f2b5d4..cdab164 100644
--- a/lib/cancan/rule.rb
+++ b/lib/cancan/rule.rb
@@ -1,14 +1,18 @@
 # frozen_string_literal: true
 
 require_relative 'conditions_matcher.rb'
+require_relative 'class_matcher.rb'
+require_relative 'relevant.rb'
+
 module CanCan
   # This class is used internally and should only be called through Ability.
   # it holds the information about a "can" call made on Ability and provides
   # helpful methods to determine permission checking and conditions hash generation.
   class Rule # :nodoc:
     include ConditionsMatcher
+    include Relevant
     include ParameterValidators
-    attr_reader :base_behavior, :subjects, :actions, :conditions, :attributes
+    attr_reader :base_behavior, :subjects, :actions, :conditions, :attributes, :block
     attr_writer :expanded_actions, :conditions
 
     # The first argument when initializing is the base_behavior which is a true/false
@@ -24,9 +28,9 @@ module CanCan
       raise Error, "Subject is required for #{action}" if action && subject.nil?
 
       @base_behavior = base_behavior
-      @actions = Array(action)
-      @subjects = Array(subject)
-      @attributes = Array(attributes)
+      @actions = wrap(action)
+      @subjects = wrap(subject)
+      @attributes = wrap(attributes)
       @conditions = extra_args || {}
       @block = block
     end
@@ -34,11 +38,13 @@ module CanCan
     def inspect
       repr = "#<#{self.class.name}"
       repr += "#{@base_behavior ? 'can' : 'cannot'} #{@actions.inspect}, #{@subjects.inspect}, #{@attributes.inspect}"
-      repr += if with_scope?
-                ", #{@conditions.where_values_hash}"
-              elsif [Hash, String].include?(@conditions.class)
-                ", #{@conditions.inspect}"
-              end
+
+      if with_scope?
+        repr += ", #{@conditions.where_values_hash}"
+      elsif [Hash, String].include?(@conditions.class)
+        repr += ", #{@conditions.inspect}"
+      end
+
       repr + '>'
     end
 
@@ -55,12 +61,6 @@ module CanCan
         (!with_scope? && [nil, false, [], {}, '', ' '].include?(@conditions))
     end
 
-    # Matches both the action, subject, and attribute, not necessarily the conditions
-    def relevant?(action, subject)
-      subject = subject.values.first if subject.class == Hash
-      @match_all || (matches_action?(action) && matches_subject?(subject))
-    end
-
     def only_block?
       conditions_empty? && @block
     end
@@ -70,7 +70,7 @@ module CanCan
     end
 
     def with_scope?
-      @conditions.is_a?(ActiveRecord::Relation)
+      defined?(ActiveRecord) && @conditions.is_a?(ActiveRecord::Relation)
     end
 
     def associations_hash(conditions = @conditions)
@@ -111,11 +111,7 @@ module CanCan
     end
 
     def matches_subject_class?(subject)
-      @subjects.any? do |sub|
-        sub.is_a?(Module) && (subject.is_a?(sub) ||
-          subject.class.to_s == sub.to_s ||
-          (subject.is_a?(Module) && subject.ancestors.include?(sub)))
-      end
+      SubjectClassMatcher.matches_subject_class?(@subjects, subject)
     end
 
     def parse_attributes_from_extra_args(args)
@@ -127,8 +123,18 @@ module CanCan
     def condition_and_block_check(conditions, block, action, subject)
       return unless conditions.is_a?(Hash) && block
 
-      raise BlockAndConditionsError, 'A hash of conditions is mutually exclusive with a block. '\
+      raise BlockAndConditionsError, 'A hash of conditions is mutually exclusive with a block. ' \
         "Check \":#{action} #{subject}\" ability."
     end
+
+    def wrap(object)
+      if object.nil?
+        []
+      elsif object.respond_to?(:to_ary)
+        object.to_ary || [object]
+      else
+        [object]
+      end
+    end
   end
 end
diff --git a/lib/cancan/sti_detector.rb b/lib/cancan/sti_detector.rb
new file mode 100644
index 0000000..fd0769d
--- /dev/null
+++ b/lib/cancan/sti_detector.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class StiDetector
+  def self.sti_class?(subject)
+    return false unless defined?(ActiveRecord::Base)
+    return false unless subject.respond_to?(:descends_from_active_record?)
+    return false if subject == :all || subject.descends_from_active_record?
+    return false unless subject < ActiveRecord::Base
+
+    true
+  end
+end
diff --git a/lib/cancan/unauthorized_message_resolver.rb b/lib/cancan/unauthorized_message_resolver.rb
index 55f3580..e0c42fb 100644
--- a/lib/cancan/unauthorized_message_resolver.rb
+++ b/lib/cancan/unauthorized_message_resolver.rb
@@ -3,10 +3,12 @@
 module CanCan
   module UnauthorizedMessageResolver
     def unauthorized_message(action, subject)
+      subject = subject.values.last if subject.is_a?(Hash)
       keys = unauthorized_message_keys(action, subject)
-      variables = { action: action.to_s }
+      variables = {}
+      variables[:action] = I18n.translate("actions.#{action}", default: action.to_s)
       variables[:subject] = translate_subject(subject)
-      message = I18n.translate(keys.shift, variables.merge(scope: :unauthorized, default: keys + ['']))
+      message = I18n.translate(keys.shift, **variables.merge(scope: :unauthorized, default: keys + ['']))
       message.blank? ? nil : message
     end
 
diff --git a/lib/cancan/version.rb b/lib/cancan/version.rb
index 4a6608f..7477402 100644
--- a/lib/cancan/version.rb
+++ b/lib/cancan/version.rb
@@ -1,5 +1,5 @@
 # frozen_string_literal: true
 
 module CanCan
-  VERSION = '3.0.1'.freeze
+  VERSION = '3.5.0'.freeze
 end
diff --git a/lib/generators/cancan/ability/templates/ability.rb b/lib/generators/cancan/ability/templates/ability.rb
index ef26a8c..08b2ba1 100644
--- a/lib/generators/cancan/ability/templates/ability.rb
+++ b/lib/generators/cancan/ability/templates/ability.rb
@@ -4,14 +4,12 @@ class Ability
   include CanCan::Ability
 
   def initialize(user)
-    # Define abilities for the passed in user here. For example:
+    # Define abilities for the user here. For example:
     #
-    #   user ||= User.new # guest user (not logged in)
-    #   if user.admin?
-    #     can :manage, :all
-    #   else
-    #     can :read, :all
-    #   end
+    #   return unless user.present?
+    #   can :read, :all
+    #   return unless user.admin?
+    #   can :manage, :all
     #
     # The first argument to `can` is the action you are giving the user
     # permission to do.
@@ -26,9 +24,9 @@ class Ability
     # objects.
     # For example, here the user can only update published articles.
     #
-    #   can :update, Article, :published => true
+    #   can :update, Article, published: true
     #
     # See the wiki for details:
-    # https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities
+    # https://github.com/CanCanCommunity/cancancan/blob/develop/docs/define_check_abilities.md
   end
 end
diff --git a/logo/bullet_train.png b/logo/bullet_train.png
new file mode 100644
index 0000000..0e40a80
Binary files /dev/null and b/logo/bullet_train.png differ
diff --git a/logo/cancancan.jpg b/logo/cancancan.jpg
deleted file mode 100644
index 8002610..0000000
Binary files a/logo/cancancan.jpg and /dev/null differ
diff --git a/logo/cancancan.png b/logo/cancancan.png
new file mode 100755
index 0000000..11f1687
Binary files /dev/null and b/logo/cancancan.png differ
diff --git a/logo/goboony.png b/logo/goboony.png
new file mode 100644
index 0000000..7d77a47
Binary files /dev/null and b/logo/goboony.png differ
diff --git a/logo/honeybadger.svg b/logo/honeybadger.svg
new file mode 100644
index 0000000..7d05316
--- /dev/null
+++ b/logo/honeybadger.svg
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 1613 269" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
+    <g transform="matrix(4.16667,0,0,4.16667,-0.000625,0.00166667)">
+        <clipPath id="_clip1">
+            <rect x="0" y="0" width="386.887" height="64.327"/>
+        </clipPath>
+        <g clip-path="url(#_clip1)">
+            <path d="M300.342,15.823L295.408,39.697C295.264,40.375 294.972,40.91 294.539,41.29C294.092,41.685 293.51,41.885 292.814,41.885C292.098,41.885 291.561,41.674 291.22,41.253C290.858,40.807 290.758,40.172 290.927,39.363L295.852,15.556C296.155,14.099 297.081,13.298 298.46,13.298C299.221,13.298 299.776,13.535 300.109,14.004C300.427,14.451 300.505,15.062 300.342,15.823M302.78,8.942L301.135,10.49L300.733,10.294C300.4,10.132 299.912,9.786 299.247,9.24C298.539,8.866 297.512,8.677 296.19,8.677C293.673,8.677 291.658,9.323 290.2,10.598C289.109,11.569 288.389,12.853 288.064,14.413L282.405,41.773C282.145,43.032 282.457,44.069 283.36,44.942C284.304,45.86 285.645,46.305 287.46,46.305C288.751,46.305 289.891,46.187 290.844,45.953C291.789,45.719 292.574,45.395 293.179,44.989L294.499,44.103L294.174,45.659C294.119,45.925 294.063,46.232 294.005,46.547C293.757,47.889 293.613,48.558 293.37,48.896C292.612,49.955 291.324,50.494 289.541,50.494L194.045,50.494L193.614,51.571C192.486,54.354 191.733,56.028 191.309,56.694C190.928,57.27 190.42,57.803 189.791,58.293L289.852,58.293C290.01,58.289 293.02,58.231 294.168,58.127C295.3,58.022 296.269,57.799 297.045,57.465C298.367,56.887 299.387,56.158 300.072,55.303C300.532,54.727 301.291,50.529 301.699,48.275C301.875,47.3 302.037,46.41 302.174,45.746L309.791,8.942L302.78,8.942Z" style="fill:rgb(234,89,55);fill-rule:nonzero;"/>
+            <path d="M204.885,8.295L189.774,46.149C188.672,48.863 186.397,55.071 185.997,55.698C185.278,56.784 184.052,57.712 182.316,58.477C181.778,58.704 181.204,58.852 180.608,58.921C180.01,58.985 179.147,59.018 178.027,59.018L92.04,59.018L93.99,49.846L175.278,49.846C175.389,49.854 175.498,49.862 175.61,49.862C175.721,49.862 175.831,49.854 175.942,49.846L176.249,49.846C176.345,49.821 176.43,49.797 176.515,49.774C178.481,49.391 180.008,47.786 180.279,45.78C180.549,43.775 179.34,8.295 179.34,8.295L188.683,8.295L188.451,28.437L195.676,8.295L204.885,8.295" style="fill:rgb(234,89,55);fill-rule:nonzero;"/>
+            <path d="M75.335,58.293L86.978,58.293L98.986,0.259L87.347,0.259L82.467,23.825L77.5,23.825L82.378,0.259L70.732,0.259L58.721,58.293L70.369,58.293L76.26,29.815L81.228,29.815L75.335,58.293Z" style="fill:rgb(234,89,55);fill-rule:nonzero;"/>
+            <path d="M133.933,45.768L141.956,45.768L148.665,13.34C148.937,12.036 148.63,10.965 147.727,10.067C146.796,9.145 145.42,8.676 143.639,8.676C142.399,8.676 141.259,8.815 140.255,9.087C139.267,9.362 138.476,9.703 137.906,10.102L136.566,11.042L137.004,8.942L128.982,8.942L121.363,45.768L129.381,45.768L135.621,15.63C135.773,14.887 136.07,14.319 136.507,13.939C136.969,13.554 137.546,13.362 138.23,13.362C138.943,13.362 139.478,13.576 139.821,13.996C140.186,14.446 140.287,15.084 140.117,15.893L133.933,45.768Z" style="fill:rgb(234,89,55);fill-rule:nonzero;"/>
+            <path d="M164.662,25.149L160.111,25.149L162.07,15.685C162.224,14.922 162.557,14.325 163.055,13.911C163.555,13.503 164.11,13.297 164.707,13.297C165.66,13.297 166.131,13.708 166.357,14.051C166.664,14.519 166.732,15.137 166.566,15.946L164.662,25.149ZM165.863,8.676C162.671,8.676 160.055,9.236 158.086,10.34C155.852,11.598 154.494,13.524 153.937,16.228L149.219,39.019C148.668,41.679 149.207,43.553 150.868,44.748C152.309,45.781 154.644,46.305 157.807,46.305C161.011,46.305 163.598,45.81 165.496,44.832C167.844,43.612 169.253,41.734 169.803,39.091L170.74,35.544L162.731,35.544L161.741,39.267C161.588,39.992 161.301,40.567 160.871,40.996C160.4,41.454 159.807,41.683 159.101,41.683C158.137,41.683 157.665,41.244 157.437,40.876C157.148,40.383 157.09,39.763 157.252,38.97L159.186,29.632L171.756,29.632L174.654,15.63C175.191,13.03 174.603,11.226 172.857,10.119C171.351,9.162 168.997,8.676 165.863,8.676" style="fill:rgb(234,89,55);fill-rule:nonzero;"/>
+            <path d="M106.383,41.684C105.406,41.684 104.94,41.236 104.719,40.861C104.43,40.382 104.37,39.758 104.539,38.96L109.351,15.69C109.517,14.912 109.85,14.316 110.34,13.914C110.831,13.506 111.388,13.298 111.989,13.298C112.944,13.298 113.416,13.713 113.643,14.062C113.947,14.521 114.017,15.16 113.846,15.957L109.03,39.228C108.877,39.976 108.581,40.57 108.151,40.994C107.688,41.452 107.092,41.684 106.383,41.684M113.347,8.677C110.156,8.677 107.515,9.238 105.495,10.344C103.174,11.604 101.776,13.529 101.218,16.229L96.5,39.02C95.951,41.676 96.511,43.548 98.212,44.746C99.694,45.78 102.076,46.306 105.291,46.306C108.49,46.306 111.053,45.811 112.906,44.835C115.157,43.667 116.524,41.789 117.086,39.092L121.936,15.631C122.406,13.373 121.857,11.706 120.254,10.537C118.566,9.302 116.241,8.677 113.347,8.677" style="fill:rgb(234,89,55);fill-rule:nonzero;"/>
+            <path d="M214.234,41.684C213.48,41.684 212.926,41.449 212.588,40.984C212.267,40.525 212.191,39.933 212.347,39.162L217.242,15.493C217.398,14.764 217.698,14.212 218.133,13.849C218.576,13.483 219.149,13.298 219.841,13.298C220.554,13.298 221.088,13.511 221.429,13.93C221.794,14.378 221.892,15.016 221.724,15.826L216.844,39.421C216.689,40.157 216.4,40.702 215.961,41.088C215.519,41.483 214.937,41.684 214.234,41.684M212.092,45.737C212.839,46.115 213.939,46.306 215.361,46.306C217.948,46.306 220.158,45.647 221.932,44.348C223.457,43.219 224.338,41.983 224.627,40.571L230.293,13.207C230.552,11.95 230.24,10.913 229.336,10.038C228.406,9.134 227.027,8.677 225.236,8.677C223.944,8.677 222.802,8.785 221.842,8.997C220.916,9.205 220.14,9.531 219.539,9.964L218.188,10.943L220.449,0L212.429,0L202.954,45.768L208.92,45.768L210.196,44.249L210.688,44.628C210.916,44.804 211.384,45.173 212.092,45.737" style="fill:rgb(234,89,55);fill-rule:nonzero;"/>
+            <path d="M240.312,41.684C239.572,41.684 239.015,41.456 238.656,41.006C238.302,40.562 238.203,39.941 238.359,39.162L239.796,32.234C239.967,31.381 240.386,30.668 241.042,30.12C241.691,29.594 242.689,29.181 244.102,28.847L245.091,28.614L242.857,39.421C242.71,40.145 242.398,40.711 241.931,41.101C241.455,41.49 240.912,41.684 240.312,41.684M245.89,13.298C246.835,13.298 247.3,13.717 247.523,14.069C247.816,14.537 247.877,15.153 247.714,15.955L246.543,21.609C246.377,22.406 245.35,23.526 239.007,26.306C234.622,28.225 232.105,30.625 231.526,33.438L229.937,41.099C229.565,42.886 229.781,44.207 230.581,45.026C231.412,45.875 233.144,46.306 235.727,46.306C237.373,46.306 238.666,46.188 239.574,45.955C240.408,45.744 241.072,45.286 241.605,44.553L243.371,42.125L242.618,45.768L249.567,45.768L255.733,15.967C256.295,13.247 255.839,11.371 254.34,10.227C253.025,9.197 250.765,8.677 247.617,8.677C244.413,8.677 241.801,9.173 239.853,10.152C237.467,11.369 236.038,13.247 235.489,15.894L234.08,22.71L241.833,22.71L243.281,15.695C243.446,14.92 243.772,14.324 244.253,13.919C244.744,13.507 245.294,13.298 245.89,13.298" style="fill:rgb(234,89,55);fill-rule:nonzero;"/>
+            <path d="M266.331,41.684C265.576,41.684 265.021,41.449 264.683,40.984C264.362,40.523 264.285,39.913 264.443,39.157L269.322,15.562C269.628,14.102 270.556,13.298 271.938,13.298C272.809,13.298 273.285,13.668 273.53,13.977C273.882,14.427 273.984,15.023 273.837,15.752L268.935,39.421C268.789,40.142 268.494,40.704 268.057,41.088C267.605,41.484 267.026,41.684 266.331,41.684M268.99,8.677C267.115,8.677 265.482,9.157 264.138,10.103C262.799,11.042 262.013,12.144 261.733,13.472L256.073,40.831C255.775,42.28 256.158,43.519 257.246,44.618C258.357,45.738 260.209,46.306 262.748,46.306C264.27,46.306 265.52,46.098 266.463,45.689C266.514,45.665 266.822,45.504 268.22,44.598L268.865,44.179L269.543,45.768L275.645,45.768L285.118,0L277.094,0L274.887,10.669L274.049,9.924C273.674,9.591 273.039,9.297 272.163,9.053C271.251,8.804 270.182,8.677 268.99,8.677" style="fill:rgb(234,89,55);fill-rule:nonzero;"/>
+            <path d="M324.295,25.149L319.743,25.149L321.702,15.685C321.857,14.922 322.19,14.325 322.688,13.911C323.188,13.503 323.742,13.297 324.341,13.297C325.293,13.297 325.764,13.708 325.99,14.051C326.297,14.519 326.365,15.137 326.199,15.946L324.295,25.149ZM325.495,8.676C322.304,8.676 319.688,9.236 317.719,10.34C315.484,11.598 314.127,13.524 313.569,16.228L308.851,39.019C308.301,41.679 308.84,43.553 310.5,44.748C311.941,45.781 314.277,46.305 317.44,46.305C320.643,46.305 323.23,45.81 325.129,44.832C327.475,43.612 328.885,41.734 329.436,39.091L330.371,35.544L322.364,35.544L321.374,39.267C321.221,39.992 320.934,40.567 320.504,40.996C320.033,41.454 319.44,41.683 318.734,41.683C317.77,41.683 317.297,41.244 317.07,40.876C316.781,40.383 316.723,39.763 316.885,38.97L318.819,29.632L331.389,29.632L334.287,15.63C334.822,13.03 334.235,11.226 332.49,10.119C330.984,9.162 328.63,8.676 325.495,8.676" style="fill:rgb(234,89,55);fill-rule:nonzero;"/>
+            <path d="M332.888,45.768L340.911,45.768L347.161,15.567C347.306,14.846 347.606,14.288 348.051,13.917C348.495,13.548 349.068,13.362 349.755,13.362C350.467,13.362 351.001,13.575 351.345,13.996C351.71,14.444 351.811,15.086 351.643,15.903L350.077,23.45L358.102,23.45L360.196,13.34C360.465,12.038 360.159,10.968 359.256,10.066C358.327,9.145 356.948,8.676 355.164,8.676C353.922,8.676 352.785,8.815 351.787,9.087C350.794,9.352 350.006,9.689 349.436,10.088L348.099,11.028L348.53,8.942L340.506,8.942L332.888,45.768Z" style="fill:rgb(234,89,55);fill-rule:nonzero;"/>
+            <path d="M365.65,21.07L371.48,21.07L372.302,17.575L366.466,17.575L365.65,21.07ZM359.777,46.162L365.605,46.162L371.005,23.116L365.175,23.116L359.777,46.162Z" style="fill:rgb(234,89,55);fill-rule:nonzero;"/>
+            <path d="M375.71,43.554C375.201,43.554 374.835,43.392 374.623,43.072C374.354,42.684 374.419,42.207 374.492,41.882L377.889,27.356C378.004,26.871 378.233,26.501 378.568,26.258C378.902,26.016 379.271,25.893 379.666,25.893C380.172,25.893 380.537,26.043 380.752,26.341C380.967,26.63 381.013,27.032 380.892,27.529L377.497,42.048C377.384,42.514 377.184,42.87 376.885,43.142C376.568,43.413 376.164,43.554 375.71,43.554M380.625,22.942C378.371,22.942 376.486,23.296 375.023,23.996C373.375,24.789 372.379,25.999 371.981,27.694L368.646,41.915C368.258,43.575 368.658,44.75 369.874,45.509C370.944,46.163 372.649,46.497 374.941,46.497C377.218,46.497 379.044,46.184 380.371,45.566C381.98,44.825 382.953,43.647 383.35,41.961L386.776,27.314C387.104,25.904 386.709,24.861 385.569,24.127C384.361,23.341 382.699,22.942 380.625,22.942" style="fill:rgb(234,89,55);fill-rule:nonzero;"/>
+            <path d="M352.467,46.162L357.781,46.162L358.84,41.821L353.527,41.821L352.467,46.162Z" style="fill:rgb(234,89,55);fill-rule:nonzero;"/>
+            <path d="M15.619,45.813L6.002,36.199C4.776,34.966 4.776,32.957 6.002,31.73L25.383,12.342C26.614,11.113 28.623,11.113 29.851,12.342L31.142,13.633L33.973,10.606L31.191,7.822C29.224,5.858 26.01,5.858 24.044,7.822L1.477,30.388C-0.492,32.353 -0.492,35.572 1.477,37.541L14.234,50.296L15.619,45.813Z" style="fill:rgb(234,89,55);fill-rule:nonzero;"/>
+            <path d="M53.758,30.388L40.64,17.276L39.093,21.589L49.227,31.727C50.462,32.958 50.462,34.967 49.227,36.199L29.852,55.583C28.623,56.811 26.614,56.811 25.383,55.583L23.225,53.425L20.391,56.448L24.044,60.1C26.01,62.071 29.225,62.071 31.192,60.1L53.758,37.541C55.721,35.572 55.721,32.354 53.758,30.388" style="fill:rgb(234,89,55);fill-rule:nonzero;"/>
+            <path d="M45.44,0.206L34.34,31.054C34.197,31.46 34.262,31.912 34.529,32.253C34.769,32.565 35.14,32.747 35.533,32.747C35.566,32.747 35.601,32.747 35.637,32.74L41.39,32.253L11.251,64.327L19.744,36.68C19.867,36.276 19.779,35.845 19.519,35.517C19.272,35.214 18.915,35.045 18.534,35.045C18.495,35.045 18.46,35.045 18.43,35.048L12.345,35.555L45.44,0.206" style="fill:rgb(234,89,55);fill-rule:nonzero;"/>
+        </g>
+    </g>
+</svg>
diff --git a/logo/modern_treasury.svg b/logo/modern_treasury.svg
new file mode 100644
index 0000000..8a15c49
--- /dev/null
+++ b/logo/modern_treasury.svg
@@ -0,0 +1,14 @@
+<svg width="845" height="72" viewBox="0 0 845 72" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M1.64529 21.5484C0.835871 21.2786 0 21.8811 0 22.7343V69C0 70.6569 1.34315 72 3 72H14C15.6569 72 17 70.6569 17 69V29.5497C17 27.828 15.8983 26.2994 14.2649 25.755L1.64529 21.5484Z" fill="#1BA778"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M27.5 31.9009C27.5 31.0477 28.3359 30.4453 29.1453 30.7151L36 33L42.8547 30.7151C43.6641 30.4453 44.5 31.0477 44.5 31.9009V69C44.5 70.6569 43.1569 72 41.5 72H30.5C28.8431 72 27.5 70.6569 27.5 69V31.9009Z" fill="#1BA778"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M57.7351 25.755C56.1017 26.2994 55 27.828 55 29.5497V69C55 70.6569 56.3431 72 58 72H69C70.6569 72 72 70.6569 72 69V22.7343C72 21.8811 71.1641 21.2786 70.3547 21.5484L57.7351 25.755Z" fill="#1BA778"/>
+<path d="M36 23L1.36754 11.4558C0.550856 11.1836 0 10.4193 0 9.55848V4C0 1.79086 1.79086 0 4 0H68C70.2091 0 72 1.79086 72 4V9.55848C72 10.4193 71.4491 11.1836 70.6325 11.4558L36 23Z" fill="#1BA778"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M677.588 19.2428C682.226 19.2428 683.807 21.9969 684.482 24.3956L684.465 24.4133C684.927 25.7815 685.602 26.4567 686.952 26.3501H692.833C694.077 26.3501 695.09 25.3195 695.09 24.058C695.09 16.7375 689.102 9.86128 677.802 9.86128C665.364 9.86128 659.607 15.6004 659.607 24.1823C659.607 35.555 668.298 38.1584 675.585 40.3409C680.844 41.9161 685.371 43.2722 685.371 47.5474C685.371 52.0071 683.132 53.4997 677.482 53.4997C670.241 53.4997 669.232 48.7536 668.854 46.9783C668.842 46.9194 668.83 46.8637 668.819 46.8117C668.803 46.7424 668.789 46.6795 668.775 46.6234C668.509 45.8239 667.869 44.6867 666.626 44.6867H660.744C659.501 44.6867 658.488 45.7173 658.488 46.9788C658.488 53.9616 664.245 62.7746 677.357 62.7746C686.632 62.7746 695.783 58.8834 695.783 47.654C695.783 35.593 686.664 32.9397 679.277 30.7902C674.247 29.3266 670.019 28.0964 670.019 24.289C670.019 20.2734 672.844 19.2428 677.588 19.2428ZM744.485 13.0595C744.485 11.798 743.472 10.7675 742.229 10.7675H736.223C734.979 10.7675 733.967 11.798 733.967 13.0595V42.0393C733.967 49.6085 730.377 52.8067 725.26 52.8067C720.143 52.8067 716.678 49.5907 716.678 42.0393V13.0595C716.678 11.798 715.665 10.7675 714.422 10.7675H708.434C707.19 10.7675 706.177 11.798 706.177 13.0595V42.0393C706.177 54.8678 713.871 62.7746 725.278 62.7746C736.685 62.7746 744.485 54.8678 744.485 42.0393V13.0595ZM222.19 9.96789C233.721 9.96789 242.41 18.5499 242.41 31.4139V41.2042C242.41 54.0327 233.828 62.7391 222.19 62.7391C210.552 62.7391 202.183 54.0327 202.183 41.2042V31.3962C202.183 18.5676 210.658 9.95012 222.19 9.95012V9.96789ZM222.19 52.7889C227.502 52.7889 231.909 48.7911 231.909 41.3285V31.5916C231.909 24.0402 227.502 20.0246 222.19 20.0246C216.877 20.0246 212.737 24.0224 212.737 31.5916V41.3285C212.737 48.8977 217.037 52.7889 222.19 52.7889ZM254.741 59.7718V13.0595C254.741 11.798 255.754 10.7675 256.997 10.7675H272.829C284.574 10.7675 293.173 19.3494 293.173 32.2135V40.6889C293.173 53.5174 284.467 62.2238 272.829 62.2238H256.997C255.754 62.1172 254.741 61.0866 254.741 59.8251V59.7718ZM265.135 51.9894H272.704C278.017 51.9894 282.424 47.9916 282.424 40.529V32.2846C282.424 24.8398 278.017 20.8242 272.704 20.8242H265.135V51.9894ZM189.87 13.0595V59.7896C189.87 61.0511 188.857 62.0817 187.613 62.0817H182.069C180.826 62.0817 179.813 61.0511 179.813 59.7896V27.4872L168.512 60.5891C168.068 61.7263 167.375 62.0817 166.629 62.0817H161.068C160.055 62.0817 159.593 61.6197 159.255 60.7135L147.582 27.3628V59.7718C147.582 61.0333 146.569 62.0639 145.325 62.0639H139.906C138.662 62.0639 137.649 61.0333 137.649 59.7718V13.0595C137.649 11.798 138.662 10.7675 139.906 10.7675H149.998C150.762 10.7142 151.455 11.1939 151.686 11.9046L163.893 47.1742L176.206 12.0112C176.437 11.5493 176.881 10.7497 177.787 10.7497H187.755C188.892 10.7497 189.905 11.7802 189.905 13.0418L189.87 13.0595ZM315.899 20.7353H335.106C336.35 20.7353 337.363 19.7048 337.363 18.4433V13.0595C337.363 11.798 336.35 10.7674 335.106 10.7674H307.637C306.393 10.7674 305.38 11.798 305.38 13.0595V59.8073C305.38 61.0689 306.393 62.0994 307.637 62.0994H335.106C336.35 62.0994 337.363 61.0689 337.363 59.8073V54.317C337.363 53.0199 336.35 52.1138 335.106 52.1138H315.899V41.0087H330.806C332.05 41.0087 333.063 39.9782 333.063 38.7166V33.3329C333.063 32.0714 332.05 31.0408 330.806 31.0408H315.899V20.7353ZM357.6 62.0639H351.613C350.369 62.0639 349.356 61.0333 349.356 59.7718V13.0595C349.356 11.798 350.369 10.7674 351.613 10.7674H371.051C379.97 10.7674 386.651 15.9202 386.651 26.1191V28.3223C386.651 35.1985 384.164 40.1203 377.732 42.4124L385.976 58.4392L386.001 58.4899C386.33 59.1651 386.651 59.8242 386.651 60.5003C386.651 61.4065 385.976 62.0994 385.07 62.0994H377.714C376.47 62.0994 375.795 61.1932 375.333 60.3759L367.533 44.3491H359.857V59.8073C359.857 61.0689 358.844 62.0994 357.6 62.0994V62.0639ZM370.145 34.3634C373.538 34.3634 376.133 32.6399 376.133 28.1802V27.1496C376.133 22.5654 373.538 20.7353 370.145 20.7353H359.75V34.3634H370.145ZM438.285 59.7896V13.0595C438.285 11.798 437.272 10.7674 436.029 10.7674H430.272C429.028 10.7674 428.015 11.798 428.015 13.0595V43.0698L411.295 12.491C410.958 11.9046 409.945 10.7674 408.701 10.7674H400.812C399.569 10.7674 398.556 11.798 398.556 13.0595V59.8073C398.556 61.0689 399.569 62.0994 400.812 62.0994H406.569C407.813 62.0994 408.826 61.0689 408.826 59.8073V27.1674L426.807 59.452C428.332 62.0908 429.411 62.0856 430.187 62.0819L430.272 62.0816H436.029C437.272 62.0816 438.285 61.0511 438.285 59.7896ZM472.418 10.7674C471.174 10.7674 470.161 11.798 470.161 13.0595L470.179 18.4077C470.179 19.6693 471.192 20.6998 472.435 20.6998H484.411V59.7896C484.411 61.0511 485.424 62.0816 486.668 62.0816H492.655C493.899 62.0816 494.912 61.0511 494.912 59.7896V20.7353H506.888C508.131 20.7353 509.144 19.7048 509.144 18.4433V13.0595C509.144 11.798 508.131 10.7674 506.888 10.7674H472.418ZM527.445 62.0639H521.457C520.214 62.0639 519.201 61.0333 519.201 59.7718V13.0595C519.201 11.798 520.214 10.7674 521.457 10.7674H540.896C549.815 10.7674 556.496 15.9202 556.496 26.1191V28.3223C556.496 35.1985 554.008 40.1203 547.576 42.4124L555.821 58.4392L555.846 58.4901C556.175 59.1652 556.496 59.8243 556.496 60.5003C556.496 61.4065 555.821 62.0994 554.915 62.0994H547.559C546.315 62.0994 545.64 61.1932 545.178 60.3759L537.378 44.3491H529.702V59.8073C529.702 61.0689 528.689 62.0994 527.445 62.0994V62.0639ZM539.989 34.3634C543.383 34.3634 545.977 32.6399 545.977 28.1802V27.1496C545.977 22.5654 543.383 20.7353 539.989 20.7353H529.595V34.3634H539.989ZM578.99 20.7353H598.198C599.441 20.7353 600.454 19.7048 600.454 18.4433V13.0595C600.454 11.798 599.441 10.7674 598.198 10.7674H570.728C569.484 10.7674 568.472 11.798 568.472 13.0595V59.8073C568.472 61.0689 569.484 62.0994 570.728 62.0994H598.198C599.441 62.0994 600.454 61.0689 600.454 59.8073V54.317C600.454 53.0199 599.441 52.1138 598.198 52.1138H578.99V41.0087H593.898C595.141 41.0087 596.154 39.9782 596.154 38.7166V33.3329C596.154 32.0714 595.141 31.0408 593.898 31.0408H578.99V20.7353ZM653.362 60.4648C653.362 61.3709 652.687 62.0639 651.674 62.0639H644.443C643.59 62.0994 642.826 61.6019 642.524 60.8024L639.592 51.9894H620.829L617.897 60.8024C617.56 61.8329 616.76 62.1705 616.209 62.1705H609.333C608.427 62.1705 607.645 61.4775 607.645 60.5714C607.645 60.2733 607.741 59.9827 607.838 59.6899C607.891 59.5288 607.945 59.3671 607.983 59.2032L624.365 12.4732C624.596 11.7802 625.271 10.8741 626.515 10.8741H634.652C635.896 10.8741 636.571 11.7802 636.802 12.4732L652.687 58.6347C652.918 58.6347 653.362 59.8962 653.362 60.4648ZM636.643 42.3591L630.548 23.1163L624.098 42.3591H636.643ZM765.735 62.0639H759.747C758.503 62.0639 757.491 61.0333 757.491 59.7718V13.0595C757.491 11.798 758.503 10.7674 759.747 10.7674H779.186C788.105 10.7674 794.786 15.9202 794.786 26.1191V28.3223C794.786 35.1985 792.298 40.1203 785.866 42.4124L794.111 58.4392L794.135 58.4899C794.465 59.1651 794.786 59.8242 794.786 60.5003C794.786 61.4065 794.111 62.0994 793.204 62.0994H785.849C784.605 62.0994 783.93 61.1932 783.468 60.3759L775.667 44.3491H767.992V59.8073C767.992 61.0689 766.979 62.0994 765.735 62.0994V62.0639ZM778.404 34.3634C781.797 34.3634 784.392 32.6399 784.392 28.1802V27.1496C784.392 22.5654 781.797 20.7353 778.404 20.7353H768.009V34.3634H778.404ZM843.713 15.0728C844.168 14.2712 844.763 13.2235 844.763 12.4732V12.4554C844.816 11.4782 844.052 10.6608 843.075 10.6253H835.95C834.874 10.6253 834.476 11.2235 834.17 11.684C834.122 11.7559 834.076 11.8245 834.031 11.8868L823.068 32.9598L812.443 12.0112C811.998 10.9984 811.323 10.6431 810.755 10.6431H803.079H803.008C802.084 10.6786 801.373 11.4426 801.391 12.3666C801.391 12.9767 801.919 13.8558 802.271 14.4434C802.381 14.6259 802.473 14.7802 802.528 14.8897L817.666 42.9454V59.7718C817.666 61.0333 818.679 62.0639 819.923 62.0639H825.911C827.155 62.0639 828.167 61.0333 828.167 59.7718V43.1587L843.626 15.2272L843.713 15.0728Z" fill="#1BA778"/>
+</g>
+<defs>
+<clipPath id="clip0">
+<rect width="845" height="72" fill="white"/>
+</clipPath>
+</defs>
+</svg>
diff --git a/logo/new_relic.png b/logo/new_relic.png
new file mode 100644
index 0000000..6b9f27c
Binary files /dev/null and b/logo/new_relic.png differ
diff --git a/logo/ontra.png b/logo/ontra.png
new file mode 100644
index 0000000..e7f243b
Binary files /dev/null and b/logo/ontra.png differ
diff --git a/logo/renuo.png b/logo/renuo.png
index 6addfda..0ce669d 100644
Binary files a/logo/renuo.png and b/logo/renuo.png differ
diff --git a/rubymine_rspec.png b/rubymine_rspec.png
new file mode 100644
index 0000000..03a9526
Binary files /dev/null and b/rubymine_rspec.png differ
diff --git a/spec/README.md b/spec/README.md
deleted file mode 100644
index 071cb1a..0000000
--- a/spec/README.md
+++ /dev/null
@@ -1,19 +0,0 @@
-= CanCanCan Specs
-
-== Running the specs
-
-To run the specs first run the +bundle+ command to install the necessary gems.
-
-  bundle
-
-Then run the appraisal command to install all the necessary test sets.
-
-  bundle exec appraisal install
-
-You can then run all test sets:
-
-  bundle exec appraisal rspec
-
-Or individual ones:
-
-  bundle exec appraisal activerecord_5.2.0 rspec
diff --git a/spec/cancan/ability_spec.rb b/spec/cancan/ability_spec.rb
index ed14578..486cbc4 100644
--- a/spec/cancan/ability_spec.rb
+++ b/spec/cancan/ability_spec.rb
@@ -127,6 +127,17 @@ describe CanCan::Ability do
     expect(@block_called).to be(true)
   end
 
+  it 'allows passing nil as extra arguments' do
+    @ability.can :to_s, Integer do |integer, arg1, arg2|
+      expect(integer).to eq(42)
+      expect(arg1).to eq(nil)
+      expect(arg2).to eq(:foo)
+      @block_called = true
+    end
+    @ability.can?(:to_s, 42, nil, nil, :foo)
+    expect(@block_called).to be(true)
+  end
+
   it 'passes nil to object when comparing class with can check' do
     @ability.can do |action, object_class, object|
       expect(action).to eq(:foo)
@@ -441,6 +452,31 @@ describe CanCan::Ability do
     expect(@ability.attributes_for(:new, Range)).to eq(foo: 'foo', bar: 123, baz: 'baz')
   end
 
+  it 'allows to check ability even the object implements `#to_a`' do
+    stub_const('X', Class.new do
+      def self.to_a
+        []
+      end
+    end)
+
+    @ability.can :read, X
+    expect(@ability.can?(:read, X.new)).to be(true)
+  end
+
+  it 'respects `#to_ary`' do
+    stub_const('X', Class.new do
+      def self.to_ary
+        [Y]
+      end
+    end)
+
+    stub_const('Y', Class.new)
+
+    @ability.can :read, X
+    expect(@ability.can?(:read, X.new)).to be(false)
+    expect(@ability.can?(:read, Y.new)).to be(true)
+  end
+
   # rubocop:disable Style/SymbolProc
   describe 'different usages of blocks and procs' do
     class A
@@ -649,6 +685,14 @@ describe CanCan::Ability do
     expect(@ability.permitted_attributes(:read, Integer)).to eq([:to_s])
   end
 
+  it 'does not retain references to subjects that do not have direct rules' do
+    @ability.can :read, String
+
+    @ability.can?(:read, 'foo')
+
+    expect(@ability.instance_variable_get(:@rules_index)).not_to have_key('foo')
+  end
+
   describe 'unauthorized message' do
     after(:each) do
       I18n.backend = nil
@@ -682,11 +726,46 @@ describe CanCan::Ability do
       end
     end
 
+    it "uses action's name in i18n" do
+      class Account
+        include ActiveModel::Model
+      end
+
+      I18n.backend.store_translations :en,
+                                      actions: { update: 'english name' },
+                                      unauthorized: { update: { all: '%{action}' } }
+      I18n.backend.store_translations :ja,
+                                      actions: { update: 'japanese name' },
+                                      unauthorized: { update: { all: '%{action}' } }
+
+      I18n.with_locale(:en) do
+        expect(@ability.unauthorized_message(:update, Account)).to eq('english name')
+      end
+
+      I18n.with_locale(:ja) do
+        expect(@ability.unauthorized_message(:update, Account)).to eq('japanese name')
+      end
+    end
+
     it 'uses symbol as subject directly' do
       I18n.backend.store_translations :en, unauthorized: { has: { cheezburger: 'Nom nom nom. I eated it.' } }
       expect(@ability.unauthorized_message(:has, :cheezburger)).to eq('Nom nom nom. I eated it.')
     end
 
+    it 'uses correct i18n keys when hashes are used' do
+      # Hashes are sent as subject when using:
+      # load_and_authorize_resource :blog
+      # load_and_authorize_resource through: :blog
+      # And subject for collection actions (ie: index) returns: { <Blog id:1> => Post(id:integer) }
+      I18n.backend.store_translations :en, unauthorized: { update: { all: 'all', array: 'foo' } }
+      expect(@ability.unauthorized_message(:update, Hash => Array)).to eq('foo')
+    end
+
+    it 'uses correct subject when hashes are used' do
+      I18n.backend.store_translations :en, unauthorized: { manage: { all: '%<action>s %<subject>s' } }
+      expect(@ability.unauthorized_message(:update, Hash => Array)).to eq('update array')
+    end
+
     it "falls back to 'manage' and 'all'" do
       I18n.backend.store_translations :en, unauthorized: {
         manage: { all: 'manage all', array: 'manage array' },
@@ -761,4 +840,38 @@ describe CanCan::Ability do
       expect(@ability.send(:rules).size).to eq(0)
     end
   end
+
+  describe 'when #can? is used with a Hash (nested resources)' do
+    it 'is unauthorized with no rules' do
+      expect(@ability.can?(:read, 1 => Symbol)).to be(false)
+    end
+
+    it 'is authorized when the child is authorized' do
+      @ability.can :read, Symbol
+      expect(@ability.can?(:read, 1 => Symbol)).to be(true)
+    end
+
+    it 'is authorized when the condition doesn\'t concern the parent' do
+      @ability.can :read, Symbol, whatever: true
+      expect(@ability.can?(:read, 1 => Symbol)).to be(true)
+    end
+
+    it 'verifies the parent against an equality condition' do
+      @ability.can :read, Symbol, integer: 1
+      expect(@ability.can?(:read, 1 => Symbol)).to be(true)
+      expect(@ability.can?(:read, 2 => Symbol)).to be(false)
+    end
+
+    it 'verifies the parent against an array condition' do
+      @ability.can :read, Symbol, integer: [0, 1]
+      expect(@ability.can?(:read, 1 => Symbol)).to be(true)
+      expect(@ability.can?(:read, 2 => Symbol)).to be(false)
+    end
+
+    it 'verifies the parent against a hash condition' do
+      @ability.can :read, Symbol, integer: { to_i: 1 }
+      expect(@ability.can?(:read, 1 => Symbol)).to be(true)
+      expect(@ability.can?(:read, 2 => Symbol)).to be(false)
+    end
+  end
 end
diff --git a/spec/cancan/controller_additions_spec.rb b/spec/cancan/controller_additions_spec.rb
index 98b2e66..b16c0c7 100644
--- a/spec/cancan/controller_additions_spec.rb
+++ b/spec/cancan/controller_additions_spec.rb
@@ -30,7 +30,7 @@ describe CanCan::ControllerAdditions do
 
   it 'load_and_authorize_resource setups a before filter which passes call to ControllerResource' do
     expect(cancan_resource_class = double).to receive(:load_and_authorize_resource)
-    allow(CanCan::ControllerResource).to receive(:new).with(@controller, nil, foo: :bar) { cancan_resource_class }
+    allow(CanCan::ControllerResource).to receive(:new).with(@controller, nil, { foo: :bar }) { cancan_resource_class }
     expect(@controller_class)
       .to receive(:before_action).with({}) { |_options, &block| block.call(@controller) }
     @controller_class.load_and_authorize_resource foo: :bar
@@ -38,7 +38,9 @@ describe CanCan::ControllerAdditions do
 
   it 'load_and_authorize_resource properly passes first argument as the resource name' do
     expect(cancan_resource_class = double).to receive(:load_and_authorize_resource)
-    allow(CanCan::ControllerResource).to receive(:new).with(@controller, :project, foo: :bar) { cancan_resource_class }
+    allow(CanCan::ControllerResource).to receive(:new).with(@controller, :project, { foo: :bar }) do
+      cancan_resource_class
+    end
     expect(@controller_class)
       .to receive(:before_action).with({}) { |_options, &block| block.call(@controller) }
     @controller_class.load_and_authorize_resource :project, foo: :bar
@@ -51,9 +53,9 @@ describe CanCan::ControllerAdditions do
 
   it 'authorize_resource setups a before filter which passes call to ControllerResource' do
     expect(cancan_resource_class = double).to receive(:authorize_resource)
-    allow(CanCan::ControllerResource).to receive(:new).with(@controller, nil, foo: :bar) { cancan_resource_class }
+    allow(CanCan::ControllerResource).to receive(:new).with(@controller, nil, { foo: :bar }) { cancan_resource_class }
     expect(@controller_class)
-      .to receive(:before_action).with(except: :show, if: true) do |_options, &block|
+      .to receive(:before_action).with({ except: :show, if: true }) do |_options, &block|
         block.call(@controller)
       end
     @controller_class.authorize_resource foo: :bar, except: :show, if: true
@@ -61,9 +63,9 @@ describe CanCan::ControllerAdditions do
 
   it 'load_resource setups a before filter which passes call to ControllerResource' do
     expect(cancan_resource_class = double).to receive(:load_resource)
-    allow(CanCan::ControllerResource).to receive(:new).with(@controller, nil, foo: :bar) { cancan_resource_class }
+    allow(CanCan::ControllerResource).to receive(:new).with(@controller, nil, { foo: :bar }) { cancan_resource_class }
     expect(@controller_class)
-      .to receive(:before_action).with(only: %i[show index], unless: false) do |_options, &block|
+      .to receive(:before_action).with({ only: %i[show index], unless: false }) do |_options, &block|
         block.call(@controller)
       end
     @controller_class.load_resource foo: :bar, only: %i[show index], unless: false
@@ -78,9 +80,9 @@ describe CanCan::ControllerAdditions do
 
   it 'check_authorization triggers AuthorizationNotPerformed in after filter' do
     expect(@controller_class)
-      .to receive(:after_action).with(only: [:test]) { |_options, &block| block.call(@controller) }
+      .to receive(:after_action).with({ only: [:test] }) { |_options, &block| block.call(@controller) }
     expect do
-      @controller_class.check_authorization(only: [:test])
+      @controller_class.check_authorization({ only: [:test] })
     end.to raise_error(CanCan::AuthorizationNotPerformed)
   end
 
@@ -105,7 +107,7 @@ describe CanCan::ControllerAdditions do
   it 'check_authorization does not raise error when @_authorized is set' do
     @controller.instance_variable_set(:@_authorized, true)
     expect(@controller_class)
-      .to receive(:after_action).with(only: [:test]) { |_options, &block| block.call(@controller) }
+      .to receive(:after_action).with({ only: [:test] }) { |_options, &block| block.call(@controller) }
     expect do
       @controller_class.check_authorization(only: [:test])
     end.not_to raise_error
@@ -139,7 +141,7 @@ describe CanCan::ControllerAdditions do
     expect(@controller_class.cancan_skipper[:load][:article]).to eq({})
   end
 
-  it 'skip_load_and_authore_resource adds itself to the cancan skipper with given model name and options' do
+  it 'skip_load_and_authorize_resource adds itself to the cancan skipper with given model name and options' do
     @controller_class.skip_load_and_authorize_resource(:project, only: %i[index show])
     expect(@controller_class.cancan_skipper[:load][:project]).to eq(only: %i[index show])
     expect(@controller_class.cancan_skipper[:authorize][:project]).to eq(only: %i[index show])
diff --git a/spec/cancan/controller_resource_spec.rb b/spec/cancan/controller_resource_spec.rb
index b6e511b..f66eb04 100644
--- a/spec/cancan/controller_resource_spec.rb
+++ b/spec/cancan/controller_resource_spec.rb
@@ -144,7 +144,7 @@ describe CanCan::ControllerResource do
         params.merge!(controller: 'model', model: { name: 'test' })
       end
 
-      it 'accepts and uses the specified symbol for santitizing input' do
+      it 'accepts and uses the specified symbol for sanitizing input' do
         allow(controller).to receive(:resource_params).and_return(resource: 'params')
         allow(controller).to receive(:model_params).and_return(model: 'params')
         allow(controller).to receive(:create_params).and_return(create: 'params')
@@ -163,7 +163,7 @@ describe CanCan::ControllerResource do
         expect(resource.send('resource_params')).to eq(custom: 'params')
       end
 
-      it 'prefers to use the create_params method for santitizing input' do
+      it 'prefers to use the create_params method for sanitizing input' do
         allow(controller).to receive(:resource_params).and_return(resource: 'params')
         allow(controller).to receive(:model_params).and_return(model: 'params')
         allow(controller).to receive(:create_params).and_return(create: 'params')
@@ -172,7 +172,7 @@ describe CanCan::ControllerResource do
         expect(resource.send('resource_params')).to eq(create: 'params')
       end
 
-      it 'prefers to use the <model_name>_params method for santitizing input if create is not found' do
+      it 'prefers to use the <model_name>_params method for sanitizing input if create is not found' do
         allow(controller).to receive(:resource_params).and_return(resource: 'params')
         allow(controller).to receive(:model_params).and_return(model: 'params')
         allow(controller).to receive(:custom_params).and_return(custom: 'params')
@@ -180,7 +180,7 @@ describe CanCan::ControllerResource do
         expect(resource.send('resource_params')).to eq(model: 'params')
       end
 
-      it 'prefers to use the resource_params method for santitizing input if create or model is not found' do
+      it 'prefers to use the resource_params method for sanitizing input if create or model is not found' do
         allow(controller).to receive(:resource_params).and_return(resource: 'params')
         allow(controller).to receive(:custom_params).and_return(custom: 'params')
         resource = CanCan::ControllerResource.new(controller)
@@ -282,7 +282,7 @@ describe CanCan::ControllerResource do
 
     it 'authorizes nested resource through parent association on index action' do
       controller.instance_variable_set(:@category, category = double)
-      allow(controller).to receive(:authorize!).with(:index, category => Model) { raise CanCan::AccessDenied }
+      allow(controller).to receive(:authorize!).with(:index, { category => Model }) { raise CanCan::AccessDenied }
       resource = CanCan::ControllerResource.new(controller, through: :category)
       expect { resource.authorize_resource }.to raise_error(CanCan::AccessDenied)
     end
@@ -542,7 +542,7 @@ describe CanCan::ControllerResource do
     end
   end
 
-  it 'calls the santitizer when the parameter hash matches our object' do
+  it 'calls the sanitizer when the parameter hash matches our object' do
     params.merge!(action: 'create', model: { name: 'test' })
     allow(controller).to receive(:create_params).and_return({})
 
@@ -551,7 +551,7 @@ describe CanCan::ControllerResource do
     expect(controller.instance_variable_get(:@model).name).to eq nil
   end
 
-  it 'santitizes correctly when the instance name is overriden' do
+  it 'sanitizes correctly when the instance name is overridden' do
     params.merge!(action: 'create', custom_name: { name: 'foobar' })
     allow(controller).to receive(:create_params).and_return({})
 
@@ -560,7 +560,7 @@ describe CanCan::ControllerResource do
     expect(controller.instance_variable_get(:@custom_name).name).to eq nil
   end
 
-  it 'calls the santitize method on non-save actions when required' do
+  it 'calls the sanitize method on non-save actions when required' do
     params.merge!(action: 'new', model: { name: 'test' })
 
     allow(controller).to receive(:resource_params).and_return({})
diff --git a/spec/cancan/exceptions_spec.rb b/spec/cancan/exceptions_spec.rb
index ee2a1f5..b441861 100644
--- a/spec/cancan/exceptions_spec.rb
+++ b/spec/cancan/exceptions_spec.rb
@@ -14,11 +14,17 @@ describe CanCan::AccessDenied do
       expect(@exception.conditions).to eq(:some_conditions)
     end
 
-    it 'has a changable default message' do
+    it 'has a changeable default message' do
       expect(@exception.message).to eq('You are not authorized to access this page.')
       @exception.default_message = 'Unauthorized!'
       expect(@exception.message).to eq('Unauthorized!')
     end
+
+    it 'has debug information on inspect' do
+      expect(@exception.inspect).to eq(
+        '#<CanCan::AccessDenied action: :some_action, subject: :some_subject, conditions: :some_conditions>'
+      )
+    end
   end
 
   describe 'with only a message' do
diff --git a/spec/cancan/matchers_spec.rb b/spec/cancan/matchers_spec.rb
index 985304f..5990e1a 100644
--- a/spec/cancan/matchers_spec.rb
+++ b/spec/cancan/matchers_spec.rb
@@ -49,7 +49,7 @@ describe 'be_able_to' do
       is_expected.not_to be_able_to([], 123)
     end
 
-    it 'delegates to can? with array of abilities with only one eligable ability' do
+    it 'delegates to can? with array of abilities with only one eligible ability' do
       is_expected.to receive(:can?).with(:read, 123) { true }
       is_expected.to receive(:can?).with(:update, 123) { false }
       is_expected.not_to be_able_to(%i[read update], 123)
diff --git a/spec/cancan/model_adapters/accessible_by_has_many_through_spec.rb b/spec/cancan/model_adapters/accessible_by_has_many_through_spec.rb
index 28e2d83..28cca16 100644
--- a/spec/cancan/model_adapters/accessible_by_has_many_through_spec.rb
+++ b/spec/cancan/model_adapters/accessible_by_has_many_through_spec.rb
@@ -72,27 +72,66 @@ RSpec.describe CanCan::ModelAdapters::ActiveRecord5Adapter do
     ability.can :read, Post, editors: { user_id: @user1 }
   end
 
-  describe 'preloading of associatons' do
-    it 'preloads associations correctly' do
-      posts = Post.accessible_by(ability).includes(likes: :user)
-      expect(posts[0].association(:likes)).to be_loaded
-      expect(posts[0].likes[0].association(:user)).to be_loaded
+  CanCan.valid_accessible_by_strategies.each do |strategy|
+    context "using #{strategy} strategy" do
+      before :each do
+        CanCan.accessible_by_strategy = strategy
+      end
+
+      describe 'preloading of associations' do
+        it 'preloads associations correctly' do
+          posts = Post.accessible_by(ability).where(published: true).includes(likes: :user)
+          expect(posts[0].association(:likes)).to be_loaded
+          expect(posts[0].likes[0].association(:user)).to be_loaded
+        end
+      end
+
+      if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0')
+        describe 'selecting custom columns' do
+          it 'extracts custom columns correctly' do
+            posts = Post.accessible_by(ability).where(published: true).select('posts.title as mytitle')
+            expect(posts[0].mytitle).to eq 'post1'
+          end
+        end
+      end
+
+      describe 'filtering of results' do
+        it 'adds the where clause correctly' do
+          posts = Post.accessible_by(ability).where(published: true)
+          expect(posts.length).to eq 1
+        end
+      end
     end
   end
 
-  describe 'filtering of results' do
-    it 'adds the where clause correctly' do
-      posts = Post.accessible_by(ability).where(published: true)
-      expect(posts.length).to eq 1
+  unless CanCan::ModelAdapters::ActiveRecordAdapter.version_lower?('5.0.0')
+    describe 'filtering of results - subquery' do
+      before :each do
+        CanCan.accessible_by_strategy = :subquery
+      end
+
+      it 'adds the where clause correctly with joins' do
+        posts = Post.joins(:editors).where('editors.user_id': @user1.id).accessible_by(ability)
+        expect(posts.length).to eq 1
+      end
     end
   end
 
-  if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0')
-    describe 'selecting custom columns' do
-      it 'extracts custom columns correctly' do
-        posts = Post.accessible_by(ability).select('title as mytitle')
-        expect(posts[0].mytitle).to eq 'post1'
+  describe 'filtering of results - left_joins' do
+    before :each do
+      CanCan.accessible_by_strategy = :left_join
+    end
+
+    if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.2.0')
+      it 'adds the where clause correctly with joins on AR 5.2+' do
+        posts = Post.joins(:editors).where('editors.user_id': @user1.id).accessible_by(ability)
+        expect(posts.length).to eq 1
       end
     end
+
+    it 'adds the where clause correctly without joins' do
+      posts = Post.where('editors.user_id': @user1.id).accessible_by(ability)
+      expect(posts.length).to eq 1
+    end
   end
 end
diff --git a/spec/cancan/model_adapters/accessible_by_integration_spec.rb b/spec/cancan/model_adapters/accessible_by_integration_spec.rb
index f5a2ae9..dc58199 100644
--- a/spec/cancan/model_adapters/accessible_by_integration_spec.rb
+++ b/spec/cancan/model_adapters/accessible_by_integration_spec.rb
@@ -74,9 +74,9 @@ RSpec.describe CanCan::ModelAdapters::ActiveRecord5Adapter do
     ability.can :read, Post, editors: { user_id: @user1 }
   end
 
-  describe 'preloading of associatons' do
+  describe 'preloading of associations' do
     it 'preloads associations correctly' do
-      posts = Post.accessible_by(ability).includes(likes: :user)
+      posts = Post.accessible_by(ability).where(published: true).includes(likes: :user)
       expect(posts[0].association(:likes)).to be_loaded
       expect(posts[0].likes[0].association(:user)).to be_loaded
     end
@@ -92,7 +92,7 @@ RSpec.describe CanCan::ModelAdapters::ActiveRecord5Adapter do
   if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0')
     describe 'selecting custom columns' do
       it 'extracts custom columns correctly' do
-        posts = Post.accessible_by(ability).select('title as mytitle')
+        posts = Post.accessible_by(ability).where(published: true).select('title as mytitle')
         expect(posts[0].mytitle).to eq 'post1'
       end
     end
diff --git a/spec/cancan/model_adapters/active_record_4_adapter_spec.rb b/spec/cancan/model_adapters/active_record_4_adapter_spec.rb
index d238ce1..f439db5 100644
--- a/spec/cancan/model_adapters/active_record_4_adapter_spec.rb
+++ b/spec/cancan/model_adapters/active_record_4_adapter_spec.rb
@@ -4,148 +4,153 @@ require 'spec_helper'
 
 if CanCan::ModelAdapters::ActiveRecordAdapter.version_lower?('5.0.0')
   describe CanCan::ModelAdapters::ActiveRecord4Adapter do
-    context 'with sqlite3' do
-      before :each do
-        connect_db
-        ActiveRecord::Migration.verbose = false
-        ActiveRecord::Schema.define do
-          create_table(:parents) do |t|
-            t.timestamps null: false
+    # only the `left_join` strategy works in AR4
+    CanCan.valid_accessible_by_strategies.each do |strategy|
+      context "with sqlite3 and #{strategy} strategy" do
+        before :each do
+          CanCan.accessible_by_strategy = strategy
+
+          connect_db
+          ActiveRecord::Migration.verbose = false
+          ActiveRecord::Schema.define do
+            create_table(:parents) do |t|
+              t.timestamps null: false
+            end
+
+            create_table(:children) do |t|
+              t.timestamps null: false
+              t.integer :parent_id
+            end
           end
 
-          create_table(:children) do |t|
-            t.timestamps null: false
-            t.integer :parent_id
+          class Parent < ActiveRecord::Base
+            has_many :children, -> { order(id: :desc) }
           end
-        end
 
-        class Parent < ActiveRecord::Base
-          has_many :children, -> { order(id: :desc) }
-        end
+          class Child < ActiveRecord::Base
+            belongs_to :parent
+          end
 
-        class Child < ActiveRecord::Base
-          belongs_to :parent
+          (@ability = double).extend(CanCan::Ability)
         end
 
-        (@ability = double).extend(CanCan::Ability)
-      end
-
-      it 'respects scope on included associations' do
-        @ability.can :read, [Parent, Child]
+        it 'respects scope on included associations' do
+          @ability.can :read, [Parent, Child]
 
-        parent = Parent.create!
-        child1 = Child.create!(parent: parent, created_at: 1.hours.ago)
-        child2 = Child.create!(parent: parent, created_at: 2.hours.ago)
+          parent = Parent.create!
+          child1 = Child.create!(parent: parent, created_at: 1.hours.ago)
+          child2 = Child.create!(parent: parent, created_at: 2.hours.ago)
 
-        expect(Parent.accessible_by(@ability).order(created_at: :asc).includes(:children).first.children)
-          .to eq [child2, child1]
-      end
+          expect(Parent.accessible_by(@ability).order(created_at: :asc).includes(:children).first.children)
+            .to eq [child2, child1]
+        end
 
-      if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('4.1.0')
-        it 'allows filters on enums' do
-          ActiveRecord::Schema.define do
-            create_table(:shapes) do |t|
-              t.integer :color, default: 0, null: false
+        if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('4.1.0')
+          it 'allows filters on enums' do
+            ActiveRecord::Schema.define do
+              create_table(:shapes) do |t|
+                t.integer :color, default: 0, null: false
+              end
             end
-          end
 
-          class Shape < ActiveRecord::Base
-            enum color: %i[red green blue] unless defined_enums.key? 'color'
-          end
+            class Shape < ActiveRecord::Base
+              enum color: %i[red green blue] unless defined_enums.key? 'color'
+            end
 
-          red = Shape.create!(color: :red)
-          green = Shape.create!(color: :green)
-          blue = Shape.create!(color: :blue)
+            red = Shape.create!(color: :red)
+            green = Shape.create!(color: :green)
+            blue = Shape.create!(color: :blue)
 
-          # A condition with a single value.
-          @ability.can :read, Shape, color: Shape.colors[:green]
+            # A condition with a single value.
+            @ability.can :read, Shape, color: Shape.colors[:green]
 
-          expect(@ability.cannot?(:read, red)).to be true
-          expect(@ability.can?(:read, green)).to be true
-          expect(@ability.cannot?(:read, blue)).to be true
+            expect(@ability.cannot?(:read, red)).to be true
+            expect(@ability.can?(:read, green)).to be true
+            expect(@ability.cannot?(:read, blue)).to be true
 
-          accessible = Shape.accessible_by(@ability)
-          expect(accessible).to contain_exactly(green)
+            accessible = Shape.accessible_by(@ability)
+            expect(accessible).to contain_exactly(green)
 
-          # A condition with multiple values.
-          @ability.can :update, Shape, color: [Shape.colors[:red],
-                                               Shape.colors[:blue]]
+            # A condition with multiple values.
+            @ability.can :update, Shape, color: [Shape.colors[:red],
+                                                 Shape.colors[:blue]]
 
-          expect(@ability.can?(:update, red)).to be true
-          expect(@ability.cannot?(:update, green)).to be true
-          expect(@ability.can?(:update, blue)).to be true
+            expect(@ability.can?(:update, red)).to be true
+            expect(@ability.cannot?(:update, green)).to be true
+            expect(@ability.can?(:update, blue)).to be true
 
-          accessible = Shape.accessible_by(@ability, :update)
-          expect(accessible).to contain_exactly(red, blue)
-        end
+            accessible = Shape.accessible_by(@ability, :update)
+            expect(accessible).to contain_exactly(red, blue)
+          end
 
-        it 'allows dual filter on enums' do
-          ActiveRecord::Schema.define do
-            create_table(:discs) do |t|
-              t.integer :color, default: 0, null: false
-              t.integer :shape, default: 3, null: false
+          it 'allows dual filter on enums' do
+            ActiveRecord::Schema.define do
+              create_table(:discs) do |t|
+                t.integer :color, default: 0, null: false
+                t.integer :shape, default: 3, null: false
+              end
             end
-          end
 
-          class Disc < ActiveRecord::Base
-            enum color: %i[red green blue] unless defined_enums.key? 'color'
-            enum shape: { triangle: 3, rectangle: 4 } unless defined_enums.key? 'shape'
-          end
+            class Disc < ActiveRecord::Base
+              enum color: %i[red green blue] unless defined_enums.key? 'color'
+              enum shape: { triangle: 3, rectangle: 4 } unless defined_enums.key? 'shape'
+            end
 
-          red_triangle = Disc.create!(color: Disc.colors[:red], shape: Disc.shapes[:triangle])
-          green_triangle = Disc.create!(color: Disc.colors[:green], shape: Disc.shapes[:triangle])
-          green_rectangle = Disc.create!(color: Disc.colors[:green], shape: Disc.shapes[:rectangle])
-          blue_rectangle = Disc.create!(color: Disc.colors[:blue], shape: Disc.shapes[:rectangle])
+            red_triangle = Disc.create!(color: Disc.colors[:red], shape: Disc.shapes[:triangle])
+            green_triangle = Disc.create!(color: Disc.colors[:green], shape: Disc.shapes[:triangle])
+            green_rectangle = Disc.create!(color: Disc.colors[:green], shape: Disc.shapes[:rectangle])
+            blue_rectangle = Disc.create!(color: Disc.colors[:blue], shape: Disc.shapes[:rectangle])
 
-          # A condition with a dual filter.
-          @ability.can :read, Disc, color: Disc.colors[:green], shape: Disc.shapes[:rectangle]
+            # A condition with a dual filter.
+            @ability.can :read, Disc, color: Disc.colors[:green], shape: Disc.shapes[:rectangle]
 
-          expect(@ability.cannot?(:read, red_triangle)).to be true
-          expect(@ability.cannot?(:read, green_triangle)).to be true
-          expect(@ability.can?(:read, green_rectangle)).to be true
-          expect(@ability.cannot?(:read, blue_rectangle)).to be true
+            expect(@ability.cannot?(:read, red_triangle)).to be true
+            expect(@ability.cannot?(:read, green_triangle)).to be true
+            expect(@ability.can?(:read, green_rectangle)).to be true
+            expect(@ability.cannot?(:read, blue_rectangle)).to be true
 
-          accessible = Disc.accessible_by(@ability)
-          expect(accessible).to contain_exactly(green_rectangle)
+            accessible = Disc.accessible_by(@ability)
+            expect(accessible).to contain_exactly(green_rectangle)
+          end
         end
       end
-    end
 
-    context 'with postgresql' do
-      before :each do
-        connect_db
-        ActiveRecord::Migration.verbose = false
-        ActiveRecord::Schema.define do
-          create_table(:parents) do |t|
-            t.timestamps null: false
+      context 'with postgresql' do
+        before :each do
+          connect_db
+          ActiveRecord::Migration.verbose = false
+          ActiveRecord::Schema.define do
+            create_table(:parents) do |t|
+              t.timestamps null: false
+            end
+
+            create_table(:children) do |t|
+              t.timestamps null: false
+              t.integer :parent_id
+            end
           end
 
-          create_table(:children) do |t|
-            t.timestamps null: false
-            t.integer :parent_id
+          class Parent < ActiveRecord::Base
+            has_many :children, -> { order(id: :desc) }
           end
-        end
 
-        class Parent < ActiveRecord::Base
-          has_many :children, -> { order(id: :desc) }
-        end
+          class Child < ActiveRecord::Base
+            belongs_to :parent
+          end
 
-        class Child < ActiveRecord::Base
-          belongs_to :parent
+          (@ability = double).extend(CanCan::Ability)
         end
 
-        (@ability = double).extend(CanCan::Ability)
-      end
-
-      it 'allows overlapping conditions in SQL and merge with hash conditions' do
-        @ability.can :read, Parent, children: { parent_id: 1 }
-        @ability.can :read, Parent, children: { parent_id: 1 }
+        it 'allows overlapping conditions in SQL and merge with hash conditions' do
+          @ability.can :read, Parent, children: { parent_id: 1 }
+          @ability.can :read, Parent, children: { parent_id: 1 }
 
-        parent = Parent.create!
-        Child.create!(parent: parent, created_at: 1.hours.ago)
-        Child.create!(parent: parent, created_at: 2.hours.ago)
+          parent = Parent.create!
+          Child.create!(parent: parent, created_at: 1.hours.ago)
+          Child.create!(parent: parent, created_at: 2.hours.ago)
 
-        expect(Parent.accessible_by(@ability)).to eq([parent])
+          expect(Parent.accessible_by(@ability)).to eq([parent])
+        end
       end
     end
   end
diff --git a/spec/cancan/model_adapters/active_record_5_adapter_spec.rb b/spec/cancan/model_adapters/active_record_5_adapter_spec.rb
index fb0edd5..b721910 100644
--- a/spec/cancan/model_adapters/active_record_5_adapter_spec.rb
+++ b/spec/cancan/model_adapters/active_record_5_adapter_spec.rb
@@ -4,158 +4,162 @@ require 'spec_helper'
 
 if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0')
   describe CanCan::ModelAdapters::ActiveRecord5Adapter do
-    context 'with sqlite3' do
-      before :each do
-        connect_db
-        ActiveRecord::Migration.verbose = false
-
-        ActiveRecord::Schema.define do
-          create_table(:shapes) do |t|
-            t.integer :color, default: 0, null: false
-          end
+    CanCan.valid_accessible_by_strategies.each do |strategy|
+      context "with sqlite3 and #{strategy} strategy" do
+        before :each do
+          CanCan.accessible_by_strategy = strategy
 
-          create_table(:things) do |t|
-            t.string :size, default: 'big', null: false
-          end
+          connect_db
+          ActiveRecord::Migration.verbose = false
+
+          ActiveRecord::Schema.define do
+            create_table(:shapes) do |t|
+              t.integer :color, default: 0, null: false
+            end
 
-          create_table(:discs) do |t|
-            t.integer :color, default: 0, null: false
-            t.integer :shape, default: 3, null: false
+            create_table(:things) do |t|
+              t.string :size, default: 'big', null: false
+            end
+
+            create_table(:discs) do |t|
+              t.integer :color, default: 0, null: false
+              t.integer :shape, default: 3, null: false
+            end
           end
-        end
 
-        unless defined?(Thing)
-          class Thing < ActiveRecord::Base
-            enum size: { big: 'big', medium: 'average', small: 'small' }
+          unless defined?(Thing)
+            class Thing < ActiveRecord::Base
+              enum size: { big: 'big', medium: 'average', small: 'small' }
+            end
           end
-        end
 
-        unless defined?(Shape)
-          class Shape < ActiveRecord::Base
-            enum color: %i[red green blue]
+          unless defined?(Shape)
+            class Shape < ActiveRecord::Base
+              enum color: %i[red green blue]
+            end
           end
-        end
 
-        unless defined?(Disc)
-          class Disc < ActiveRecord::Base
-            enum color: %i[red green blue]
-            enum shape: { triangle: 3, rectangle: 4 }
+          unless defined?(Disc)
+            class Disc < ActiveRecord::Base
+              enum color: %i[red green blue]
+              enum shape: { triangle: 3, rectangle: 4 }
+            end
           end
         end
-      end
 
-      subject(:ability) { Ability.new(nil) }
+        subject(:ability) { Ability.new(nil) }
 
-      context 'when enums use integers as values' do
-        let(:red) { Shape.create!(color: :red) }
-        let(:green) { Shape.create!(color: :green) }
-        let(:blue) { Shape.create!(color: :blue) }
+        context 'when enums use integers as values' do
+          let(:red) { Shape.create!(color: :red) }
+          let(:green) { Shape.create!(color: :green) }
+          let(:blue) { Shape.create!(color: :blue) }
 
-        context 'when the condition contains a single value' do
-          before do
-            ability.can :read, Shape, color: :green
-          end
+          context 'when the condition contains a single value' do
+            before do
+              ability.can :read, Shape, color: :green
+            end
 
-          it 'can check ability on single models' do
-            is_expected.not_to be_able_to(:read, red)
-            is_expected.to be_able_to(:read, green)
-            is_expected.not_to be_able_to(:read, blue)
-          end
+            it 'can check ability on single models' do
+              is_expected.not_to be_able_to(:read, red)
+              is_expected.to be_able_to(:read, green)
+              is_expected.not_to be_able_to(:read, blue)
+            end
 
-          it 'can use accessible_by helper' do
-            accessible = Shape.accessible_by(ability)
-            expect(accessible).to contain_exactly(green)
+            it 'can use accessible_by helper' do
+              accessible = Shape.accessible_by(ability)
+              expect(accessible).to contain_exactly(green)
+            end
           end
-        end
 
-        context 'when the condition contains multiple values' do
-          before do
-            ability.can :update, Shape, color: %i[red blue]
-          end
+          context 'when the condition contains multiple values' do
+            before do
+              ability.can :update, Shape, color: %i[red blue]
+            end
 
-          it 'can check ability on single models' do
-            is_expected.to be_able_to(:update, red)
-            is_expected.not_to be_able_to(:update, green)
-            is_expected.to be_able_to(:update, blue)
-          end
+            it 'can check ability on single models' do
+              is_expected.to be_able_to(:update, red)
+              is_expected.not_to be_able_to(:update, green)
+              is_expected.to be_able_to(:update, blue)
+            end
 
-          it 'can use accessible_by helper' do
-            accessible = Shape.accessible_by(ability, :update)
-            expect(accessible).to contain_exactly(red, blue)
+            it 'can use accessible_by helper' do
+              accessible = Shape.accessible_by(ability, :update)
+              expect(accessible).to contain_exactly(red, blue)
+            end
           end
         end
-      end
 
-      context 'when enums use strings as values' do
-        let(:big) { Thing.create!(size: :big) }
-        let(:medium) { Thing.create!(size: :medium) }
-        let(:small) { Thing.create!(size: :small) }
+        context 'when enums use strings as values' do
+          let(:big) { Thing.create!(size: :big) }
+          let(:medium) { Thing.create!(size: :medium) }
+          let(:small) { Thing.create!(size: :small) }
 
-        context 'when the condition contains a single value' do
-          before do
-            ability.can :read, Thing, size: :medium
-          end
+          context 'when the condition contains a single value' do
+            before do
+              ability.can :read, Thing, size: :medium
+            end
 
-          it 'can check ability on single models' do
-            is_expected.not_to be_able_to(:read, big)
-            is_expected.to be_able_to(:read, medium)
-            is_expected.not_to be_able_to(:read, small)
-          end
+            it 'can check ability on single models' do
+              is_expected.not_to be_able_to(:read, big)
+              is_expected.to be_able_to(:read, medium)
+              is_expected.not_to be_able_to(:read, small)
+            end
 
-          it 'can use accessible_by helper' do
-            expect(Thing.accessible_by(ability)).to contain_exactly(medium)
+            it 'can use accessible_by helper' do
+              expect(Thing.accessible_by(ability)).to contain_exactly(medium)
+            end
+
+            context 'when a rule is overridden' do
+              before do
+                ability.cannot :read, Thing, size: 'average'
+              end
+
+              it 'is recognised correctly' do
+                is_expected.not_to be_able_to(:read, medium)
+                expect(Thing.accessible_by(ability)).to be_empty
+              end
+            end
           end
 
-          context 'when a rule is overriden' do
+          context 'when the condition contains multiple values' do
             before do
-              ability.cannot :read, Thing, size: 'average'
+              ability.can :update, Thing, size: %i[big small]
             end
 
-            it 'is recognised correctly' do
-              is_expected.not_to be_able_to(:read, medium)
-              expect(Thing.accessible_by(ability)).to be_empty
+            it 'can check ability on single models' do
+              is_expected.to be_able_to(:update, big)
+              is_expected.not_to be_able_to(:update, medium)
+              is_expected.to be_able_to(:update, small)
+            end
+
+            it 'can use accessible_by helper' do
+              expect(Thing.accessible_by(ability, :update)).to contain_exactly(big, small)
             end
           end
         end
 
-        context 'when the condition contains multiple values' do
+        context 'when multiple enums are present' do
+          let(:red_triangle) { Disc.create!(color: :red, shape: :triangle) }
+          let(:green_triangle) { Disc.create!(color: :green, shape: :triangle) }
+          let(:green_rectangle) { Disc.create!(color: :green, shape: :rectangle) }
+          let(:blue_rectangle) { Disc.create!(color: :blue, shape: :rectangle) }
+
           before do
-            ability.can :update, Thing, size: %i[big small]
+            ability.can :read, Disc, color: :green, shape: :rectangle
           end
 
           it 'can check ability on single models' do
-            is_expected.to be_able_to(:update, big)
-            is_expected.not_to be_able_to(:update, medium)
-            is_expected.to be_able_to(:update, small)
+            is_expected.not_to be_able_to(:read, red_triangle)
+            is_expected.not_to be_able_to(:read, green_triangle)
+            is_expected.to be_able_to(:read, green_rectangle)
+            is_expected.not_to be_able_to(:read, blue_rectangle)
           end
 
           it 'can use accessible_by helper' do
-            expect(Thing.accessible_by(ability, :update)).to contain_exactly(big, small)
+            expect(Disc.accessible_by(ability)).to contain_exactly(green_rectangle)
           end
         end
       end
-
-      context 'when multiple enums are present' do
-        let(:red_triangle) { Disc.create!(color: :red, shape: :triangle) }
-        let(:green_triangle) { Disc.create!(color: :green, shape: :triangle) }
-        let(:green_rectangle) { Disc.create!(color: :green, shape: :rectangle) }
-        let(:blue_rectangle) { Disc.create!(color: :blue, shape: :rectangle) }
-
-        before do
-          ability.can :read, Disc, color: :green, shape: :rectangle
-        end
-
-        it 'can check ability on single models' do
-          is_expected.not_to be_able_to(:read, red_triangle)
-          is_expected.not_to be_able_to(:read, green_triangle)
-          is_expected.to be_able_to(:read, green_rectangle)
-          is_expected.not_to be_able_to(:read, blue_rectangle)
-        end
-
-        it 'can use accessible_by helper' do
-          expect(Disc.accessible_by(ability)).to contain_exactly(green_rectangle)
-        end
-      end
     end
   end
 end
diff --git a/spec/cancan/model_adapters/active_record_adapter_spec.rb b/spec/cancan/model_adapters/active_record_adapter_spec.rb
index e569104..810af59 100644
--- a/spec/cancan/model_adapters/active_record_adapter_spec.rb
+++ b/spec/cancan/model_adapters/active_record_adapter_spec.rb
@@ -2,7 +2,7 @@
 
 require 'spec_helper'
 
-describe CanCan::ModelAdapters::ActiveRecordAdapter do
+RSpec.describe CanCan::ModelAdapters::ActiveRecordAdapter do
   let(:true_v) do
     ActiveRecord::Base.connection.quoted_true
   end
@@ -49,12 +49,23 @@ describe CanCan::ModelAdapters::ActiveRecordAdapter do
         t.timestamps null: false
       end
 
+      create_table(:legacy_comments) do |t|
+        t.integer :post_id
+        t.timestamps null: false
+      end
+
       create_table(:legacy_mentions) do |t|
         t.integer :user_id
         t.integer :article_id
         t.timestamps null: false
       end
 
+      create_table(:attachments) do |t|
+        t.integer :attachable_id
+        t.string :attachable_type
+        t.timestamps null: false
+      end
+
       create_table(:users) do |t|
         t.string :name
         t.timestamps null: false
@@ -62,6 +73,7 @@ describe CanCan::ModelAdapters::ActiveRecordAdapter do
     end
 
     class Project < ActiveRecord::Base
+      has_many :articles
       has_many :comments
     end
 
@@ -80,6 +92,13 @@ describe CanCan::ModelAdapters::ActiveRecordAdapter do
       has_many :mentioned_users, through: :mentions, source: :user
       belongs_to :user
       belongs_to :project
+      has_many :attachments, as: :attachable
+
+      scope :unpopular, lambda {
+        joins('LEFT OUTER JOIN comments ON (comments.post_id = posts.id)')
+          .group('articles.id')
+          .where('COUNT(comments.id) < 3')
+      }
     end
 
     class Mention < ActiveRecord::Base
@@ -90,12 +109,24 @@ describe CanCan::ModelAdapters::ActiveRecordAdapter do
 
     class Comment < ActiveRecord::Base
       belongs_to :article
+      belongs_to :project
+      has_many :attachments, as: :attachable
+    end
+
+    class LegacyComment < ActiveRecord::Base
+      belongs_to :article, foreign_key: 'post_id'
+      belongs_to :project
+    end
+
+    class Attachment < ActiveRecord::Base
+      belongs_to :attachable, polymorphic: true
     end
 
     class User < ActiveRecord::Base
       has_many :articles
       has_many :mentions
       has_many :mentioned_articles, through: :mentions, source: :article
+      has_many :comments, through: :articles
     end
 
     (@ability = double).extend(CanCan::Ability)
@@ -103,312 +134,751 @@ describe CanCan::ModelAdapters::ActiveRecordAdapter do
     @comment_table = Comment.table_name
   end
 
-  it 'is for only active record classes' do
-    if ActiveRecord.version > Gem::Version.new('5')
-      expect(CanCan::ModelAdapters::ActiveRecord5Adapter).to_not be_for_class(Object)
-      expect(CanCan::ModelAdapters::ActiveRecord5Adapter).to be_for_class(Article)
-      expect(CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article))
-        .to eq(CanCan::ModelAdapters::ActiveRecord5Adapter)
-    elsif ActiveRecord.version > Gem::Version.new('4')
-      expect(CanCan::ModelAdapters::ActiveRecord4Adapter).to_not be_for_class(Object)
-      expect(CanCan::ModelAdapters::ActiveRecord4Adapter).to be_for_class(Article)
-      expect(CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article))
-        .to eq(CanCan::ModelAdapters::ActiveRecord4Adapter)
+  CanCan.valid_accessible_by_strategies.each do |strategy|
+    context "base functionality with #{strategy} strategy" do
+      before :each do
+        CanCan.accessible_by_strategy = strategy
+      end
+
+      it 'does not fires query with accessible_by() for abilities defined with association' do
+        user = User.create!
+        @ability.can :edit, Article, user.articles.unpopular
+        callback = ->(*) { raise 'No query expected' }
+
+        ActiveSupport::Notifications.subscribed(callback, 'sql.active_record') do
+          Article.accessible_by(@ability, :edit)
+          nil
+        end
+      end
+
+      it 'fetches only the articles that are published' do
+        @ability.can :read, Article, published: true
+        article1 = Article.create!(published: true)
+        Article.create!(published: false)
+        expect(Article.accessible_by(@ability)).to eq([article1])
+      end
+
+      it 'is for only active record classes' do
+        if ActiveRecord.version > Gem::Version.new('5')
+          expect(CanCan::ModelAdapters::ActiveRecord5Adapter).to_not be_for_class(Object)
+          expect(CanCan::ModelAdapters::ActiveRecord5Adapter).to be_for_class(Article)
+          expect(CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article))
+            .to eq(CanCan::ModelAdapters::ActiveRecord5Adapter)
+        elsif ActiveRecord.version > Gem::Version.new('4')
+          expect(CanCan::ModelAdapters::ActiveRecord4Adapter).to_not be_for_class(Object)
+          expect(CanCan::ModelAdapters::ActiveRecord4Adapter).to be_for_class(Article)
+          expect(CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article))
+            .to eq(CanCan::ModelAdapters::ActiveRecord4Adapter)
+        end
+      end
+
+      it 'finds record' do
+        article = Article.create!
+        adapter = CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article)
+        expect(adapter.find(Article, article.id)).to eq(article)
+      end
+
+      it 'does not fetch any records when no abilities are defined' do
+        Article.create!
+        expect(Article.accessible_by(@ability)).to be_empty
+      end
+
+      it 'fetches all articles when one can read all' do
+        @ability.can :read, Article
+        article = Article.create!
+        expect(Article.accessible_by(@ability)).to match_array([article])
+      end
+
+      it 'fetches only the articles that are published' do
+        @ability.can :read, Article, published: true
+        article1 = Article.create!(published: true)
+        Article.create!(published: false)
+        expect(Article.accessible_by(@ability)).to match_array([article1])
+      end
+
+      it 'fetches any articles which are published or secret' do
+        @ability.can :read, Article, published: true
+        @ability.can :read, Article, secret: true
+        article1 = Article.create!(published: true, secret: false)
+        article2 = Article.create!(published: true, secret: true)
+        article3 = Article.create!(published: false, secret: true)
+        Article.create!(published: false, secret: false)
+        expect(Article.accessible_by(@ability)).to match_array([article1, article2, article3])
+      end
+
+      it 'fetches any articles which we are cited in' do
+        user = User.create!
+        cited = Article.create!
+        Article.create!
+        cited.mentioned_users << user
+        @ability.can :read, Article, mentioned_users: { id: user.id }
+        @ability.can :read, Article, mentions: { user_id: user.id }
+        expect(Article.accessible_by(@ability)).to match_array([cited])
+      end
+
+      it 'fetches only the articles that are published and not secret' do
+        @ability.can :read, Article, published: true
+        @ability.cannot :read, Article, secret: true
+        article1 = Article.create!(published: true, secret: false)
+        Article.create!(published: true, secret: true)
+        Article.create!(published: false, secret: true)
+        Article.create!(published: false, secret: false)
+        expect(Article.accessible_by(@ability)).to match_array([article1])
+      end
+
+      it 'only reads comments for articles which are published' do
+        @ability.can :read, Comment, article: { published: true }
+        comment1 = Comment.create!(article: Article.create!(published: true))
+        Comment.create!(article: Article.create!(published: false))
+        expect(Comment.accessible_by(@ability)).to match_array([comment1])
+      end
+
+      it 'should only read articles which are published or in visible categories' do
+        @ability.can :read, Article, category: { visible: true }
+        @ability.can :read, Article, published: true
+        article1 = Article.create!(published: true)
+        Article.create!(published: false)
+        article3 = Article.create!(published: false, category: Category.create!(visible: true))
+        expect(Article.accessible_by(@ability)).to match_array([article1, article3])
+      end
+
+      it 'should only read categories once even if they have multiple articles' do
+        @ability.can :read, Category, articles: { published: true }
+        @ability.can :read, Article, published: true
+        category = Category.create!
+        Article.create!(published: true, category: category)
+        Article.create!(published: true, category: category)
+        expect(Category.accessible_by(@ability)).to match_array([category])
+      end
+
+      it 'only reads comments for visible categories through articles' do
+        @ability.can :read, Comment, article: { category: { visible: true } }
+        comment1 = Comment.create!(article: Article.create!(category: Category.create!(visible: true)))
+        Comment.create!(article: Article.create!(category: Category.create!(visible: false)))
+        expect(Comment.accessible_by(@ability)).to match_array([comment1])
+        expect(Comment.accessible_by(@ability).count).to eq(1)
+      end
+
+      it 'allows conditions in SQL and merge with hash conditions' do
+        @ability.can :read, Article, published: true
+        @ability.can :read, Article, ['secret=?', true]
+        article1 = Article.create!(published: true, secret: false)
+        article2 = Article.create!(published: true, secret: true)
+        article3 = Article.create!(published: false, secret: true)
+        Article.create!(published: false, secret: false)
+        expect(Article.accessible_by(@ability)).to match_array([article1, article2, article3])
+      end
+
+      it 'allows a scope for conditions' do
+        @ability.can :read, Article, Article.where(secret: true)
+        article1 = Article.create!(secret: true)
+        Article.create!(secret: false)
+        expect(Article.accessible_by(@ability)).to match_array([article1])
+      end
+
+      it 'fetches only associated records when using with a scope for conditions' do
+        @ability.can :read, Article, Article.where(secret: true)
+        category1 = Category.create!(visible: false)
+        category2 = Category.create!(visible: true)
+        article1 = Article.create!(secret: true, category: category1)
+        Article.create!(secret: true, category: category2)
+        expect(category1.articles.accessible_by(@ability)).to match_array([article1])
+      end
+
+      it 'raises an exception when trying to merge scope with other conditions' do
+        @ability.can :read, Article, published: true
+        @ability.can :read, Article, Article.where(secret: true)
+        expect { Article.accessible_by(@ability) }
+          .to raise_error(CanCan::Error,
+                          'Unable to merge an Active Record scope with other conditions. ' \
+                            'Instead use a hash or SQL for read Article ability.')
+      end
+
+      it 'does not raise an exception when the rule with scope is suppressed' do
+        @ability.can :read, Article, published: true
+        @ability.can :read, Article, Article.where(secret: true)
+        @ability.cannot :read, Article
+        expect { Article.accessible_by(@ability) }.not_to raise_error
+      end
+
+      it 'recognises empty scopes and compresses them' do
+        @ability.can :read, Article, published: true
+        @ability.can :read, Article, Article.all
+        expect { Article.accessible_by(@ability) }.not_to raise_error
+      end
+
+      it 'does not allow to fetch records when ability with just block present' do
+        @ability.can :read, Article do
+          false
+        end
+        expect { Article.accessible_by(@ability) }.to raise_error(CanCan::Error)
+      end
+
+      it 'should support more than one deeply nested conditions' do
+        @ability.can :read, Comment, article: {
+          category: {
+            name: 'foo', visible: true
+          }
+        }
+        expect { Comment.accessible_by(@ability) }.to_not raise_error
+      end
+
+      it 'does not allow to check ability on object against SQL conditions without block' do
+        @ability.can :read, Article, ['secret=?', true]
+        expect { @ability.can? :read, Article.new }.to raise_error(CanCan::Error)
+      end
+
+      it 'has false conditions if no abilities match' do
+        expect(@ability.model_adapter(Article, :read).conditions).to eq(false_condition)
+      end
+
+      it 'returns false conditions for cannot clause' do
+        @ability.cannot :read, Article
+        expect(@ability.model_adapter(Article, :read).conditions).to eq(false_condition)
+      end
+
+      it 'returns SQL for single `can` definition in front of default `cannot` condition' do
+        @ability.cannot :read, Article
+        @ability.can :read, Article, published: false, secret: true
+        expect(@ability.model_adapter(Article, :read)).to generate_sql(%(
+    SELECT "articles".*
+    FROM "articles"
+    WHERE "articles"."published" = #{false_v} AND "articles"."secret" = #{true_v}))
+      end
+
+      it 'returns true condition for single `can` definition in front of default `can` condition' do
+        @ability.can :read, Article
+        @ability.can :read, Article, published: false, secret: true
+        expect(@ability.model_adapter(Article, :read).conditions).to eq({})
+        expect(@ability.model_adapter(Article, :read)).to generate_sql(%(SELECT "articles".* FROM "articles"))
+      end
+
+      it 'returns `false condition` for single `cannot` definition in front of default `cannot` condition' do
+        @ability.cannot :read, Article
+        @ability.cannot :read, Article, published: false, secret: true
+        expect(@ability.model_adapter(Article, :read).conditions).to eq(false_condition)
+      end
+
+      it 'returns `not (sql)` for single `cannot` definition in front of default `can` condition' do
+        @ability.can :read, Article
+        @ability.cannot :read, Article, published: false, secret: true
+        expect(@ability.model_adapter(Article, :read).conditions)
+          .to orderlessly_match(
+            %["not (#{@article_table}"."published" = #{false_v} AND "#{@article_table}"."secret" = #{true_v})]
+          )
+      end
+
+      it 'returns appropriate sql conditions in complex case' do
+        @ability.can :read, Article
+        @ability.can :manage, Article, id: 1
+        @ability.can :update, Article, published: true
+        @ability.cannot :update, Article, secret: true
+        expect(@ability.model_adapter(Article, :update).conditions)
+          .to eq(%[not ("#{@article_table}"."secret" = #{true_v}) ] +
+                   %[AND (("#{@article_table}"."published" = #{true_v}) ] +
+                   %[OR ("#{@article_table}"."id" = 1))])
+        expect(@ability.model_adapter(Article, :manage).conditions).to eq(id: 1)
+        expect(@ability.model_adapter(Article, :read).conditions).to eq({})
+        expect(@ability.model_adapter(Article, :read)).to generate_sql(%(SELECT "articles".* FROM "articles"))
+      end
+
+      it 'returns appropriate sql conditions in complex case with nested joins' do
+        @ability.can :read, Comment, article: { category: { visible: true } }
+        expect(@ability.model_adapter(Comment, :read).conditions).to eq(Category.table_name.to_sym => { visible: true })
+      end
+
+      it 'returns appropriate sql conditions in complex case with nested joins of different depth' do
+        @ability.can :read, Comment, article: { published: true, category: { visible: true } }
+        expect(@ability.model_adapter(Comment, :read).conditions)
+          .to eq(Article.table_name.to_sym => { published: true }, Category.table_name.to_sym => { visible: true })
+      end
+
+      it 'does not forget conditions when calling with SQL string' do
+        @ability.can :read, Article, published: true
+        @ability.can :read, Article, ['secret = ?', false]
+        adapter = @ability.model_adapter(Article, :read)
+        2.times do
+          expect(adapter.conditions).to eq(%[(secret = #{false_v}) OR ("#{@article_table}"."published" = #{true_v})])
+        end
+      end
+
+      it 'has nil joins if no rules' do
+        expect(@ability.model_adapter(Article, :read).joins).to be_nil
+      end
+
+      context 'if rules got compressed' do
+        it 'has nil joins' do
+          @ability.can :read, Comment, article: { category: { visible: true } }
+          @ability.can :read, Comment
+          expect(@ability.model_adapter(Comment, :read))
+            .to generate_sql("SELECT \"#{@comment_table}\".* FROM \"#{@comment_table}\"")
+          expect(@ability.model_adapter(Comment, :read).joins).to be_nil
+        end
+      end
+
+      context 'if rules did not get compressed' do
+        before :each do
+          CanCan.rules_compressor_enabled = false
+        end
+
+        it 'has joins' do
+          @ability.can :read, Comment, article: { category: { visible: true } }
+          @ability.can :read, Comment
+          expect(@ability.model_adapter(Comment, :read).joins).to be_present
+        end
+      end
+
+      it 'has nil joins if no nested hashes specified in conditions' do
+        @ability.can :read, Article, published: false
+        @ability.can :read, Article, secret: true
+        expect(@ability.model_adapter(Article, :read).joins).to be_nil
+      end
+
+      it 'merges separate joins into a single array' do
+        @ability.can :read, Article, project: { blocked: false }
+        @ability.can :read, Article, company: { admin: true }
+        expect(@ability.model_adapter(Article, :read).joins.inspect).to orderlessly_match(%i[company project].inspect)
+      end
+
+      it 'merges same joins into a single array' do
+        @ability.can :read, Article, project: { blocked: false }
+        @ability.can :read, Article, project: { admin: true }
+        expect(@ability.model_adapter(Article, :read).joins).to eq([:project])
+      end
+
+      it 'merges nested and non-nested joins' do
+        @ability.can :read, Article, project: { blocked: false }
+        @ability.can :read, Article, project: { comments: { spam: true } }
+        expect(@ability.model_adapter(Article, :read).joins).to eq([{ project: [:comments] }])
+      end
+
+      it 'merges :all conditions with other conditions' do
+        user = User.create!
+        article = Article.create!(user: user)
+        ability = Ability.new(user)
+        ability.can :manage, :all
+        ability.can :manage, Article, user_id: user.id
+        expect(Article.accessible_by(ability)).to eq([article])
+      end
+
+      it 'should not execute a scope when checking ability on the class' do
+        relation = Article.where(secret: true)
+        @ability.can :read, Article, relation do |article|
+          article.secret == true
+        end
+
+        allow(relation).to receive(:count).and_raise('Unexpected scope execution.')
+
+        expect { @ability.can? :read, Article }.not_to raise_error
+      end
+
+      it 'should ignore cannot rules with attributes when querying' do
+        user = User.create!
+        article = Article.create!(user: user)
+        ability = Ability.new(user)
+        ability.can :read, Article
+        ability.cannot :read, Article, :secret
+        expect(Article.accessible_by(ability)).to eq([article])
+      end
+
+      describe 'when can? is used with a Hash (nested resources)' do
+        it 'verifies parent equality correctly' do
+          user1 = User.create!(name: 'user1')
+          user2 = User.create!(name: 'user2')
+          category = Category.create!(name: 'cat')
+          article1 = Article.create!(name: 'article1', category: category, user: user1)
+          article2 = Article.create!(name: 'article2', category: category, user: user2)
+          comment1 = Comment.create!(article: article1)
+          comment2 = Comment.create!(article: article2)
+
+          ability1 = Ability.new(user1)
+          ability1.can :read, Article
+          ability1.can :manage, Article, user: user1
+          ability1.can :manage, Comment, article: user1.articles
+
+          expect(ability1.can?(:manage, { article1 => Comment })).to eq(true)
+          expect(ability1.can?(:manage, { article2 => Comment })).to eq(false)
+          expect(ability1.can?(:manage, { article1 => comment1 })).to eq(true)
+          expect(ability1.can?(:manage, { article2 => comment2 })).to eq(false)
+
+          ability2 = Ability.new(user2)
+
+          expect(ability2.can?(:manage, { article1 => Comment })).to eq(false)
+          expect(ability2.can?(:manage, { article2 => Comment })).to eq(false)
+          expect(ability2.can?(:manage, { article1 => comment1 })).to eq(false)
+          expect(ability2.can?(:manage, { article2 => comment2 })).to eq(false)
+        end
+      end
     end
   end
 
-  it 'finds record' do
-    article = Article.create!
-    adapter = CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article)
-    expect(adapter.find(Article, article.id)).to eq(article)
-  end
+  describe 'when can? is used with a Hash (nested resources)' do
+    let(:user1) { User.create!(name: 'user1') }
+    let(:user2) { User.create!(name: 'user2') }
 
-  it 'does not fetch any records when no abilities are defined' do
-    Article.create!
-    expect(Article.accessible_by(@ability)).to be_empty
-  end
+    before do
+      category = Category.create!(name: 'category')
+      @article1 = Article.create!(name: 'article1', category: category, user: user1)
+      @article2 = Article.create!(name: 'article2', category: category, user: user2)
+      @comment1 = Comment.create!(article: @article1)
+      @comment2 = Comment.create!(article: @article2)
+      @legacy_comment1 = LegacyComment.create!(article: @article1)
+      @legacy_comment2 = LegacyComment.create!(article: @article2)
+      @attachment1 = Attachment.create!(attachable: @article1)
+      @comment_attachment1 = Attachment.create!(attachable: @comment1)
+      @attachment2 = Attachment.create!(attachable: @article2)
+      @comment_attachment2 = Attachment.create!(attachable: @comment2)
+    end
 
-  it 'fetches all articles when one can read all' do
-    @ability.can :read, Article
-    article = Article.create!
-    expect(Article.accessible_by(@ability)).to match_array([article])
-  end
+    context 'when conditions are defined using the parent model' do
+      let(:ability) do
+        Ability.new(user1).tap do |ability|
+          ability.can :read, Article
+          ability.can :manage, Article, user: user1
+          ability.can :manage, Comment, article: user1.articles
+          ability.can :manage, LegacyComment, article: user1.articles
+          ability.can :manage, Attachment, attachable: user1.articles
+          ability.can :manage, Attachment, attachable: user1.comments
+        end
+      end
 
-  it 'fetches only the articles that are published' do
-    @ability.can :read, Article, published: true
-    article1 = Article.create!(published: true)
-    Article.create!(published: false)
-    expect(Article.accessible_by(@ability)).to match_array([article1])
-  end
+      it 'verifies parent equality correctly' do
+        expect(ability.can?(:manage, { @article1 => Comment })).to eq(true)
+        expect(ability.can?(:manage, { @article1 => LegacyComment })).to eq(true)
+        expect(ability.can?(:manage, { @article1 => @comment1 })).to eq(true)
+        expect(ability.can?(:manage, { @article1 => @legacy_comment1 })).to eq(true)
 
-  it 'fetches any articles which are published or secret' do
-    @ability.can :read, Article, published: true
-    @ability.can :read, Article, secret: true
-    article1 = Article.create!(published: true, secret: false)
-    article2 = Article.create!(published: true, secret: true)
-    article3 = Article.create!(published: false, secret: true)
-    Article.create!(published: false, secret: false)
-    expect(Article.accessible_by(@ability)).to match_array([article1, article2, article3])
-  end
+        expect(ability.can?(:manage, { @article2 => Comment })).to eq(false)
+        expect(ability.can?(:manage, { @article2 => LegacyComment })).to eq(false)
+        expect(ability.can?(:manage, { @article2 => @legacy_comment2 })).to eq(false)
+      end
 
-  it 'fetches any articles which we are cited in' do
-    user = User.create!
-    cited = Article.create!
-    Article.create!
-    cited.mentioned_users << user
-    @ability.can :read, Article, mentioned_users: { id: user.id }
-    @ability.can :read, Article, mentions: { user_id: user.id }
-    expect(Article.accessible_by(@ability)).to match_array([cited])
-  end
+      context 'when the association is polymorphic' do
+        it 'verifies parent equality correctly' do
+          expect(ability.can?(:manage, { @article1 => Attachment })).to eq(true)
+          expect(ability.can?(:manage, { @article1 => @attachment1 })).to eq(true)
+          expect(ability.can?(:manage, { @comment1 => Attachment })).to eq(true)
+          expect(ability.can?(:manage, { @comment1 => @comment_attachment1 })).to eq(true)
+
+          expect(ability.can?(:manage, { @article2 => Attachment })).to eq(false)
+          expect(ability.can?(:manage, { @article2 => @attachment2 })).to eq(false)
+          expect(ability.can?(:manage, { @comment2 => Attachment })).to eq(false)
+          expect(ability.can?(:manage, { @comment2 => @comment_attachment2 })).to eq(false)
+        end
+      end
+    end
 
-  it 'fetches only the articles that are published and not secret' do
-    @ability.can :read, Article, published: true
-    @ability.cannot :read, Article, secret: true
-    article1 = Article.create!(published: true, secret: false)
-    Article.create!(published: true, secret: true)
-    Article.create!(published: false, secret: true)
-    Article.create!(published: false, secret: false)
-    expect(Article.accessible_by(@ability)).to match_array([article1])
-  end
+    context 'when conditions are defined using the parent id' do
+      let(:ability) do
+        Ability.new(user1).tap do |ability|
+          ability.can :read, Article
+          ability.can :manage, Article, user_id: user1.id
+          ability.can :manage, Comment, article_id: user1.article_ids
+          ability.can :manage, LegacyComment, post_id: user1.article_ids
+          ability.can :manage, Attachment, attachable_id: user1.article_ids
+        end
+      end
 
-  it 'only reads comments for articles which are published' do
-    @ability.can :read, Comment, article: { published: true }
-    comment1 = Comment.create!(article: Article.create!(published: true))
-    Comment.create!(article: Article.create!(published: false))
-    expect(Comment.accessible_by(@ability)).to match_array([comment1])
-  end
+      it 'verifies parent equality correctly' do
+        expect(ability.can?(:manage, { @article1 => Comment })).to eq(true)
+        expect(ability.can?(:manage, { @article1 => LegacyComment })).to eq(true)
+        expect(ability.can?(:manage, { @article1 => @comment1 })).to eq(true)
+        expect(ability.can?(:manage, { @article1 => @legacy_comment1 })).to eq(true)
 
-  it 'should only read articles which are published or in visible categories' do
-    @ability.can :read, Article, category: { visible: true }
-    @ability.can :read, Article, published: true
-    article1 = Article.create!(published: true)
-    Article.create!(published: false)
-    article3 = Article.create!(published: false, category: Category.create!(visible: true))
-    expect(Article.accessible_by(@ability)).to match_array([article1, article3])
-  end
+        expect(ability.can?(:manage, { @article2 => Comment })).to eq(false)
+        expect(ability.can?(:manage, { @article2 => LegacyComment })).to eq(false)
+        expect(ability.can?(:manage, { @article2 => @legacy_comment2 })).to eq(false)
+      end
 
-  it 'should only read categories once even if they have multiple articles' do
-    @ability.can :read, Category, articles: { published: true }
-    @ability.can :read, Article, published: true
-    category = Category.create!
-    Article.create!(published: true, category: category)
-    Article.create!(published: true, category: category)
-    expect(Category.accessible_by(@ability)).to match_array([category])
+      context 'when the association is polymorphic' do
+        it 'verifies parent equality correctly' do
+          expect(ability.can?(:manage, { @article1 => Attachment })).to eq(true)
+          expect(ability.can?(:manage, { @article1 => @attachment1 })).to eq(true)
+          expect(ability.can?(:manage, { @comment1 => Attachment })).to eq(true)
+          expect(ability.can?(:manage, { @comment1 => @comment_attachment1 })).to eq(true)
+
+          expect(ability.can?(:manage, { @article2 => Attachment })).to eq(false)
+          expect(ability.can?(:manage, { @article2 => @attachment2 })).to eq(false)
+          expect(ability.can?(:manage, { @comment2 => Attachment })).to eq(false)
+          expect(ability.can?(:manage, { @comment2 => @comment_attachment2 })).to eq(false)
+        end
+      end
+    end
   end
 
-  it 'only reads comments for visible categories through articles' do
-    @ability.can :read, Comment, article: { category: { visible: true } }
-    comment1 = Comment.create!(article: Article.create!(category: Category.create!(visible: true)))
-    Comment.create!(article: Article.create!(category: Category.create!(visible: false)))
-    expect(Comment.accessible_by(@ability)).to match_array([comment1])
-  end
+  unless CanCan::ModelAdapters::ActiveRecordAdapter.version_lower?('5.0.0')
+    context 'base behaviour subquery specific' do
+      before :each do
+        CanCan.accessible_by_strategy = :subquery
+      end
 
-  it 'allows conditions in SQL and merge with hash conditions' do
-    @ability.can :read, Article, published: true
-    @ability.can :read, Article, ['secret=?', true]
-    article1 = Article.create!(published: true, secret: false)
-    article2 = Article.create!(published: true, secret: true)
-    article3 = Article.create!(published: false, secret: true)
-    Article.create!(published: false, secret: false)
-    expect(Article.accessible_by(@ability)).to match_array([article1, article2, article3])
-  end
+      it 'allows ordering via relations' do
+        @ability.can :read, Comment, article: { category: { visible: true } }
+        comment1 = Comment.create!(article: Article.create!(name: 'B', category: Category.create!(visible: true)))
+        comment2 = Comment.create!(article: Article.create!(name: 'A', category: Category.create!(visible: true)))
+        Comment.create!(article: Article.create!(category: Category.create!(visible: false)))
+
+        # doesn't work without explicitly calling a join on AR 5+,
+        # but does before that (where we don't use subqueries at all)
+        if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0')
+          expect { Comment.accessible_by(@ability).order('articles.name').to_a }
+            .to raise_error(ActiveRecord::StatementInvalid)
+        else
+          expect(Comment.accessible_by(@ability).order('articles.name'))
+            .to match_array([comment2, comment1])
+        end
 
-  it 'allows a scope for conditions' do
-    @ability.can :read, Article, Article.where(secret: true)
-    article1 = Article.create!(secret: true)
-    Article.create!(secret: false)
-    expect(Article.accessible_by(@ability)).to match_array([article1])
+        # works with the explicit join
+        expect(Comment.accessible_by(@ability).joins(:article).order('articles.name'))
+          .to match_array([comment2, comment1])
+      end
+    end
   end
 
-  it 'fetches only associated records when using with a scope for conditions' do
-    @ability.can :read, Article, Article.where(secret: true)
-    category1 = Category.create!(visible: false)
-    category2 = Category.create!(visible: true)
-    article1 = Article.create!(secret: true, category: category1)
-    Article.create!(secret: true, category: category2)
-    expect(category1.articles.accessible_by(@ability)).to match_array([article1])
-  end
+  context 'base behaviour left_join specific' do
+    before :each do
+      CanCan.accessible_by_strategy = :left_join
+    end
 
-  it 'raises an exception when trying to merge scope with other conditions' do
-    @ability.can :read, Article, published: true
-    @ability.can :read, Article, Article.where(secret: true)
-    expect(-> { Article.accessible_by(@ability) })
-      .to raise_error(CanCan::Error,
-                      'Unable to merge an Active Record scope with other conditions. '\
-                        'Instead use a hash or SQL for read Article ability.')
-  end
+    it 'allows ordering via relations in sqlite' do
+      skip unless sqlite?
+
+      @ability.can :read, Comment, article: { category: { visible: true } }
+      comment1 = Comment.create!(article: Article.create!(name: 'B', category: Category.create!(visible: true)))
+      comment2 = Comment.create!(article: Article.create!(name: 'A', category: Category.create!(visible: true)))
+      Comment.create!(article: Article.create!(category: Category.create!(visible: false)))
+
+      # works without explicitly calling a join
+      expect(Comment.accessible_by(@ability).order('articles.name')).to match_array([comment2, comment1])
+
+      # works with the explicit join in AR 5.2+ and AR 4.2
+      if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.2.0')
+        expect(Comment.accessible_by(@ability).joins(:article).order('articles.name'))
+          .to match_array([comment2, comment1])
+      elsif CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0')
+        expect { Comment.accessible_by(@ability).joins(:article).order('articles.name').to_a }
+          .to raise_error(ActiveRecord::StatementInvalid)
+      else
+        expect(Comment.accessible_by(@ability).joins(:article).order('articles.name'))
+          .to match_array([comment2, comment1])
+      end
+    end
 
-  it 'does not raise an exception when the rule with scope is suppressed' do
-    @ability.can :read, Article, published: true
-    @ability.can :read, Article, Article.where(secret: true)
-    @ability.cannot :read, Article
-    expect(-> { Article.accessible_by(@ability) }).not_to raise_error
-  end
+    # this fails on Postgres. see https://github.com/CanCanCommunity/cancancan/pull/608
+    it 'fails to order via relations in postgres on AR 5+' do
+      skip unless postgres?
 
-  it 'recognises empty scopes and compresses them' do
-    @ability.can :read, Article, published: true
-    @ability.can :read, Article, Article.all
-    expect(-> { Article.accessible_by(@ability) }).not_to raise_error
-  end
+      @ability.can :read, Comment, article: { category: { visible: true } }
+      comment1 = Comment.create!(article: Article.create!(name: 'B', category: Category.create!(visible: true)))
+      comment2 = Comment.create!(article: Article.create!(name: 'A', category: Category.create!(visible: true)))
+      Comment.create!(article: Article.create!(category: Category.create!(visible: false)))
 
-  it 'does not allow to fetch records when ability with just block present' do
-    @ability.can :read, Article do
-      false
+      if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0')
+        # doesn't work with or without the join
+        expect { Comment.accessible_by(@ability).order('articles.name').to_a }
+          .to raise_error(ActiveRecord::StatementInvalid)
+        expect { Comment.accessible_by(@ability).joins(:article).order('articles.name').to_a }
+          .to raise_error(ActiveRecord::StatementInvalid)
+      else
+        expect(Comment.accessible_by(@ability).order('articles.name'))
+          .to match_array([comment2, comment1])
+        expect(Comment.accessible_by(@ability).joins(:article).order('articles.name'))
+          .to match_array([comment2, comment1])
+      end
     end
-    expect(-> { Article.accessible_by(@ability) }).to raise_error(CanCan::Error)
   end
 
-  it 'should support more than one deeply nested conditions' do
-    @ability.can :read, Comment, article: {
-      category: {
-        name: 'foo', visible: true
-      }
-    }
-    expect { Comment.accessible_by(@ability) }.to_not raise_error
-  end
+  it 'allows an empty array to be used as a condition for a has_many, but this is never a passing condition' do
+    a1 = Article.create!
+    a2 = Article.create!
+    a2.comments = [Comment.create!]
 
-  it 'does not allow to check ability on object against SQL conditions without block' do
-    @ability.can :read, Article, ['secret=?', true]
-    expect(-> { @ability.can? :read, Article.new }).to raise_error(CanCan::Error)
-  end
+    @ability.can :read, Article, comment_ids: []
 
-  it 'has false conditions if no abilities match' do
-    expect(@ability.model_adapter(Article, :read).conditions).to eq(false_condition)
-  end
+    expect(@ability.can?(:read, a1)).to eq(false)
+    expect(@ability.can?(:read, a2)).to eq(false)
 
-  it 'returns false conditions for cannot clause' do
-    @ability.cannot :read, Article
-    expect(@ability.model_adapter(Article, :read).conditions).to eq(false_condition)
-  end
+    expect(Article.accessible_by(@ability)).to eq([])
 
-  it 'returns SQL for single `can` definition in front of default `cannot` condition' do
-    @ability.cannot :read, Article
-    @ability.can :read, Article, published: false, secret: true
-    expect(@ability.model_adapter(Article, :read)).to generate_sql(%(
-SELECT "articles".*
-FROM "articles"
-WHERE "articles"."published" = #{false_v} AND "articles"."secret" = #{true_v}))
+    if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0')
+      expect(@ability.model_adapter(Article, :read)).to generate_sql(%(
+  SELECT "articles".*
+  FROM "articles"
+  WHERE 1=0))
+    end
   end
 
-  it 'returns true condition for single `can` definition in front of default `can` condition' do
-    @ability.can :read, Article
-    @ability.can :read, Article, published: false, secret: true
-    expect(@ability.model_adapter(Article, :read).conditions).to eq({})
-    expect(@ability.model_adapter(Article, :read)).to generate_sql(%(SELECT "articles".* FROM "articles"))
-  end
+  it 'allows a nil to be used as a condition for a has_many - with join' do
+    a1 = Article.create!
+    a2 = Article.create!
+    a2.comments = [Comment.create!]
 
-  it 'returns `false condition` for single `cannot` definition in front of default `cannot` condition' do
-    @ability.cannot :read, Article
-    @ability.cannot :read, Article, published: false, secret: true
-    expect(@ability.model_adapter(Article, :read).conditions).to eq(false_condition)
-  end
+    @ability.can :read, Article, comments: { id: nil }
 
-  it 'returns `not (sql)` for single `cannot` definition in front of default `can` condition' do
-    @ability.can :read, Article
-    @ability.cannot :read, Article, published: false, secret: true
-    expect(@ability.model_adapter(Article, :read).conditions)
-      .to orderlessly_match(
-        %["not (#{@article_table}"."published" = #{false_v} AND "#{@article_table}"."secret" = #{true_v})]
-      )
-  end
+    expect(@ability.can?(:read, a1)).to eq(true)
+    expect(@ability.can?(:read, a2)).to eq(false)
 
-  it 'returns appropriate sql conditions in complex case' do
-    @ability.can :read, Article
-    @ability.can :manage, Article, id: 1
-    @ability.can :update, Article, published: true
-    @ability.cannot :update, Article, secret: true
-    expect(@ability.model_adapter(Article, :update).conditions)
-      .to eq(%[not ("#{@article_table}"."secret" = #{true_v}) ] +
-               %[AND (("#{@article_table}"."published" = #{true_v}) ] +
-               %[OR ("#{@article_table}"."id" = 1))])
-    expect(@ability.model_adapter(Article, :manage).conditions).to eq(id: 1)
-    expect(@ability.model_adapter(Article, :read).conditions).to eq({})
-    expect(@ability.model_adapter(Article, :read)).to generate_sql(%(SELECT "articles".* FROM "articles"))
-  end
+    expect(Article.accessible_by(@ability)).to eq([a1])
 
-  it 'returns appropriate sql conditions in complex case with nested joins' do
-    @ability.can :read, Comment, article: { category: { visible: true } }
-    expect(@ability.model_adapter(Comment, :read).conditions).to eq(Category.table_name.to_sym => { visible: true })
+    if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0')
+      expect(@ability.model_adapter(Article, :read)).to generate_sql(%(
+  SELECT "articles".*
+  FROM "articles"
+  WHERE "articles"."id" IN (SELECT "articles"."id" FROM "articles"
+    LEFT OUTER JOIN "comments" ON "comments"."article_id" = "articles"."id"
+    WHERE "comments"."id" IS NULL)))
+    end
   end
 
-  it 'returns appropriate sql conditions in complex case with nested joins of different depth' do
-    @ability.can :read, Comment, article: { published: true, category: { visible: true } }
-    expect(@ability.model_adapter(Comment, :read).conditions)
-      .to eq(Article.table_name.to_sym => { published: true }, Category.table_name.to_sym => { visible: true })
-  end
+  it 'allows several nils to be used as a condition for a has_many - with join' do
+    a1 = Article.create!
+    a2 = Article.create!
+    a2.comments = [Comment.create!]
+
+    @ability.can :read, Article, comments: { id: nil, spam: nil }
+
+    expect(@ability.can?(:read, a1)).to eq(true)
+    expect(@ability.can?(:read, a2)).to eq(false)
 
-  it 'does not forget conditions when calling with SQL string' do
-    @ability.can :read, Article, published: true
-    @ability.can :read, Article, ['secret = ?', false]
-    adapter = @ability.model_adapter(Article, :read)
-    2.times do
-      expect(adapter.conditions).to eq(%[(secret = #{false_v}) OR ("#{@article_table}"."published" = #{true_v})])
+    expect(Article.accessible_by(@ability)).to eq([a1])
+
+    if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0')
+      expect(@ability.model_adapter(Article, :read)).to generate_sql(%(
+  SELECT "articles".*
+  FROM "articles"
+  WHERE "articles"."id" IN (SELECT "articles"."id" FROM "articles"
+    LEFT OUTER JOIN "comments" ON "comments"."article_id" = "articles"."id"
+    WHERE "comments"."id" IS NULL AND "comments"."spam" IS NULL)))
     end
   end
 
-  it 'has nil joins if no rules' do
-    expect(@ability.model_adapter(Article, :read).joins).to be_nil
-  end
+  it 'doesn\'t permit anything if a nil is used as a condition for a has_many alongside other attributes' do
+    a1 = Article.create!
+    a2 = Article.create!
+    a2.comments = [Comment.create!(spam: true)]
+    a3 = Article.create!
+    a3.comments = [Comment.create!(spam: false)]
 
-  it 'has nil joins if rules got compressed' do
-    @ability.can :read, Comment, article: { category: { visible: true } }
-    @ability.can :read, Comment
-    expect(@ability.model_adapter(Comment, :read))
-      .to generate_sql("SELECT \"#{@comment_table}\".* FROM \"#{@comment_table}\"")
-    expect(@ability.model_adapter(Comment, :read).joins).to be_nil
-  end
+    # if we are checking for `id: nil` and any other criteria, we should never return any Article.
+    # either the Article has Comments, which means `id: nil` fails.
+    # or the Article has no Comments, which means `spam: true` fails.
+    @ability.can :read, Article, comments: { id: nil, spam: true }
 
-  it 'has nil joins if no nested hashes specified in conditions' do
-    @ability.can :read, Article, published: false
-    @ability.can :read, Article, secret: true
-    expect(@ability.model_adapter(Article, :read).joins).to be_nil
-  end
+    expect(@ability.can?(:read, a1)).to eq(false)
+    expect(@ability.can?(:read, a2)).to eq(false)
+    expect(@ability.can?(:read, a3)).to eq(false)
 
-  it 'merges separate joins into a single array' do
-    @ability.can :read, Article, project: { blocked: false }
-    @ability.can :read, Article, company: { admin: true }
-    expect(@ability.model_adapter(Article, :read).joins.inspect).to orderlessly_match(%i[company project].inspect)
-  end
+    expect(Article.accessible_by(@ability)).to eq([])
 
-  it 'merges same joins into a single array' do
-    @ability.can :read, Article, project: { blocked: false }
-    @ability.can :read, Article, project: { admin: true }
-    expect(@ability.model_adapter(Article, :read).joins).to eq([:project])
+    if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0')
+      expect(@ability.model_adapter(Article, :read)).to generate_sql(%(
+  SELECT "articles".*
+  FROM "articles"
+  WHERE "articles"."id" IN (SELECT "articles"."id" FROM "articles"
+    LEFT OUTER JOIN "comments" ON "comments"."article_id" = "articles"."id"
+    WHERE "comments"."id" IS NULL AND "comments"."spam" = #{true_v})))
+    end
   end
 
-  it 'merges nested and non-nested joins' do
-    @ability.can :read, Article, project: { blocked: false }
-    @ability.can :read, Article, project: { comments: { spam: true } }
-    expect(@ability.model_adapter(Article, :read).joins).to eq([{ project: [:comments] }])
-  end
+  it 'doesn\'t permit if a nil is used as a condition for a has_many alongside other attributes - false case' do
+    a1 = Article.create!
+    a2 = Article.create!
+    a2.comments = [Comment.create!(spam: true)]
+    a3 = Article.create!
+    a3.comments = [Comment.create!(spam: false)]
 
-  it 'merges :all conditions with other conditions' do
-    user = User.create!
-    article = Article.create!(user: user)
-    ability = Ability.new(user)
-    ability.can :manage, :all
-    ability.can :manage, Article, user_id: user.id
-    expect(Article.accessible_by(ability)).to eq([article])
+    # if we are checking for `id: nil` and any other criteria, we should never return any Article.
+    # either the Article has Comments, which means `id: nil` fails.
+    # or the Article has no Comments, which means `spam: false` fails.
+    @ability.can :read, Article, comments: { id: nil, spam: false }
+
+    expect(@ability.can?(:read, a1)).to eq(false)
+    expect(@ability.can?(:read, a2)).to eq(false)
+    expect(@ability.can?(:read, a3)).to eq(false)
+
+    expect(Article.accessible_by(@ability)).to eq([])
+
+    if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0')
+      expect(@ability.model_adapter(Article, :read)).to generate_sql(%(
+  SELECT "articles".*
+  FROM "articles"
+  WHERE "articles"."id" IN (SELECT "articles"."id" FROM "articles"
+    LEFT OUTER JOIN "comments" ON "comments"."article_id" = "articles"."id"
+    WHERE "comments"."id" IS NULL AND "comments"."spam" = #{false_v})))
+    end
   end
 
-  it 'should not execute a scope when checking ability on the class' do
-    relation = Article.where(secret: true)
-    @ability.can :read, Article, relation do |article|
-      article.secret == true
+  it 'allows a nil to be used as a condition for a has_many when combined with other conditions' do
+    a1 = Article.create!
+    a2 = Article.create!
+    a2.comments = [Comment.create!(spam: true)]
+    a3 = Article.create!
+    a3.comments = [Comment.create!(spam: false)]
+
+    @ability.can :read, Article, comments: { spam: true }
+    @ability.can :read, Article, comments: { id: nil }
+
+    expect(@ability.can?(:read, a1)).to eq(true) # true because no comments
+    expect(@ability.can?(:read, a2)).to eq(true) # true because has comments but they have spam=true
+    expect(@ability.can?(:read, a3)).to eq(false) # false because has comments but none with spam=true
+
+    expect(Article.accessible_by(@ability).sort_by(&:id)).to eq([a1, a2].sort_by(&:id))
+
+    if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0')
+      expect(@ability.model_adapter(Article, :read)).to generate_sql(%(
+  SELECT "articles".*
+  FROM "articles"
+  WHERE "articles"."id" IN (SELECT "articles"."id" FROM "articles"
+    LEFT OUTER JOIN "comments" ON "comments"."article_id" = "articles"."id"
+    WHERE (("comments"."id" IS NULL) OR ("comments"."spam" = #{true_v})))))
     end
+  end
+
+  it 'allows a nil to be used as a condition for a has_many alongside other attributes on the parent' do
+    a1 = Article.create!(secret: true)
+    a2 = Article.create!(secret: true)
+    a2.comments = [Comment.create!]
+    a3 = Article.create!(secret: false)
+    a3.comments = [Comment.create!]
+    a4 = Article.create!(secret: false)
+
+    @ability.can :read, Article, secret: true, comments: { id: nil }
 
-    allow(relation).to receive(:count).and_raise('Unexpected scope execution.')
+    expect(@ability.can?(:read, a1)).to eq(true)
+    expect(@ability.can?(:read, a2)).to eq(false)
+    expect(@ability.can?(:read, a3)).to eq(false)
+    expect(@ability.can?(:read, a4)).to eq(false)
 
-    expect { @ability.can? :read, Article }.not_to raise_error
+    expect(Article.accessible_by(@ability)).to eq([a1])
+
+    if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0')
+      expect(@ability.model_adapter(Article, :read)).to generate_sql(%(
+  SELECT "articles".*
+  FROM "articles"
+  WHERE "articles"."id" IN (SELECT "articles"."id" FROM "articles"
+    LEFT OUTER JOIN "comments" ON "comments"."article_id" = "articles"."id"
+    WHERE "articles"."secret" = #{true_v} AND "comments"."id" IS NULL)))
+    end
   end
 
-  it 'should ignore cannot rules with attributes when querying' do
-    user = User.create!
-    article = Article.create!(user: user)
-    ability = Ability.new(user)
-    ability.can :read, Article
-    ability.cannot :read, Article, :secret
-    expect(Article.accessible_by(ability)).to eq([article])
+  it 'allows an empty array to be used as a condition for a belongs_to; this never returns true' do
+    a1 = Article.create!
+    a2 = Article.create!
+    a2.project = Project.create!
+
+    @ability.can :read, Article, project_id: []
+
+    expect(@ability.can?(:read, a1)).to eq(false)
+    expect(@ability.can?(:read, a2)).to eq(false)
+
+    expect(Article.accessible_by(@ability)).to eq([])
+
+    if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0')
+      expect(@ability.model_adapter(Article, :read)).to generate_sql(%(
+  SELECT "articles".*
+  FROM "articles"
+  WHERE 1=0))
+    end
   end
 
   context 'with namespaced models' do
@@ -471,6 +941,125 @@ WHERE "articles"."published" = #{false_v} AND "articles"."secret" = #{true_v}))
     end
   end
 
+  context 'when an association is used to create a rule' do
+    before do
+      ActiveRecord::Schema.define do
+        create_table(:foos) do |t|
+          t.string :name
+        end
+        create_table(:bars) do |t|
+          t.string :name
+        end
+        create_table :roles do |t|
+          t.string :name
+
+          t.timestamps
+        end
+        create_table :user_roles do |t|
+          t.references :user, foreign_key: true
+          t.references :role, foreign_key: true
+          t.references :subject, polymorphic: true
+
+          t.timestamps
+        end
+      end
+
+      class Foo < ActiveRecord::Base
+        has_many :user_roles, as: :subject
+      end
+
+      class Bar < ActiveRecord::Base
+        has_many :user_roles, as: :subject
+      end
+
+      class Role < ActiveRecord::Base
+        has_many :user_roles
+        has_many :users, through: :user_roles
+        has_many :foos, through: :user_roles
+        has_many :bars, through: :user_roles
+      end
+
+      class UserRole < ActiveRecord::Base
+        belongs_to :user
+        belongs_to :role
+        belongs_to :subject, polymorphic: true, required: false
+      end
+    end
+
+    it 'allows for access with association' do
+      user = User.create!
+      foo = Foo.create(name: 'foo')
+      role = Role.create(name: 'adviser')
+      UserRole.create(user: user, role: role, subject: foo)
+      ability = Ability.new(user)
+      ability.can :read, Foo, user_roles: { user: user }
+      expect(ability.can?(:read, Foo)).to eq(true)
+    end
+
+    it 'allows for access with association with accessible_by' do
+      user = User.new
+      foo = Foo.create(name: 'foo')
+      bar = Bar.create(name: 'bar')
+      role = Role.create(name: 'adviser')
+      UserRole.create(user: user, role: role, subject: foo)
+      UserRole.create(user: user, role: role, subject: bar)
+      ability = Ability.new(user)
+      ability.can :read, Foo, user_roles: { user: user }
+      expect(Foo.accessible_by(ability)).to match_array([foo])
+      expect(Bar.accessible_by(ability)).to match_array([])
+    end
+
+    it 'blocks access with association' do
+      user = User.create!
+      foo = Foo.create(name: 'foo')
+      role = Role.create(name: 'adviser')
+      UserRole.create(user: user, role: role, subject: foo)
+      ability = Ability.new(user)
+      ability.cannot :read, Foo, user_roles: { user: user }
+      expect(ability.can?(:read, Foo)).to eq(false)
+    end
+
+    it 'blocks access with association for accessible_by' do
+      user = User.create!
+      foo = Foo.create(name: 'foo')
+      role = Role.create(name: 'adviser')
+      UserRole.create(user: user, role: role, subject: foo)
+      ability = Ability.new(user)
+      ability.cannot :read, Foo, user_roles: { user: user }
+      expect(Foo.accessible_by(ability)).to match_array([])
+      expect(ability.can?(:read, Foo)).to eq(false)
+    end
+
+    it 'manages access with multiple models and users' do
+      (0..5).each do |index|
+        user = User.create!
+        foo = Foo.create(name: 'foo')
+        role = Role.create(name: "adviser_#{index}")
+        UserRole.create(user: user, role: role, subject: foo)
+      end
+
+      user = User.first
+
+      Foo.all.each do |foo|
+        role = Role.create(name: 'new_user')
+        UserRole.create(user: user, role: role, subject: foo)
+      end
+
+      ability = Ability.new(user)
+      ability.can :read, Foo, user_roles: { user: user }
+      expect(Foo.accessible_by(ability).count).to eq(Foo.count)
+
+      User.where.not(id: user.id).each do |limited_permission_user|
+        ability = Ability.new(limited_permission_user)
+        expect(ability.can?(:read, Foo)).to eq(false)
+        expect(Foo.accessible_by(ability).count).to eq(0)
+        ability.can :read, Foo, user_roles: { user: limited_permission_user }
+        expect(ability.can?(:read, Foo)).to eq(true)
+        expect(Foo.accessible_by(ability).count).to eq(1)
+      end
+    end
+  end
+
   context 'when a table references another one twice' do
     before do
       ActiveRecord::Schema.define do
@@ -499,25 +1088,67 @@ WHERE "articles"."published" = #{false_v} AND "articles"."secret" = #{true_v}))
     end
   end
 
-  context 'when a table is references multiple times' do
-    it 'can filter correctly on the different associations' do
-      u1 = User.create!(name: 'pippo')
-      u2 = User.create!(name: 'paperino')
+  CanCan.valid_accessible_by_strategies.each do |strategy|
+    context "when a table is referenced multiple times with #{strategy} strategy" do
+      before :each do
+        CanCan.accessible_by_strategy = strategy
+      end
+      it 'can filter correctly on the different associations' do
+        u1 = User.create!(name: 'pippo')
+        u2 = User.create!(name: 'paperino')
+
+        a1 = Article.create!(user: u1)
+        a2 = Article.create!(user: u2)
+
+        ability = Ability.new(u1)
+        ability.can :read, Article, user: { id: u1.id }
+        ability.can :read, Article, mentioned_users: { name: u1.name }
+        ability.can :read, Article, mentioned_users: { mentioned_articles: { id: a2.id } }
+        ability.can :read, Article, mentioned_users: { articles: { user: { name: 'deep' } } }
+        ability.can :read, Article, mentioned_users: { articles: { mentioned_users: { name: 'd2' } } }
+        expect(Article.accessible_by(ability)).to match_array([a1])
+      end
+    end
+  end
 
-      a1 = Article.create!(user: u1)
-      a2 = Article.create!(user: u2)
+  unless CanCan::ModelAdapters::ActiveRecordAdapter.version_lower?('5.0.0')
+    context 'has_many through is defined and referenced differently - subquery strategy' do
+      before do
+        CanCan.accessible_by_strategy = :subquery
+      end
 
-      ability = Ability.new(u1)
-      ability.can :read, Article, user: { id: u1.id }
-      ability.can :read, Article, mentioned_users: { name: u1.name }
-      ability.can :read, Article, mentioned_users: { mentioned_articles: { id: a2.id } }
-      ability.can :read, Article, mentioned_users: { articles: { user: { name: 'deep' } } }
-      ability.can :read, Article, mentioned_users: { articles: { mentioned_users: { name: 'd2' } } }
-      expect(Article.accessible_by(ability)).to match_array([a1])
+      it 'recognises it and simplifies the query' do
+        u1 = User.create!(name: 'pippo')
+        u2 = User.create!(name: 'paperino')
+
+        a1 = Article.create!(mentioned_users: [u1])
+        a2 = Article.create!(mentioned_users: [u2])
+
+        ability = Ability.new(u1)
+        ability.can :read, Article, mentioned_users: { name: u1.name }
+        ability.can :read, Article, mentions: { user: { name: u2.name } }
+        expect(Article.accessible_by(ability)).to match_array([a1, a2])
+        if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0')
+          expect(ability.model_adapter(Article, :read)).to generate_sql(%(
+    SELECT "articles".*
+    FROM "articles"
+    WHERE "articles"."id" IN
+    (SELECT "articles"."id"
+      FROM "articles"
+      LEFT OUTER JOIN "legacy_mentions" ON "legacy_mentions"."article_id" = "articles"."id"
+      LEFT OUTER JOIN "users" ON "users"."id" = "legacy_mentions"."user_id"
+      WHERE (("users"."name" = 'paperino') OR ("users"."name" = 'pippo')))
+    ))
+        end
+      end
     end
   end
 
-  context 'has_many through is defined and referenced differently' do
+  context 'has_many through is defined and referenced differently - left_join strategy' do
+    before do
+      CanCan.accessible_by_strategy = :left_join
+    end
+
     it 'recognises it and simplifies the query' do
       u1 = User.create!(name: 'pippo')
       u2 = User.create!(name: 'paperino')
@@ -529,6 +1160,7 @@ WHERE "articles"."published" = #{false_v} AND "articles"."secret" = #{true_v}))
       ability.can :read, Article, mentioned_users: { name: u1.name }
       ability.can :read, Article, mentions: { user: { name: u2.name } }
       expect(Article.accessible_by(ability)).to match_array([a1, a2])
+
       if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0')
         expect(ability.model_adapter(Article, :read)).to generate_sql(%(
   SELECT DISTINCT "articles".*
@@ -539,4 +1171,330 @@ WHERE "articles"."published" = #{false_v} AND "articles"."secret" = #{true_v}))
       end
     end
   end
+
+  if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0')
+    context 'switching strategies' do
+      before do
+        CanCan.accessible_by_strategy = :left_join # default - should be ignored in these tests
+      end
+
+      it 'allows you to switch strategies with a keyword argument' do
+        u = User.create!(name: 'pippo')
+        Article.create!(mentioned_users: [u])
+
+        ability = Ability.new(u)
+        ability.can :read, Article, mentions: { user: { name: u.name } }
+
+        subquery_sql = Article.accessible_by(ability, strategy: :subquery).to_sql
+        left_join_sql = Article.accessible_by(ability, strategy: :left_join).to_sql
+
+        expect(subquery_sql.strip.squeeze(' ')).to eq(%(
+    SELECT "articles".*
+    FROM "articles"
+    WHERE "articles"."id" IN
+    (SELECT "articles"."id"
+      FROM "articles"
+      LEFT OUTER JOIN "legacy_mentions" ON "legacy_mentions"."article_id" = "articles"."id"
+      LEFT OUTER JOIN "users" ON "users"."id" = "legacy_mentions"."user_id"
+      WHERE "users"."name" = 'pippo')
+    ).gsub(/\s+/, ' ').strip)
+
+        expect(left_join_sql.strip.squeeze(' ')).to eq(%(
+  SELECT DISTINCT "articles".*
+  FROM "articles"
+  LEFT OUTER JOIN "legacy_mentions" ON "legacy_mentions"."article_id" = "articles"."id"
+  LEFT OUTER JOIN "users" ON "users"."id" = "legacy_mentions"."user_id"
+  WHERE "users"."name" = 'pippo').gsub(/\s+/, ' ').strip)
+      end
+
+      it 'allows you to switch strategies with a block' do
+        u = User.create!(name: 'pippo')
+        Article.create!(mentioned_users: [u])
+
+        ability = Ability.new(u)
+        ability.can :read, Article, mentions: { user: { name: u.name } }
+
+        subquery_sql = CanCan.with_accessible_by_strategy(:subquery) { Article.accessible_by(ability).to_sql }
+        left_join_sql = CanCan.with_accessible_by_strategy(:left_join) { Article.accessible_by(ability).to_sql }
+
+        expect(subquery_sql.strip.squeeze(' ')).to eq(%(
+    SELECT "articles".*
+    FROM "articles"
+    WHERE "articles"."id" IN
+    (SELECT "articles"."id"
+      FROM "articles"
+      LEFT OUTER JOIN "legacy_mentions" ON "legacy_mentions"."article_id" = "articles"."id"
+      LEFT OUTER JOIN "users" ON "users"."id" = "legacy_mentions"."user_id"
+      WHERE "users"."name" = 'pippo')
+    ).gsub(/\s+/, ' ').strip)
+
+        expect(left_join_sql.strip.squeeze(' ')).to eq(%(
+  SELECT DISTINCT "articles".*
+  FROM "articles"
+  LEFT OUTER JOIN "legacy_mentions" ON "legacy_mentions"."article_id" = "articles"."id"
+  LEFT OUTER JOIN "users" ON "users"."id" = "legacy_mentions"."user_id"
+  WHERE "users"."name" = 'pippo').gsub(/\s+/, ' ').strip)
+      end
+
+      it 'allows you to switch strategies with a block, and to_sql called outside the block' do
+        u = User.create!(name: 'pippo')
+        Article.create!(mentioned_users: [u])
+
+        ability = Ability.new(u)
+        ability.can :read, Article, mentions: { user: { name: u.name } }
+
+        subquery_sql = CanCan.with_accessible_by_strategy(:subquery) { Article.accessible_by(ability) }.to_sql
+        left_join_sql = CanCan.with_accessible_by_strategy(:left_join) { Article.accessible_by(ability) }.to_sql
+
+        expect(subquery_sql.strip.squeeze(' ')).to eq(%(
+    SELECT "articles".*
+    FROM "articles"
+    WHERE "articles"."id" IN
+    (SELECT "articles"."id"
+      FROM "articles"
+      LEFT OUTER JOIN "legacy_mentions" ON "legacy_mentions"."article_id" = "articles"."id"
+      LEFT OUTER JOIN "users" ON "users"."id" = "legacy_mentions"."user_id"
+      WHERE "users"."name" = 'pippo')
+    ).gsub(/\s+/, ' ').strip)
+
+        expect(left_join_sql.strip.squeeze(' ')).to eq(%(
+  SELECT DISTINCT "articles".*
+  FROM "articles"
+  LEFT OUTER JOIN "legacy_mentions" ON "legacy_mentions"."article_id" = "articles"."id"
+  LEFT OUTER JOIN "users" ON "users"."id" = "legacy_mentions"."user_id"
+  WHERE "users"."name" = 'pippo').gsub(/\s+/, ' ').strip)
+      end
+    end
+  end
+
+  CanCan.valid_accessible_by_strategies.each do |strategy|
+    context "when a model has renamed primary_key with #{strategy} strategy" do
+      before :each do
+        CanCan.accessible_by_strategy = strategy
+      end
+      before do
+        ActiveRecord::Schema.define do
+          create_table(:custom_pk_users, primary_key: :gid) do |t|
+            t.string :name
+          end
+
+          create_table(:custom_pk_transactions, primary_key: :gid) do |t|
+            t.integer :custom_pk_user_id
+            t.string :data
+          end
+        end
+
+        class CustomPkUser < ActiveRecord::Base
+          self.primary_key = 'gid'
+        end
+
+        class CustomPkTransaction < ActiveRecord::Base
+          self.primary_key = 'gid'
+
+          belongs_to :custom_pk_user
+        end
+      end
+
+      it 'can filter correctly' do
+        user1 = CustomPkUser.create!
+        user2 = CustomPkUser.create!
+
+        transaction1 = CustomPkTransaction.create!(custom_pk_user: user1)
+        CustomPkTransaction.create!(custom_pk_user: user2)
+
+        ability = Ability.new(user1)
+        ability.can :read, CustomPkTransaction, custom_pk_user: { gid: user1.gid }
+
+        expect(CustomPkTransaction.accessible_by(ability)).to match_array([transaction1])
+      end
+    end
+  end
+
+  context 'when a table has json type column' do
+    before do
+      json_supported =
+        ActiveRecord::Base.connection.respond_to?(:supports_json?) &&
+        ActiveRecord::Base.connection.supports_json?
+
+      skip "Adapter don't support JSON column type" unless json_supported
+
+      ActiveRecord::Schema.define do
+        create_table(:json_transactions) do |t|
+          t.integer :user_id
+          t.json :additional_data
+        end
+      end
+
+      class JsonTransaction < ActiveRecord::Base
+        belongs_to :user
+      end
+    end
+
+    it 'can filter correctly if using subquery strategy' do
+      CanCan.accessible_by_strategy = :subquery
+
+      user = User.create!
+      transaction = JsonTransaction.create!(user: user)
+
+      ability = Ability.new(user)
+      ability.can :read, JsonTransaction, user: { id: user.id }
+
+      expect(JsonTransaction.accessible_by(ability)).to match_array([transaction])
+    end
+
+    # this fails on Postgres. see https://github.com/CanCanCommunity/cancancan/pull/608
+    it 'cannot filter JSON on postgres columns using left_join strategy' do
+      skip unless postgres?
+
+      CanCan.accessible_by_strategy = :left_join
+
+      user = User.create!
+      JsonTransaction.create!(user: user)
+
+      ability = Ability.new(user)
+      ability.can :read, JsonTransaction, user: { id: user.id }
+
+      expect { JsonTransaction.accessible_by(ability).to_a }
+        .to raise_error(ActiveRecord::StatementInvalid)
+    end
+  end
+
+  context 'with rule application to subclass for non sti class' do
+    before do
+      ActiveRecord::Schema.define do
+        create_table :parents, force: true
+
+        create_table :children, force: true
+      end
+
+      class ApplicationRecord < ActiveRecord::Base
+        self.abstract_class = true
+      end
+
+      class Parent < ActiveRecord::Base
+      end
+
+      class Child < Parent
+      end
+    end
+
+    it 'cannot rules are not effecting parent class' do
+      u1 = User.create!(name: 'pippo')
+      ability = Ability.new(u1)
+      ability.can :manage, Parent
+      ability.cannot :manage, Child
+      expect(ability).not_to be_able_to(:index, Child)
+      expect(ability).to be_able_to(:index, Parent)
+    end
+
+    it 'can rules are not effecting parent class' do
+      u1 = User.create!(name: 'pippo')
+      ability = Ability.new(u1)
+      ability.can :manage, Child
+      expect(ability).to be_able_to(:index, Child)
+      expect(ability).not_to be_able_to(:index, Parent)
+    end
+  end
+
+  context 'when STI is in use' do
+    before do
+      ActiveRecord::Schema.define do
+        create_table(:brands) do |t|
+          t.string :name
+        end
+
+        create_table(:vehicles) do |t|
+          t.string :type
+          t.integer :capacity
+        end
+      end
+
+      class ApplicationRecord < ActiveRecord::Base
+        self.abstract_class = true
+      end
+
+      class Vehicle < ApplicationRecord
+      end
+
+      class Car < Vehicle
+      end
+
+      class Motorbike < Vehicle
+      end
+
+      class Suzuki < Motorbike
+      end
+    end
+
+    it 'recognises rules applied to the base class' do
+      u1 = User.create!(name: 'pippo')
+
+      car = Car.create!
+      motorbike = Motorbike.create!
+
+      ability = Ability.new(u1)
+      ability.can :read, Vehicle
+      expect(Vehicle.accessible_by(ability)).to match_array([car, motorbike])
+      expect(Car.accessible_by(ability)).to match_array([car])
+      expect(Motorbike.accessible_by(ability)).to match_array([motorbike])
+    end
+
+    it 'recognises rules applied to the base class multiple classes deep' do
+      u1 = User.create!(name: 'pippo')
+
+      car = Car.create!
+      motorbike = Motorbike.create!
+      suzuki = Suzuki.create!
+
+      ability = Ability.new(u1)
+      ability.can :read, Vehicle
+      expect(Vehicle.accessible_by(ability)).to match_array([suzuki, car, motorbike])
+      expect(Car.accessible_by(ability)).to match_array([car])
+      expect(Motorbike.accessible_by(ability)).to match_array([suzuki, motorbike])
+      expect(Suzuki.accessible_by(ability)).to match_array([suzuki])
+    end
+
+    it 'recognises rules applied to subclasses' do
+      u1 = User.create!(name: 'pippo')
+      car = Car.create!
+      Motorbike.create!
+
+      ability = Ability.new(u1)
+      ability.can :read, [Car]
+      expect(Vehicle.accessible_by(ability)).to match_array([car])
+      expect(Car.accessible_by(ability)).to eq([car])
+      expect(Motorbike.accessible_by(ability)).to eq([])
+    end
+
+    it 'recognises rules applied to subclasses on 3 level' do
+      u1 = User.create!(name: 'pippo')
+      suzuki = Suzuki.create!
+      Motorbike.create!
+      ability = Ability.new(u1)
+      ability.can :read, [Suzuki]
+      expect(Motorbike.accessible_by(ability)).to eq([suzuki])
+    end
+
+    it 'recognises rules applied to subclass of subclass even with be_able_to' do
+      u1 = User.create!(name: 'pippo')
+      motorbike = Motorbike.create!
+      ability = Ability.new(u1)
+      ability.can :read, [Motorbike]
+      expect(ability).to be_able_to(:read, motorbike)
+      expect(ability).to be_able_to(:read, Suzuki.new)
+    end
+
+    it 'allows a scope of a subclass for conditions' do
+      u1 = User.create!(name: 'pippo')
+      car = Car.create!(capacity: 2)
+      Car.create!(capacity: 4)
+      Motorbike.create!(capacity: 2)
+
+      ability = Ability.new(u1)
+      ability.can :read, [Car], Car.where(capacity: 2)
+      expect(Vehicle.accessible_by(ability)).to match_array([car])
+      expect(Car.accessible_by(ability)).to eq([car])
+      expect(Motorbike.accessible_by(ability)).to eq([])
+    end
+  end
 end
diff --git a/spec/cancan/model_adapters/conditions_normalizer_spec.rb b/spec/cancan/model_adapters/conditions_normalizer_spec.rb
index fdfb7f5..9368ae6 100644
--- a/spec/cancan/model_adapters/conditions_normalizer_spec.rb
+++ b/spec/cancan/model_adapters/conditions_normalizer_spec.rb
@@ -24,6 +24,14 @@ RSpec.describe CanCan::ModelAdapters::ConditionsNormalizer do
         t.integer :user_id
         t.integer :article_id
       end
+
+      create_table(:attachments) do |t|
+        t.references :record, polymorphic: true
+        t.integer :blob_id
+      end
+
+      create_table(:blob) do |t|
+      end
     end
 
     class Article < ActiveRecord::Base
@@ -31,6 +39,7 @@ RSpec.describe CanCan::ModelAdapters::ConditionsNormalizer do
       has_many :comments, through: :spread_comments
       has_many :mentions
       has_many :mentioned_users, through: :mentions, source: :user
+      has_many :attachments, as: :record
     end
 
     class Comment < ActiveRecord::Base
@@ -53,6 +62,16 @@ RSpec.describe CanCan::ModelAdapters::ConditionsNormalizer do
       has_many :mentions
       has_many :mentioned_articles, through: :mentions, source: :article
     end
+
+    class Attachment < ActiveRecord::Base
+      belongs_to :record, polymorphic: true
+      belongs_to :blob
+    end
+
+    class Blob < ActiveRecord::Base
+      has_many :attachments
+      has_many :articles, through: :attachments, source: :record, source_type: 'Article'
+    end
   end
 
   it 'simplifies has_many through associations' do
@@ -61,6 +80,12 @@ RSpec.describe CanCan::ModelAdapters::ConditionsNormalizer do
     expect(rule.conditions).to eq(spread_comments: { article: { mentions: { user: { name: 'pippo' } } } })
   end
 
+  it 'does not simplifies has_many through polymorphic associations' do
+    rule = CanCan::Rule.new(true, :read, Blob, articles: { id: 11 })
+    CanCan::ModelAdapters::ConditionsNormalizer.normalize(Blob, [rule])
+    expect(rule.conditions).to eq(articles: { id: 11 })
+  end
+
   it 'normalizes the has_one through associations' do
     class Supplier < ActiveRecord::Base
       has_one :accountant
diff --git a/spec/cancan/model_adapters/has_and_belongs_to_many_spec.rb b/spec/cancan/model_adapters/has_and_belongs_to_many_spec.rb
index ad55606..c98adb1 100644
--- a/spec/cancan/model_adapters/has_and_belongs_to_many_spec.rb
+++ b/spec/cancan/model_adapters/has_and_belongs_to_many_spec.rb
@@ -45,20 +45,108 @@ RSpec.describe CanCan::ModelAdapters::ActiveRecord5Adapter do
     ability.can :read, House, people: { id: @person1.id }
   end
 
-  describe 'fetching of records' do
-    it 'it retreives the records correctly' do
+  unless CanCan::ModelAdapters::ActiveRecordAdapter.version_lower?('5.0.0')
+    describe 'fetching of records - joined_alias_subquery strategy' do
+      before do
+        CanCan.accessible_by_strategy = :joined_alias_exists_subquery
+      end
+
+      it 'it retreives the records correctly' do
+        houses = House.accessible_by(ability)
+        expect(houses).to match_array [@house2, @house1]
+      end
+
+      if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0')
+        it 'generates the correct query' do
+          expect(ability.model_adapter(House, :read))
+            .to generate_sql("SELECT \"houses\".*
+                             FROM \"houses\"
+                             JOIN \"houses\" AS \"houses_alias\" ON \"houses_alias\".\"id\" = \"houses\".\"id\"
+                             WHERE (EXISTS (SELECT 1
+                               FROM \"houses\"
+                               LEFT OUTER JOIN \"houses_people\" ON \"houses_people\".\"house_id\" = \"houses\".\"id\"
+                               LEFT OUTER JOIN \"people\" ON \"people\".\"id\" = \"houses_people\".\"person_id\"
+                               WHERE
+                                 \"people\".\"id\" = #{@person1.id} AND
+                                 (\"houses\".\"id\" = \"houses_alias\".\"id\") LIMIT 1))
+                            ")
+        end
+      end
+    end
+
+    describe 'fetching of records - joined_alias_each_rule_as_exists_subquery strategy' do
+      before do
+        CanCan.accessible_by_strategy = :joined_alias_each_rule_as_exists_subquery
+      end
+
+      it 'it retreives the records correctly' do
+        houses = House.accessible_by(ability)
+        expect(houses).to match_array [@house2, @house1]
+      end
+
+      if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0')
+        it 'generates the correct query' do
+          expect(ability.model_adapter(House, :read))
+            .to generate_sql("SELECT \"houses\".*
+                             FROM \"houses\"
+                             JOIN \"houses\" AS \"houses_alias\" ON \"houses_alias\".\"id\" = \"houses\".\"id\"
+                             WHERE (EXISTS (SELECT 1
+                               FROM \"houses\"
+                               INNER JOIN \"houses_people\" ON \"houses_people\".\"house_id\" = \"houses\".\"id\"
+                               INNER JOIN \"people\" ON \"people\".\"id\" = \"houses_people\".\"person_id\"
+                               WHERE (\"houses\".\"id\" = \"houses_alias\".\"id\") AND
+                               (\"people\".\"id\" = #{@person1.id})
+                               LIMIT 1))
+                             ")
+        end
+      end
+    end
+
+    describe 'fetching of records - subquery strategy' do
+      before do
+        CanCan.accessible_by_strategy = :subquery
+      end
+
+      it 'it retrieves the records correctly' do
+        houses = House.accessible_by(ability)
+        expect(houses).to match_array [@house2, @house1]
+      end
+
+      if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0')
+        it 'generates the correct query' do
+          expect(ability.model_adapter(House, :read))
+            .to generate_sql("SELECT \"houses\".*
+                            FROM \"houses\"
+                            WHERE \"houses\".\"id\" IN
+                              (SELECT \"houses\".\"id\"
+                              FROM \"houses\"
+                              LEFT OUTER JOIN \"houses_people\" ON \"houses_people\".\"house_id\" = \"houses\".\"id\"
+                              LEFT OUTER JOIN \"people\" ON \"people\".\"id\" = \"houses_people\".\"person_id\"
+                              WHERE \"people\".\"id\" = #{@person1.id})
+                            ")
+        end
+      end
+    end
+  end
+
+  describe 'fetching of records - left_join strategy' do
+    before do
+      CanCan.accessible_by_strategy = :left_join
+    end
+
+    it 'it retrieves the records correctly' do
       houses = House.accessible_by(ability)
       expect(houses).to match_array [@house2, @house1]
     end
 
     if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0')
       it 'generates the correct query' do
-        expect(ability.model_adapter(House, :read))
-          .to generate_sql("SELECT DISTINCT \"houses\".*
-                          FROM \"houses\"
-                          LEFT OUTER JOIN \"houses_people\" ON \"houses_people\".\"house_id\" = \"houses\".\"id\"
-                          LEFT OUTER JOIN \"people\" ON \"people\".\"id\" = \"houses_people\".\"person_id\"
-                          WHERE \"people\".\"id\" = #{@person1.id}")
+        expect(ability.model_adapter(House, :read)).to generate_sql(%(
+    SELECT DISTINCT "houses".*
+    FROM "houses"
+    LEFT OUTER JOIN "houses_people" ON "houses_people"."house_id" = "houses"."id"
+    LEFT OUTER JOIN "people" ON "people"."id" = "houses_people"."person_id"
+    WHERE "people"."id" = #{@person1.id}))
       end
     end
   end
diff --git a/spec/cancan/rule_spec.rb b/spec/cancan/rule_spec.rb
index 9f99568..7de14c1 100644
--- a/spec/cancan/rule_spec.rb
+++ b/spec/cancan/rule_spec.rb
@@ -79,6 +79,11 @@ RSpec.describe CanCan::Rule do
         count = count_queries { rule.inspect }
         expect(count).to eq 0
       end
+
+      it 'displays the rule correctly when it is constructed through sql array' do
+        rule = CanCan::Rule.new(true, :read, Watermelon, ['visible=?', true], {}, {})
+        expect(rule.inspect).not_to be_blank
+      end
     end
   end
 end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index f8f51c1..72b4ac2 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -27,6 +27,11 @@ RSpec.configure do |config|
   end
 
   config.include SQLHelpers
+
+  config.after :each do
+    CanCan.accessible_by_strategy = CanCan.default_accessible_by_strategy
+    CanCan.rules_compressor_enabled = true
+  end
 end
 
 RSpec::Matchers.define :generate_sql do |expected|
diff --git a/spec/support/sql_helpers.rb b/spec/support/sql_helpers.rb
index d4cf2e4..20b783c 100644
--- a/spec/support/sql_helpers.rb
+++ b/spec/support/sql_helpers.rb
@@ -5,15 +5,25 @@ module SQLHelpers
     adapter.database_records.to_sql.strip.squeeze(' ')
   end
 
+  def sqlite?
+    ENV['DB'] == 'sqlite'
+  end
+
+  def postgres?
+    ENV['DB'] == 'postgres'
+  end
+
   def connect_db
     # ActiveRecord::Base.logger = Logger.new(STDOUT)
     ActiveRecord::Base.logger = nil
-    if ENV['DB'] == 'sqlite'
+    if sqlite?
       ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
-    elsif ENV['DB'] == 'postgres'
+    elsif postgres?
       connect_postgres
+    elsif ENV['DB'].nil?
+      raise StandardError, "ENV['DB'] not specified"
     else
-      raise StandardError, 'database not supported'
+      raise StandardError, "database not supported: #{ENV['DB']}.  Try DB='sqlite' or DB='postgres'"
     end
   end
 
@@ -21,11 +31,13 @@ module SQLHelpers
 
   def connect_postgres
     ActiveRecord::Base.establish_connection(adapter: 'postgresql', host: 'localhost',
-                                            database: 'postgres', schema_search_path: 'public')
+                                            database: 'postgres', schema_search_path: 'public',
+                                            user: 'postgres', password: 'postgres')
     ActiveRecord::Base.connection.drop_database('cancan_postgresql_spec')
     ActiveRecord::Base.connection.create_database('cancan_postgresql_spec', 'encoding' => 'utf-8',
                                                                             'adapter' => 'postgresql')
     ActiveRecord::Base.establish_connection(adapter: 'postgresql', host: 'localhost',
-                                            database: 'cancan_postgresql_spec')
+                                            database: 'cancan_postgresql_spec',
+                                            user: 'postgres', password: 'postgres')
   end
 end

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/cancan/class_matcher.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/cancan/config.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/cancan/model_adapters/sti_normalizer.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/cancan/model_adapters/strategies/base.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/cancan/model_adapters/strategies/joined_alias_exists_subquery.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/cancan/model_adapters/strategies/left_join.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/cancan/model_adapters/strategies/subquery.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/cancan/relevant.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/cancan/sti_detector.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/specifications/cancancan-3.5.0.gemspec

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/share/rubygems-integration/all/specifications/cancancan-3.0.1.gemspec

No differences were encountered in the control files

More details

Full run details