From 29fc3343c8d5d8183d344d28fa81c8dd5425c214 Mon Sep 17 00:00:00 2001 From: Alex Ghiculescu Date: Wed, 9 Dec 2020 16:11:07 -0600 Subject: [PATCH 01/21] Switch to github actions --- .github/workflows/test.yml | 79 +++++++++++++++++++ .travis.yml | 63 --------------- Appraisals | 34 ++++++++ gemfiles/activerecord_6.1.0.gemfile | 20 +++++ gemfiles/activerecord_master.gemfile | 20 +++++ .../model_adapters/active_record_5_adapter.rb | 7 +- spec/support/sql_helpers.rb | 6 +- 7 files changed, 158 insertions(+), 71 deletions(-) create mode 100644 .github/workflows/test.yml delete mode 100644 .travis.yml create mode 100644 gemfiles/activerecord_6.1.0.gemfile create mode 100644 gemfiles/activerecord_master.gemfile diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..dc23084e1 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,79 @@ +name: Test & lint +on: [push] + +env: + RAILS_ENV: test + PGHOST: localhost + PGUSER: postgres + +jobs: + tests: + name: Test & lint + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + ruby: ['2.4', '2.5', '2.6', '2.7', 'jruby'] + 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', 'gemfiles/activerecord_6.1.0.gemfile', 'gemfiles/activerecord_master.gemfile'] + exclude: + - gemfile: 'gemfiles/activerecord_4.2.0.gemfile' + ruby: '2.7' # rails 4.2 can't run on ruby 2.7 due to BigDecimal API change + - gemfile: 'gemfiles/activerecord_6.0.0.gemfile' + ruby: '2.4' # rails 6+ requires ruby 2.5+ + - gemfile: 'gemfiles/activerecord_6.1.0.gemfile' + ruby: '2.4' # rails 6+ requires ruby 2.5+ + - gemfile: 'gemfiles/activerecord_master.gemfile' + ruby: '2.4' # rails 6+ requires ruby 2.5+ + - 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_master.gemfile' + ruby: 'jruby' # this *should* work. it seems like there's an issue with rails 6 on jruby. + + 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' + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + + - name: Install bundler + run: gem install bundler + + # https://github.com/oracle/truffleruby/issues/62#issuecomment-362065726 + # TODO: can't get this to work on Github actions. see comments in https://github.com/CanCanCommunity/cancancan/pull/669 + - name: Nokogiri support for Truffleruby + run: BUNDLE_GEMFILE=${{ matrix.gemfile }} bundle config build.nokogiri --use-system-libraries + if: ${{ matrix.ruby == 'truffleruby' }} + + - name: Install gems + run: BUNDLE_GEMFILE=${{ matrix.gemfile }} bundle install --jobs 2 + + - name: Run linter + run: BUNDLE_GEMFILE=${{ matrix.gemfile }} bundle exec rubocop + + - name: Run tests on sqlite + run: DB=sqlite BUNDLE_GEMFILE=${{ matrix.gemfile }} bundle exec rake + + - name: Run tests on postgres + run: DB=postgres BUNDLE_GEMFILE=${{ matrix.gemfile }} bundle exec rake diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index db1150ba5..000000000 --- a/.travis.yml +++ /dev/null @@ -1,63 +0,0 @@ -language: ruby -cache: bundler -addons: - postgresql: "9.6" -rvm: - - 2.3.5 - - 2.4.2 - - 2.5.1 - - 2.6.3 - - 2.7.0 - - ruby-head - - jruby-9.1.17.0 - - jruby-9.2.11.1 - - jruby-head - - truffleruby-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: 2.7.0 - gemfile: gemfiles/activerecord_4.2.0.gemfile - - rvm: truffleruby-head - gemfile: gemfiles/activerecord_4.2.0.gemfile - - rvm: jruby-9.1.17.0 - gemfile: gemfiles/activerecord_5.0.2.gemfile - - rvm: jruby-9.1.17.0 - gemfile: gemfiles/activerecord_6.0.0.gemfile - - rvm: jruby-9.2.11.1 - gemfile: gemfiles/activerecord_5.0.2.gemfile - - rvm: jruby-9.2.11.1 - 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: - - rvm get stable - - gem update --system - - gem install bundler -script: - - bundle exec rubocop && bundle exec rake diff --git a/Appraisals b/Appraisals index 9fd8ce0c8..f7b89691a 100644 --- a/Appraisals +++ b/Appraisals @@ -83,3 +83,37 @@ appraise 'activerecord_6.0.0' do gem 'sqlite3', '~> 1.4.0' 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.2.3' + gem 'sqlite3', '~> 1.4.2' + end +end + +appraise 'activerecord_master' do + gem 'actionpack', github: 'rails/rails', require: 'action_pack' + gem 'activerecord', github: 'rails/rails', require: 'active_record' + gem 'activesupport', github: 'rails/rails', 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.2.3' + gem 'sqlite3', '~> 1.4.2' + end +end diff --git a/gemfiles/activerecord_6.1.0.gemfile b/gemfiles/activerecord_6.1.0.gemfile new file mode 100644 index 000000000..857cfa4e2 --- /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.2.3" + gem "sqlite3", "~> 1.4.2" +end + +gemspec path: "../" diff --git a/gemfiles/activerecord_master.gemfile b/gemfiles/activerecord_master.gemfile new file mode 100644 index 000000000..8328cadc1 --- /dev/null +++ b/gemfiles/activerecord_master.gemfile @@ -0,0 +1,20 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "actionpack", github: "rails/rails", require: "action_pack" +gem "activerecord", github: "rails/rails", require: "active_record" +gem "activesupport", github: "rails/rails", 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.2.3" + gem "sqlite3", "~> 1.4.2" +end + +gemspec path: "../" diff --git a/lib/cancan/model_adapters/active_record_5_adapter.rb b/lib/cancan/model_adapters/active_record_5_adapter.rb index f7c5df8dc..1326aeece 100644 --- a/lib/cancan/model_adapters/active_record_5_adapter.rb +++ b/lib/cancan/model_adapters/active_record_5_adapter.rb @@ -32,7 +32,6 @@ def build_relation(*where_conditions) end end - # Rails 4.2 deprecates `sanitize_sql_hash_for_conditions` def sanitize_sql(conditions) if conditions.is_a?(Hash) sanitize_sql_activerecord5(conditions) @@ -46,11 +45,7 @@ def sanitize_sql_activerecord5(conditions) 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/spec/support/sql_helpers.rb b/spec/support/sql_helpers.rb index fc1ac8cdc..4d5c703a6 100644 --- a/spec/support/sql_helpers.rb +++ b/spec/support/sql_helpers.rb @@ -23,11 +23,13 @@ def connect_db 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 From 387af0fa6e8cf8d86dc859612777078053a08957 Mon Sep 17 00:00:00 2001 From: Alex Ghiculescu Date: Mon, 14 Dec 2020 17:04:54 -0600 Subject: [PATCH 02/21] Support modifying the querying strategy on a per-query basis Fixes https://github.com/CanCanCommunity/cancancan/discussions/668 --- CHANGELOG.md | 4 + lib/cancan/config.rb | 28 +++++- lib/cancan/model_additions.rb | 6 +- .../active_record_adapter_spec.rb | 95 +++++++++++++++++++ 4 files changed, 127 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9a426283..b0952e3ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## Unreleased + +* [#675](https://github.com/CanCanCommunity/cancancan/pull/675): Support modifying the `accessible_by` querying strategy on a per-query basis. ([@ghiculescu][]) + ## 3.2.1 * [#674](https://github.com/CanCanCommunity/cancancan/pull/674): Fix accidental dependency on ActiveRecord in 3.2.0. ([@ghiculescu][]) diff --git a/lib/cancan/config.rb b/lib/cancan/config.rb index 19243395e..a9106526b 100644 --- a/lib/cancan/config.rb +++ b/lib/cancan/config.rb @@ -21,7 +21,9 @@ def self.valid_accessible_by_strategies # `distinct` is not reliable in some cases. See # https://github.com/CanCanCommunity/cancancan/pull/605 def self.accessible_by_strategy - @accessible_by_strategy || default_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 @@ -36,9 +38,7 @@ def self.default_accessible_by_strategy end def self.accessible_by_strategy=(value) - unless valid_accessible_by_strategies.include?(value) - raise ArgumentError, "accessible_by_strategy must be one of #{valid_accessible_by_strategies.join(', ')}" - end + validate_accessible_by_strategy!(value) if value == :subquery && does_not_support_subquery_strategy? raise ArgumentError, 'accessible_by_strategy = :subquery requires ActiveRecord 5 or newer' @@ -47,6 +47,26 @@ def self.accessible_by_strategy=(value) @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') diff --git a/lib/cancan/model_additions.rb b/lib/cancan/model_additions.rb index 96732133b..ef2d26eb3 100644 --- a/lib/cancan/model_additions.rb +++ b/lib/cancan/model_additions.rb @@ -20,8 +20,10 @@ module ClassMethods # @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/spec/cancan/model_adapters/active_record_adapter_spec.rb b/spec/cancan/model_adapters/active_record_adapter_spec.rb index df2a019e2..2ac33756b 100644 --- a/spec/cancan/model_adapters/active_record_adapter_spec.rb +++ b/spec/cancan/model_adapters/active_record_adapter_spec.rb @@ -817,6 +817,101 @@ class Transaction < ActiveRecord::Base 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 From 9a96e2e4ea3c6af018fe01d6398a9f68171f40f0 Mon Sep 17 00:00:00 2001 From: Alex Ghiculescu Date: Fri, 18 Dec 2020 11:52:38 -0600 Subject: [PATCH 03/21] https://github.com/CanCanCommunity/cancancan/pull/669#issuecomment-748019539 --- .github/workflows/test.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dc23084e1..89c4189e9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -60,10 +60,9 @@ jobs: - name: Install bundler run: gem install bundler - # https://github.com/oracle/truffleruby/issues/62#issuecomment-362065726 - # TODO: can't get this to work on Github actions. see comments in https://github.com/CanCanCommunity/cancancan/pull/669 + # https://github.com/CanCanCommunity/cancancan/pull/669#issuecomment-748019539 - name: Nokogiri support for Truffleruby - run: BUNDLE_GEMFILE=${{ matrix.gemfile }} bundle config build.nokogiri --use-system-libraries + run: sudo apt-get -yqq install libxml2-dev libxslt-dev if: ${{ matrix.ruby == 'truffleruby' }} - name: Install gems From 27db5911e17a4827100f37431f11db003e04af97 Mon Sep 17 00:00:00 2001 From: Alex Ghiculescu Date: Fri, 18 Dec 2020 11:53:35 -0600 Subject: [PATCH 04/21] put bundle gemfile in env --- .github/workflows/test.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 89c4189e9..8d680725b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,6 +32,9 @@ jobs: - gemfile: 'gemfiles/activerecord_master.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 @@ -66,13 +69,13 @@ jobs: if: ${{ matrix.ruby == 'truffleruby' }} - name: Install gems - run: BUNDLE_GEMFILE=${{ matrix.gemfile }} bundle install --jobs 2 + run: bundle install --jobs 2 - name: Run linter - run: BUNDLE_GEMFILE=${{ matrix.gemfile }} bundle exec rubocop + run: bundle exec rubocop - name: Run tests on sqlite - run: DB=sqlite BUNDLE_GEMFILE=${{ matrix.gemfile }} bundle exec rake + run: DB=sqlite bundle exec rake - name: Run tests on postgres - run: DB=postgres BUNDLE_GEMFILE=${{ matrix.gemfile }} bundle exec rake + run: DB=postgres bundle exec rake From 4c099f7c9802178a7779030a84aed56bbdf50a35 Mon Sep 17 00:00:00 2001 From: Alex Ghiculescu Date: Fri, 18 Dec 2020 11:55:47 -0600 Subject: [PATCH 05/21] actually test truffleruby --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8d680725b..40ff27fa3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,7 +14,7 @@ jobs: strategy: fail-fast: false matrix: - ruby: ['2.4', '2.5', '2.6', '2.7', 'jruby'] + ruby: ['2.4', '2.5', '2.6', '2.7', 'jruby', 'truffleruby'] 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', 'gemfiles/activerecord_6.1.0.gemfile', 'gemfiles/activerecord_master.gemfile'] exclude: - gemfile: 'gemfiles/activerecord_4.2.0.gemfile' From fbe6c5dab79c4816b52636f426ac12934f5e9607 Mon Sep 17 00:00:00 2001 From: Alex Ghiculescu Date: Tue, 29 Dec 2020 14:23:21 -0500 Subject: [PATCH 06/21] restore .travis.yml --- .travis.yml | 82 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..bffcb1a99 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,82 @@ +language: ruby +cache: bundler +addons: + postgresql: "9.6" +rvm: + - 2.4.2 + - 2.5.1 + - 2.6.3 + - 2.7.0 + - ruby-head + - jruby-9.1.17.0 + - jruby-9.2.11.1 + - jruby-head + - truffleruby-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 + - gemfiles/activerecord_6.1.0.gemfile + - gemfiles/activerecord_master.gemfile +env: + - DB=sqlite + - DB=postgres + +matrix: + fast_finish: true + exclude: + - rvm: 2.4.2 + gemfile: gemfiles/activerecord_6.0.0.gemfile + - rvm: 2.2.6 + gemfile: gemfiles/activerecord_6.1.0.gemfile + - rvm: 2.3.5 + gemfile: gemfiles/activerecord_6.1.0.gemfile + - rvm: 2.4.2 + gemfile: gemfiles/activerecord_6.1.0.gemfile + - rvm: 2.2.6 + gemfile: gemfiles/activerecord_master.gemfile + - rvm: 2.3.5 + gemfile: gemfiles/activerecord_master.gemfile + - rvm: 2.4.2 + gemfile: gemfiles/activerecord_master.gemfile + - rvm: 2.7.0 + gemfile: gemfiles/activerecord_4.2.0.gemfile + - rvm: ruby-head + gemfile: gemfiles/activerecord_4.2.0.gemfile + - rvm: truffleruby-head + gemfile: gemfiles/activerecord_4.2.0.gemfile + - rvm: jruby-9.1.17.0 + gemfile: gemfiles/activerecord_5.0.2.gemfile + - rvm: jruby-9.1.17.0 + gemfile: gemfiles/activerecord_6.0.0.gemfile + - rvm: jruby-9.1.17.0 + gemfile: gemfiles/activerecord_6.1.0.gemfile + - rvm: jruby-9.1.17.0 + gemfile: gemfiles/activerecord_master.gemfile + - rvm: jruby-9.2.11.1 + gemfile: gemfiles/activerecord_5.0.2.gemfile + - rvm: jruby-9.2.11.1 + gemfile: gemfiles/activerecord_6.0.0.gemfile + - rvm: jruby-9.2.11.1 + gemfile: gemfiles/activerecord_6.1.0.gemfile + - rvm: jruby-9.2.11.1 + gemfile: gemfiles/activerecord_master.gemfile + allow_failures: + - rvm: ruby-head + - rvm: jruby-head + +notifications: + email: + recipients: + - alessandro.rodi@renuo.ch + on_success: change + on_failure: change +before_install: + - rvm get stable + - gem update --system + - gem install bundler +script: + - bundle exec rubocop && bundle exec rake From 3ff31f766b0ce8cb46111af4a39e1ae4718204ec Mon Sep 17 00:00:00 2001 From: Ahmad hamza Date: Mon, 11 Jan 2021 14:52:27 +0530 Subject: [PATCH 07/21] Added page links --- docs/Defining-Abilities-with-Blocks.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/Defining-Abilities-with-Blocks.md b/docs/Defining-Abilities-with-Blocks.md index 14f8506b5..1c16c114b 100644 --- a/docs/Defining-Abilities-with-Blocks.md +++ b/docs/Defining-Abilities-with-Blocks.md @@ -94,8 +94,8 @@ Here the block will be triggered for every `can?` check, even when only a class ## Additional Docs -* [[Defining Abilities]] -* [[Checking Abilities]] -* [[Testing Abilities]] -* [[Debugging Abilities]] -* [[Ability Precedence]] +* [Defining Abilities](./Defining-Abilities.md) +* [Checking Abilities](./Checking-Abilities.md) +* [Testing Abilities](./Testing-Abilities.md) +* [Debugging Abilities](./Debugging-Abilities.md) +* [Ability Precedence](./Ability-Precedence.md) From a307c7504e4d882eec02f99fc6dc78cb6f7a84d6 Mon Sep 17 00:00:00 2001 From: Andrew Culver Date: Thu, 21 Jan 2021 19:16:44 -0500 Subject: [PATCH 08/21] Add Bullet Train Sponsorship Logo --- README.md | 5 +++++ logo/bullet_train.png | Bin 0 -> 40893 bytes 2 files changed, 5 insertions(+) create mode 100644 logo/bullet_train.png diff --git a/README.md b/README.md index 1758bae2d..475829469 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,11 @@ of models automatically and reduce duplicated code.

+ + Bullet Train + +
+
Do you want to sponsor CanCanCan and show your logo here? Check our [Sponsors Page](https://github.com/sponsors/coorasse). diff --git a/logo/bullet_train.png b/logo/bullet_train.png new file mode 100644 index 0000000000000000000000000000000000000000..0e40a801d7c776329d1a5c13599c40c038db8174 GIT binary patch literal 40893 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 ixN63YRI Date: Wed, 30 Dec 2020 11:43:11 +0100 Subject: [PATCH 09/21] Use bundler-cache: true to automatically cache installed gems --- .github/workflows/test.yml | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 40ff27fa3..20a5de79c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,21 +55,16 @@ jobs: with: fetch-depth: '20' - - name: Setup Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby }} - - - name: Install bundler - run: gem install bundler - # 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: Install gems - run: bundle install --jobs 2 + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true - name: Run linter run: bundle exec rubocop From e2bf8d525d470ca53002cc4f6d0a832b6cce751a Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 30 Dec 2020 11:46:57 +0100 Subject: [PATCH 10/21] Trigger on both push and PRs --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 20a5de79c..fb1732fa0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,5 +1,5 @@ name: Test & lint -on: [push] +on: [push, pull_request] env: RAILS_ENV: test From 0ac649426170acca5fa836cb6afb2fbaf32c8b97 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 30 Dec 2020 11:56:57 +0100 Subject: [PATCH 11/21] Exclude vendor/ for RuboCop * Otherwise it would look at, e.g., vendor/bundle/ruby/2.7.0/gems/rainbow-3.0.0/.rubocop.yml and fail --- .rubocop.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.rubocop.yml b/.rubocop.yml index 214673a53..8f5637e7a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -37,4 +37,5 @@ AllCops: TargetRubyVersion: 2.2.0 Exclude: - 'gemfiles/**/*' + - 'vendor/**/*' - 'Appraisals' From 5196dfb45d8e7df6cc7663e07990417ffa41f698 Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Wed, 30 Dec 2020 12:11:30 +0100 Subject: [PATCH 12/21] Exclude Rails 4 on TruffleRuby preventatively since the next release will target Ruby 2.7 --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fb1732fa0..12b156548 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,6 +19,8 @@ jobs: exclude: - gemfile: 'gemfiles/activerecord_4.2.0.gemfile' ruby: '2.7' # rails 4.2 can't run on ruby 2.7 due to BigDecimal API change + - gemfile: 'gemfiles/activerecord_4.2.0.gemfile' + ruby: '2.7' # TruffleRuby 21.0 targets Ruby 2.7, same as above - gemfile: 'gemfiles/activerecord_6.0.0.gemfile' ruby: '2.4' # rails 6+ requires ruby 2.5+ - gemfile: 'gemfiles/activerecord_6.1.0.gemfile' From 2bc190f2554d72b07d9eb691dc13ff3f12dff798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Braun?= Date: Thu, 11 Mar 2021 13:36:36 +0100 Subject: [PATCH 13/21] Update `_filter` terminology in docs https://edgeguides.rubyonrails.org/4_2_release_notes.html#action-pack-notable-changes To avoid confusion for Rails newcomers. --- docs/Ensure-Authorization.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Ensure-Authorization.md b/docs/Ensure-Authorization.md index 0e55cd35a..6722b3c54 100644 --- a/docs/Ensure-Authorization.md +++ b/docs/Ensure-Authorization.md @@ -6,7 +6,7 @@ class ApplicationController < ActionController::Base end ``` -This will add an `after_filter` 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_filter` so you can exclude certain actions with `:only` and `:except`. +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 From d3728f07acb825e0663e98d60389794689bea22f Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Wed, 28 Apr 2021 17:05:14 +0200 Subject: [PATCH 14/21] Goodbye Travis (#707) --- .github/workflows/test.yml | 6 ++- .travis.yml | 82 -------------------------------------- README.md | 2 +- 3 files changed, 6 insertions(+), 84 deletions(-) delete mode 100644 .travis.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 12b156548..a0d20db60 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,7 +20,11 @@ jobs: - gemfile: 'gemfiles/activerecord_4.2.0.gemfile' ruby: '2.7' # rails 4.2 can't run on ruby 2.7 due to BigDecimal API change - gemfile: 'gemfiles/activerecord_4.2.0.gemfile' - ruby: '2.7' # TruffleRuby 21.0 targets Ruby 2.7, same as above + ruby: 'truffleruby' # TruffleRuby 21.0 targets Ruby 2.7, same as above + - gemfile: 'gemfiles/activerecord_master.gemfile' + ruby: '2.6' # rails 7+ requires ruby 3.0+ + - gemfile: 'gemfiles/activerecord_master.gemfile' + ruby: '2.5' # rails 7+ requires ruby 3.0+ - gemfile: 'gemfiles/activerecord_6.0.0.gemfile' ruby: '2.4' # rails 6+ requires ruby 2.5+ - gemfile: 'gemfiles/activerecord_6.1.0.gemfile' diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index bffcb1a99..000000000 --- a/.travis.yml +++ /dev/null @@ -1,82 +0,0 @@ -language: ruby -cache: bundler -addons: - postgresql: "9.6" -rvm: - - 2.4.2 - - 2.5.1 - - 2.6.3 - - 2.7.0 - - ruby-head - - jruby-9.1.17.0 - - jruby-9.2.11.1 - - jruby-head - - truffleruby-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 - - gemfiles/activerecord_6.1.0.gemfile - - gemfiles/activerecord_master.gemfile -env: - - DB=sqlite - - DB=postgres - -matrix: - fast_finish: true - exclude: - - rvm: 2.4.2 - gemfile: gemfiles/activerecord_6.0.0.gemfile - - rvm: 2.2.6 - gemfile: gemfiles/activerecord_6.1.0.gemfile - - rvm: 2.3.5 - gemfile: gemfiles/activerecord_6.1.0.gemfile - - rvm: 2.4.2 - gemfile: gemfiles/activerecord_6.1.0.gemfile - - rvm: 2.2.6 - gemfile: gemfiles/activerecord_master.gemfile - - rvm: 2.3.5 - gemfile: gemfiles/activerecord_master.gemfile - - rvm: 2.4.2 - gemfile: gemfiles/activerecord_master.gemfile - - rvm: 2.7.0 - gemfile: gemfiles/activerecord_4.2.0.gemfile - - rvm: ruby-head - gemfile: gemfiles/activerecord_4.2.0.gemfile - - rvm: truffleruby-head - gemfile: gemfiles/activerecord_4.2.0.gemfile - - rvm: jruby-9.1.17.0 - gemfile: gemfiles/activerecord_5.0.2.gemfile - - rvm: jruby-9.1.17.0 - gemfile: gemfiles/activerecord_6.0.0.gemfile - - rvm: jruby-9.1.17.0 - gemfile: gemfiles/activerecord_6.1.0.gemfile - - rvm: jruby-9.1.17.0 - gemfile: gemfiles/activerecord_master.gemfile - - rvm: jruby-9.2.11.1 - gemfile: gemfiles/activerecord_5.0.2.gemfile - - rvm: jruby-9.2.11.1 - gemfile: gemfiles/activerecord_6.0.0.gemfile - - rvm: jruby-9.2.11.1 - gemfile: gemfiles/activerecord_6.1.0.gemfile - - rvm: jruby-9.2.11.1 - gemfile: gemfiles/activerecord_master.gemfile - allow_failures: - - rvm: ruby-head - - rvm: jruby-head - -notifications: - email: - recipients: - - alessandro.rodi@renuo.ch - on_success: change - on_failure: change -before_install: - - rvm get stable - - gem update --system - - gem install bundler -script: - - bundle exec rubocop && bundle exec rake diff --git a/README.md b/README.md index 475829469..03bce0778 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![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](./docs) | From 1c9aa38bb5f6d1cca1a5d863469d9909c44755c1 Mon Sep 17 00:00:00 2001 From: Josua Schmid Date: Thu, 29 Apr 2021 08:12:18 +0200 Subject: [PATCH 15/21] Update README.md (#704) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 03bce0778..178256143 100644 --- a/README.md +++ b/README.md @@ -280,7 +280,7 @@ If you find a bug please add an [issue on GitHub](https://github.com/CanCanCommu 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. +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`. From 98205cb25d59e689c82488f3aeb16181ed314f2f Mon Sep 17 00:00:00 2001 From: Achilleas Date: Thu, 29 Apr 2021 16:36:05 +0200 Subject: [PATCH 16/21] Add Goboony logo (#709) --- README.md | 5 +++++ logo/goboony.png | Bin 0 -> 30408 bytes 2 files changed, 5 insertions(+) create mode 100644 logo/goboony.png diff --git a/README.md b/README.md index 178256143..bd5bf3ffb 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,11 @@ of models automatically and reduce duplicated code.

+ + Goboony + +
+
Do you want to sponsor CanCanCan and show your logo here? Check our [Sponsors Page](https://github.com/sponsors/coorasse). diff --git a/logo/goboony.png b/logo/goboony.png new file mode 100644 index 0000000000000000000000000000000000000000..7d77a47826f22306ca5a4e2cc3c6269f4c9a15ba GIT binary patch literal 30408 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 Ih#LC;4{(p=@Bjb+ literal 0 HcmV?d00001 From 3e52a0dc751371fde5afe40f35377b05c7918b3a Mon Sep 17 00:00:00 2001 From: Andreas Baumgart Date: Thu, 17 Jun 2021 08:54:52 +0200 Subject: [PATCH 17/21] Fix links in Defining-Abilities.md (#713) --- docs/Defining-Abilities.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Defining-Abilities.md b/docs/Defining-Abilities.md index 5e2fd564b..a5144e820 100644 --- a/docs/Defining-Abilities.md +++ b/docs/Defining-Abilities.md @@ -18,7 +18,7 @@ class Ability end ``` -The `current_user` model is passed into the initialize method, so the permissions can be modified based on any user attributes. CanCanCan makes no assumption about how roles are handled in your application. See [[Role Based Authorization]] for an example. +The `current_user` model is passed into the initialize method, so the permissions can be modified based on any user attributes. CanCanCan makes no assumption about how roles are handled in your application. See [Role Based Authorization](./Role-Based-Authorization.md) for an example. ## The `can` Method @@ -36,7 +36,7 @@ can :read, :all # user can read any object can :manage, :all # user can perform any action on any object ``` -Common actions are `:read`, `:create`, `:update` and `:destroy` but it can be anything. See [[Action Aliases]] and [[Custom Actions]] for more information on actions. +Common actions are `:read`, `:create`, `:update` and `:destroy` but it can be anything. See [Action Aliases](./Action-Aliases.md) and [Custom Actions](./Custom-Actions.md) for more information on actions. You can pass an array for either of these parameters to match any one. For example, here the user will have the ability to update or destroy both articles and comments. @@ -74,7 +74,7 @@ A hash of conditions can be passed to further restrict which records this permis can :read, Project, active: true, user_id: user.id ``` -It is important to only use database columns for these conditions so it can be reused for [[Fetching Records]]. +It is important to only use database columns for these conditions so it can be reused for [Fetching Records](./Fetching-Records.md). You can use nested hashes to define conditions on associations. Here the project can only be read if the category it belongs to is visible. From 649814200c58d1590ede26fb103de3f91a013700 Mon Sep 17 00:00:00 2001 From: Alex Ghiculescu Date: Fri, 18 Jun 2021 06:01:48 -0500 Subject: [PATCH 18/21] Add docs on securing nested resources (#708) We recently were made aware of a potential security hole in our usage of `load_and_authorize_resource :through`. Now that we have patched it, I thought some docs on how to prevent it would be helpful. --- docs/Nested-Resources.md | 42 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/docs/Nested-Resources.md b/docs/Nested-Resources.md index 3fd597d6a..18d37d1ce 100644 --- a/docs/Nested-Resources.md +++ b/docs/Nested-Resources.md @@ -28,6 +28,46 @@ If the name of the association doesn't match the resource name, for instance `ha 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 @@ -153,7 +193,7 @@ in ability.rb can :create, User, groups_users: {group: {CONDITION_ON_GROUP} } ``` -Don't forget the **inverse_of** option, is the trick to make it works correctly. +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}`) From 9b1797e61d64ab0bfe8b87b3c1456f667e4e92ab Mon Sep 17 00:00:00 2001 From: mtoneil Date: Fri, 18 Jun 2021 07:14:40 -0400 Subject: [PATCH 19/21] Don't hold unnecessary references to subjects in @rules_index (#714) * Don't hold unnecessary references to subjects in @rules_index. The @rules_index ends up holding a reference to any subject that is authorized, by virtue of the hash having a default value of an empty array. This in effect facilitiates a memory leak, by which objects can't be garbage collected until the Ability instance is discarded, even though the Ability has no use for these objects. This is especially problematic for long-running background jobs that need to authorize many objects (e.g. hundreds of thousands) against a single Ability instance. By getting rid of @rule_index's default empty array value, we can allow for better garbage collection and reduce the size of the hash. These differences add up greatly when authorizing objects at scale. * Fix RuboCop violation * Add entry to CHANGELOG * Add link definition --- CHANGELOG.md | 2 ++ lib/cancan/ability/rules.rb | 7 +++++-- spec/cancan/ability_spec.rb | 8 ++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0952e3ff..5b2fe562a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## Unreleased * [#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.1 @@ -683,3 +684,4 @@ Please read the [guide on migrating from CanCanCan 2.x to 3.0](https://github.co [@ayumu838]: https://github.com/ayumu838 [@Liberatys]: https://github.com/Liberatys [@ghiculescu]: https://github.com/ghiculescu +[@mtoneil]: https://github.com/mtoneil diff --git a/lib/cancan/ability/rules.rb b/lib/cancan/ability/rules.rb index 0d1297be7..c7491a6ec 100644 --- a/lib/cancan/ability/rules.rb +++ b/lib/cancan/ability/rules.rb @@ -19,12 +19,13 @@ def add_rule(rule) 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 @@ def possible_relevant_rules(subject) 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 diff --git a/spec/cancan/ability_spec.rb b/spec/cancan/ability_spec.rb index 7a6899db7..7c998fdb5 100644 --- a/spec/cancan/ability_spec.rb +++ b/spec/cancan/ability_spec.rb @@ -674,6 +674,14 @@ def active? 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 From d9186a45e2ebb97beee798a9fa4761fe2d75349c Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Fri, 18 Jun 2021 13:19:49 +0200 Subject: [PATCH 20/21] Update CHANGELOG --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b2fe562a..7d4b7312d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,12 @@ -## Unreleased +## 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][]) From bec9629b901bd94dcfc6368518a45711c9a7522e Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Fri, 18 Jun 2021 13:20:19 +0200 Subject: [PATCH 21/21] Bump version --- lib/cancan/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cancan/version.rb b/lib/cancan/version.rb index b94820c8a..d436325d1 100644 --- a/lib/cancan/version.rb +++ b/lib/cancan/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module CanCan - VERSION = '3.2.2'.freeze + VERSION = '3.3.0'.freeze end