diff --git a/.rubocop.yml b/.rubocop.yml index f6f797d5..eae9b658 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -30,9 +30,15 @@ Metrics/ModuleLength: - 'lib/cancan/ability.rb' - 'lib/cancan/model_adapters/active_record_adapter.rb' +Metrics/BlockLength: + Exclude: + - 'lib/cancan/matchers.rb' + - '**/*_spec.rb' + AllCops: TargetRubyVersion: 2.0 Exclude: - 'gemfiles/vendor/bundle/**/*' + - 'Appraisals' inherit_from: .rubocop_todo.yml diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 6c1303f1..1bb21e5e 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1,42 +1,53 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2016-12-14 08:11:19 +0100 using RuboCop version 0.45.0. +# on 2017-03-26 15:25:15 +0200 using RuboCop version 0.46.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: 14 +# Offense count: 12 Metrics/AbcSize: - Max: 25 + Max: 21 + +# Offense count: 3 +# Configuration parameters: CountComments. +Metrics/BlockLength: + Max: 58 -# Offense count: 5 +# Offense count: 4 Metrics/CyclomaticComplexity: Max: 9 -# Offense count: 14 +# Offense count: 13 # Configuration parameters: CountComments. Metrics/MethodLength: Max: 21 - -Metrics/BlockLength: - Max: 28 - Exclude: - - "**/*_spec.rb" - -# Offense count: 7 +# Offense count: 4 Metrics/PerceivedComplexity: - Max: 11 + Max: 10 -# TODO: due to mongoid. can't be fixed -# Offense count: 1 -Performance/FixedSize: +# Offense count: 3 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles, ProceduralMethods, FunctionalMethods, IgnoredMethods. +# SupportedStyles: line_count_based, semantic, braces_for_chaining +# ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object +# FunctionalMethods: let, let!, subject, watch +# IgnoredMethods: lambda, proc, it +Style/BlockDelimiters: Exclude: - - 'spec/cancan/model_adapters/mongoid_adapter_spec.rb' + - 'spec/cancan/matchers_spec.rb' +# Offense count: 4 +# Configuration parameters: ExpectMatchingDefinition, Regex, IgnoreExecutableScripts. +Style/FileName: + Exclude: + - 'spec/cancan/inherited_resource_spec_BACKUP_24010.rb' + - 'spec/cancan/inherited_resource_spec_BASE_24010.rb' + - 'spec/cancan/inherited_resource_spec_LOCAL_24010.rb' + - 'spec/cancan/inherited_resource_spec_REMOTE_24010.rb' -# TODO: fixing this would change the APIs # Offense count: 2 # Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist. # NamePrefix: is_, has_, have_ @@ -46,3 +57,35 @@ Style/PredicateName: Exclude: - 'spec/**/*' - 'lib/cancan/ability.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, SupportedStyles. +# SupportedStyles: space, no_space +Style/SpaceAroundEqualsInParameterDefault: + Exclude: + - 'spec/cancan/inherited_resource_spec_BASE_24010.rb' + +# Offense count: 4 +# Cop supports --auto-correct. +# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SupportedStyles. +# SupportedStyles: space, no_space, compact +Style/SpaceInsideHashLiteralBraces: + Exclude: + - 'spec/cancan/inherited_resource_spec_BASE_24010.rb' + +# Offense count: 1 +# Cop supports --auto-correct. +Style/TrailingWhitespace: + Exclude: + - 'lib/cancan.rb' + + +# has a bug +Style/FileName: + Exclude: + - 'Appraisals' + +# disagree +Lint/AmbiguousBlockAssociation: + Enabled: false diff --git a/.travis.yml b/.travis.yml index baaee794..2c9a4f53 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,32 +2,37 @@ language: ruby cache: bundler sudo: false rvm: - - 2.0.0 - - 2.1.0 - 2.2.0 - - 2.2.2 + - 2.2.7 + - 2.3.4 + - 2.4.1 - jruby-9.0.5.0 + - jruby-9.1.8.0 gemfile: - - gemfiles/activerecord_3.2.gemfile - - gemfiles/activerecord_4.0.gemfile - - gemfiles/activerecord_4.1.gemfile - gemfiles/activerecord_4.2.gemfile - - gemfiles/activerecord_5.0.gemfile - - gemfiles/mongoid_2.x.gemfile - - gemfiles/sequel_3.x.gemfile + - gemfiles/activerecord_5.0.2.gemfile + - gemfiles/activerecord_5.1.0.gemfile services: - mongodb matrix: fast_finish: true exclude: - - rvm: 2.0.0 - gemfile: gemfiles/activerecord_5.0.gemfile - - rvm: 2.1.0 - gemfile: gemfiles/activerecord_5.0.gemfile - rvm: 2.2.0 - gemfile: gemfiles/activerecord_5.0.gemfile + gemfile: gemfiles/activerecord_5.0.2.gemfile + - rvm: 2.2.0 + gemfile: gemfiles/activerecord_5.1.0.gemfile + - rvm: 2.2.7 + gemfile: gemfiles/activerecord_5.0.2.gemfile + - rvm: 2.2.7 + gemfile: gemfiles/activerecord_5.1.0.gemfile + - rvm: jruby-9.0.5.0 + gemfile: gemfiles/activerecord_5.0.2.gemfile + - rvm: jruby-9.1.8.0 + gemfile: gemfiles/activerecord_5.0.2.gemfile - rvm: jruby-9.0.5.0 - gemfile: gemfiles/activerecord_5.0.gemfile + gemfile: gemfiles/activerecord_5.1.0.gemfile + - rvm: jruby-9.1.8.0 + gemfile: gemfiles/activerecord_5.1.0.gemfile notifications: email: recipients: @@ -36,4 +41,5 @@ notifications: - zora.fuchs@renuo.ch on_success: change on_failure: change - +script: + - bundle exec rubocop && bundle exec rake diff --git a/Appraisals b/Appraisals index 9cacb60e..1a41f355 100644 --- a/Appraisals +++ b/Appraisals @@ -1,105 +1,48 @@ -appraise "activerecord_3.2" do - gem "activerecord", "~> 3.2.0", :require => "active_record" - gem "actionpack", "~> 3.2.0", :require => "action_pack" +appraise 'activerecord_4.2' 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" - gem "jdbc-sqlite3" + gem 'activerecord-jdbcsqlite3-adapter' + gem 'jdbc-sqlite3' end gemfile.platforms :ruby, :mswin, :mingw do - gem "sqlite3" + gem 'sqlite3' + gem 'pg' end end -appraise "activerecord_4.0" do - gem "activerecord", "~> 4.0.5", :require => "active_record" - gem "activesupport", "~> 4.0.5", :require => "active_support/all" - gem "actionpack", "~> 4.0.5", :require => "action_pack" - - - gemfile.platforms :jruby do - gem "activerecord-jdbcsqlite3-adapter" - gem "jdbc-sqlite3" - end - - gemfile.platforms :ruby, :mswin, :mingw do - gem "sqlite3" - end -end - -appraise "activerecord_4.1" do - gem "activerecord", "~> 4.1.1", :require => "active_record" - gem "activesupport", "~> 4.1.1", :require => "active_support/all" - gem "actionpack", "~> 4.1.1", :require => "action_pack" - - gemfile.platforms :jruby do - gem "activerecord-jdbcsqlite3-adapter" - gem "jdbc-sqlite3" - end - - gemfile.platforms :ruby, :mswin, :mingw do - gem "sqlite3" - end -end - -appraise "activerecord_4.2" 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 +appraise 'activerecord_5.0.2' do + gem 'activerecord', '~> 5.0.2', require: 'active_record' + gem 'activesupport', '~> 5.0.2', require: 'active_support/all' + gem 'actionpack', '~> 5.0.2', require: 'action_pack' gemfile.platforms :jruby do - gem "activerecord-jdbcsqlite3-adapter" - gem "jdbc-sqlite3" + gem 'activerecord-jdbcsqlite3-adapter' + gem 'jdbc-sqlite3' end gemfile.platforms :ruby, :mswin, :mingw do - gem "sqlite3" - gem "pg" - end -end - -appraise "activerecord_5.0" do - gem "activerecord", "~> 5.0.0.rc1", :require => "active_record" - gem 'activesupport', '~> 5.0.0.rc1', :require => 'active_support/all' - gem "actionpack", "~> 5.0.0.rc1", :require => "action_pack" - - gemfile.platforms :jruby do - gem "activerecord-jdbcsqlite3-adapter" - gem "jdbc-sqlite3" - end - - gemfile.platforms :ruby, :mswin, :mingw do - gem "sqlite3" - gem "pg" - end -end - -appraise "mongoid_2.x" do - gem "activesupport", "~> 3.0", :require => "active_support/all" - gem "actionpack", "~> 3.0", :require => "action_pack" - gem "mongoid", "~> 2.0.0" - - gemfile.platforms :ruby, :mswin, :mingw do - gem "bson_ext", "~> 1.1" - end - - gemfile.platforms :jruby do - gem "mongo", "~> 1.9.2" + gem 'sqlite3' + gem 'pg' end end -appraise "sequel_3.x" do - gem "sequel", "~> 3.48.0" - gem "activesupport", "~> 3.0", :require => "active_support/all" - gem "actionpack", "~> 3.0", :require => "action_pack" +appraise 'activerecord_5.1.0' do + gem 'activerecord', '~> 5.1.0', require: 'active_record' + gem 'activesupport', '~> 5.1.0', require: 'active_support/all' + gem 'actionpack', '~> 5.1.0', require: 'action_pack' gemfile.platforms :jruby do - gem "jdbc-sqlite3" + gem 'activerecord-jdbcsqlite3-adapter' + gem 'jdbc-sqlite3' end gemfile.platforms :ruby, :mswin, :mingw do - gem "sqlite3" + gem 'sqlite3' + gem 'pg' end end diff --git a/CHANGELOG.rdoc b/CHANGELOG.rdoc index a7cdd574..92c7e67e 100644 --- a/CHANGELOG.rdoc +++ b/CHANGELOG.rdoc @@ -2,10 +2,23 @@ Develop Unreleased +2.0.0 (TBD) + +* Drop support for Rails < 4.2 (oliverklee) +* Drop support for ruby < 2.2 (coorasse) +* Drop support for InheritedResource (coorasse) +* Drop support for Sequel (coorasse) +* Drop support for Mongoid (coorasse) +* Add ability to rspec matcher to take array of abilities (gingray) +* Increase Performance (timraymond) (https://github.com/CanCanCommunity/cancancan/pull/204) +* Removed controller methods: skip_authorization, unauthorized! (coorasse) +* Removed options: nested, name, resource (coorasse) + 1.17.0 (March 26th, 2017) * Improve performance for the Mongoid Adapter + 1.16.0 (February 2nd, 2017) * Introduce rubocop and fixes most of the issues diff --git a/README.md b/README.md index 9b37f416..45141b93 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # CanCanCan +![CanCanCan Logo](/logo/cancancan.jpg) + [![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) [![Code Climate Badge](https://codeclimate.com/github/CanCanCommunity/cancancan.svg)](https://codeclimate.com/github/CanCanCommunity/cancancan) @@ -10,7 +12,7 @@ [Screencast](http://railscasts.com/episodes/192-authorization-with-cancan) | [Gitter](https://gitter.im/CanCanCommunity/cancancan) -CanCanCan is an authorization library for Ruby 2.0+ and Ruby on Rails 3+ which restricts what resources a given user is allowed to access. +CanCanCan is an authorization library for Ruby >= 2.2.0 and Ruby on Rails >= 4.2 which restricts what resources a given user is allowed to access. All permissions are defined in a single location (the `Ability` class) and not duplicated across controllers, views, and database queries. @@ -19,10 +21,24 @@ All permissions are defined in a single location (the `Ability` class) and not d Add this to your Gemfile: - gem 'cancancan' + gem 'cancancan', '~> 1.10' and run the `bundle install` command. +For Rails < 4.2 use: + + gem 'cancancan', '~> 1.10' + +## Version 2.0 + +Version 2.0 drops support for Mongoid and Sequel. + +Please use `gem 'cancancan', '~> 1.10'` for them. + +If you are interested in supporting them, contribute to the sibling gems `cancancan-sequel` and `cancancan-mongoid`. + +Version 2.0 drops also support for Rails < 4.2 and ruby < 2.2 so, again, use the version 1 of the Gem for these. + ## Getting Started CanCanCan expects a `current_user` method to exist in the controller. @@ -85,7 +101,7 @@ See [Authorizing Controller Actions](https://github.com/CanCanCommunity/cancanca #### Strong Parameters -When using `strong_parameters` or Rails 4+, you have to sanitize inputs before saving the record, in actions such as `:create` and `:update`. +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: @@ -104,7 +120,7 @@ def update_params end ``` -For the `:create` action, CanCan will try to initialize a new instance with sanitized input by seeing if your +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` @@ -219,7 +235,7 @@ of Rails, as well as the different model adapters. When first developing, you may 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_3.0 rake`. +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. @@ -227,8 +243,14 @@ See the [CONTRIBUTING](https://github.com/CanCanCommunity/cancancan/blob/develop ## Special Thanks -CanCanCan was inspired by [declarative_authorization](https://github.com/stffn/declarative_authorization/) and -[aegis](https://github.com/makandra/aegis). +[![Renuo AG](/logo/renuo.png)](https://www.renuo.ch) + +Thanks to [Renuo](https://www.renuo.ch) for currently maintaining and supporting the project. Also many thanks to the [CanCanCan contributors](https://github.com/CanCanCommunity/cancancan/contributors). See the [CHANGELOG](https://github.com/CanCanCommunity/cancancan/blob/master/CHANGELOG.rdoc) for the full list. + +CanCanCan was inspired by [declarative_authorization](https://github.com/stffn/declarative_authorization/) and +[aegis](https://github.com/makandra/aegis). + + diff --git a/Rakefile b/Rakefile index c9306b20..5f62d901 100644 --- a/Rakefile +++ b/Rakefile @@ -1,13 +1,9 @@ require 'bundler/gem_tasks' require 'rspec/core/rake_task' -require 'rubocop/rake_task' - -desc 'Run Rubocop' -RuboCop::RakeTask.new desc 'Run RSpec' RSpec::Core::RakeTask.new do |t| t.verbose = false end -task default: [:rubocop, :spec] +task default: :spec diff --git a/cancancan.gemspec b/cancancan.gemspec index 58b7f58f..92ce5c2a 100644 --- a/cancancan.gemspec +++ b/cancancan.gemspec @@ -1,4 +1,5 @@ # coding: utf-8 + lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'cancan/version' @@ -14,16 +15,14 @@ Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.license = 'MIT' - s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR) - s.test_files = `git ls-files -- Appraisals {spec,features,gemfiles}/*`.split($INPUT_RECORD_SEPARATOR) - s.executables = `git ls-files -- bin/*`.split($INPUT_RECORD_SEPARATOR).map { |f| File.basename(f) } + s.files = `git ls-files lib init.rb cancancan.gemspec`.split($INPUT_RECORD_SEPARATOR) s.require_paths = ['lib'] s.required_ruby_version = '>= 2.0.0' s.add_development_dependency 'bundler', '~> 1.3' s.add_development_dependency 'rubocop', '~> 0.46' - s.add_development_dependency 'rake', '~> 10.1.1' - s.add_development_dependency 'rspec', '~> 3.2.0' - s.add_development_dependency 'appraisal', '>= 2.0.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 'appraisal', '~> 2.0', '>= 2.0.0' end diff --git a/gemfiles/activerecord_3.2.gemfile b/gemfiles/activerecord_3.2.gemfile deleted file mode 100644 index 20f06238..00000000 --- a/gemfiles/activerecord_3.2.gemfile +++ /dev/null @@ -1,17 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "activerecord", "~> 3.2.0", :require => "active_record" -gem "actionpack", "~> 3.2.0", :require => "action_pack" - -platforms :jruby do - gem "activerecord-jdbcsqlite3-adapter" - gem "jdbc-sqlite3" -end - -platforms :ruby, :mswin, :mingw do - gem "sqlite3" -end - -gemspec :path => "../" diff --git a/gemfiles/activerecord_4.0.gemfile b/gemfiles/activerecord_5.0.2.gemfile similarity index 55% rename from gemfiles/activerecord_4.0.gemfile rename to gemfiles/activerecord_5.0.2.gemfile index 1803439c..68b527f1 100644 --- a/gemfiles/activerecord_4.0.gemfile +++ b/gemfiles/activerecord_5.0.2.gemfile @@ -2,9 +2,9 @@ source "https://rubygems.org" -gem "activerecord", "~> 4.0.5", :require => "active_record" -gem "activesupport", "~> 4.0.5", :require => "active_support/all" -gem "actionpack", "~> 4.0.5", :require => "action_pack" +gem "activerecord", "~> 5.0.2", :require => "active_record" +gem "activesupport", "~> 5.0.2", :require => "active_support/all" +gem "actionpack", "~> 5.0.2", :require => "action_pack" platforms :jruby do gem "activerecord-jdbcsqlite3-adapter" @@ -13,6 +13,7 @@ end platforms :ruby, :mswin, :mingw do gem "sqlite3" + gem "pg" end gemspec :path => "../" diff --git a/gemfiles/activerecord_5.0.gemfile b/gemfiles/activerecord_5.0.gemfile index 0f2de615..7ca0de8c 100644 --- a/gemfiles/activerecord_5.0.gemfile +++ b/gemfiles/activerecord_5.0.gemfile @@ -5,6 +5,7 @@ source "https://rubygems.org" gem "activerecord", "~> 5.0.0.rc1", :require => "active_record" gem "activesupport", "~> 5.0.0.rc1", :require => "active_support/all" gem "actionpack", "~> 5.0.0.rc1", :require => "action_pack" +gem "rubocop", "0.48.1" platforms :jruby do gem "activerecord-jdbcsqlite3-adapter" diff --git a/gemfiles/activerecord_4.1.gemfile b/gemfiles/activerecord_5.1.0.gemfile similarity index 55% rename from gemfiles/activerecord_4.1.gemfile rename to gemfiles/activerecord_5.1.0.gemfile index 4e3a05b4..2b136a9f 100644 --- a/gemfiles/activerecord_4.1.gemfile +++ b/gemfiles/activerecord_5.1.0.gemfile @@ -2,9 +2,9 @@ source "https://rubygems.org" -gem "activerecord", "~> 4.1.1", :require => "active_record" -gem "activesupport", "~> 4.1.1", :require => "active_support/all" -gem "actionpack", "~> 4.1.1", :require => "action_pack" +gem "activerecord", "~> 5.1.0", :require => "active_record" +gem "activesupport", "~> 5.1.0", :require => "active_support/all" +gem "actionpack", "~> 5.1.0", :require => "action_pack" platforms :jruby do gem "activerecord-jdbcsqlite3-adapter" @@ -13,6 +13,7 @@ end platforms :ruby, :mswin, :mingw do gem "sqlite3" + gem "pg" end gemspec :path => "../" diff --git a/gemfiles/mongoid_2.x.gemfile b/gemfiles/mongoid_2.x.gemfile deleted file mode 100644 index 02591c8d..00000000 --- a/gemfiles/mongoid_2.x.gemfile +++ /dev/null @@ -1,17 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "activesupport", "~> 3.0", :require => "active_support/all" -gem "actionpack", "~> 3.0", :require => "action_pack" -gem "mongoid", "~> 2.0.0" - -platforms :ruby, :mswin, :mingw do - gem "bson_ext", "~> 1.1" -end - -platforms :jruby do - gem "mongo", "~> 1.9.2" -end - -gemspec :path => "../" diff --git a/gemfiles/sequel_3.x.gemfile b/gemfiles/sequel_3.x.gemfile deleted file mode 100644 index e01bdc6a..00000000 --- a/gemfiles/sequel_3.x.gemfile +++ /dev/null @@ -1,17 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "sequel", "~> 3.48.0" -gem "activesupport", "~> 3.0", :require => "active_support/all" -gem "actionpack", "~> 3.0", :require => "action_pack" - -platforms :jruby do - gem "jdbc-sqlite3" -end - -platforms :ruby, :mswin, :mingw do - gem "sqlite3" -end - -gemspec :path => "../" diff --git a/lib/cancan.rb b/lib/cancan.rb index 95e5af1c..92a8ee88 100644 --- a/lib/cancan.rb +++ b/lib/cancan.rb @@ -5,20 +5,11 @@ require 'cancan/controller_additions' require 'cancan/model_additions' require 'cancan/exceptions' -require 'cancan/inherited_resource' require 'cancan/model_adapters/abstract_adapter' require 'cancan/model_adapters/default_adapter' if defined? ActiveRecord - require 'cancan/model_adapters/active_record_adapter' - if ActiveRecord.respond_to?(:version) && - ActiveRecord.version >= Gem::Version.new('4') - require 'cancan/model_adapters/active_record_4_adapter' - else - require 'cancan/model_adapters/active_record_3_adapter' - end + require 'cancan/model_adapters/active_record_adapter' + require 'cancan/model_adapters/active_record_4_adapter' end - -require 'cancan/model_adapters/mongoid_adapter' if defined?(Mongoid) && defined?(Mongoid::Document) -require 'cancan/model_adapters/sequel_adapter' if defined? Sequel diff --git a/lib/cancan/ability.rb b/lib/cancan/ability.rb index a45719cf..a7661a43 100644 --- a/lib/cancan/ability.rb +++ b/lib/cancan/ability.rb @@ -414,7 +414,7 @@ def relevant_rules_for_query(action, subject) def default_alias_actions { - read: [:index, :show], + read: %i[index show], create: [:new], update: [:edit] } diff --git a/lib/cancan/controller_additions.rb b/lib/cancan/controller_additions.rb index 1ff4529b..258b49d8 100644 --- a/lib/cancan/controller_additions.rb +++ b/lib/cancan/controller_additions.rb @@ -256,8 +256,6 @@ def skip_authorize_resource(*args) # check_authorization :unless => :devise_controller? # def check_authorization(options = {}) - method_name = active_support_4? ? :after_action : :after_filter - block = proc do |controller| next if controller.instance_variable_defined?(:@_authorized) next if options[:if] && !controller.send(options[:if]) @@ -267,7 +265,7 @@ def check_authorization(options = {}) 'Add skip_authorization_check to bypass this check.' end - send(method_name, options.slice(:only, :except), &block) + send(:after_action, options.slice(:only, :except), &block) end # Call this in the class of a controller to skip the check_authorization behavior on the actions. @@ -278,32 +276,17 @@ def check_authorization(options = {}) # # Any arguments are passed to the +before_action+ it triggers. def skip_authorization_check(*args) - method_name = active_support_4? ? :before_action : :before_filter block = proc { |controller| controller.instance_variable_set(:@_authorized, true) } - send(method_name, *args, &block) - end - - def skip_authorization(*_args) - raise ImplementationRemoved, - 'The CanCan skip_authorization method has been renamed to skip_authorization_check. '\ - 'Please update your code.' + send(:before_action, *args, &block) end def cancan_resource_class - if ancestors.map(&:to_s).include? 'InheritedResources::Actions' - InheritedResource - else - ControllerResource - end + ControllerResource end def cancan_skipper @_cancan_skipper ||= { authorize: {}, load: {} } end - - def active_support_4? - ActiveSupport.respond_to?(:version) && ActiveSupport.version >= Gem::Version.new('4') - end end def self.included(base) @@ -352,10 +335,6 @@ def authorize!(*args) current_ability.authorize!(*args) end - def unauthorized!(_message = nil) - raise ImplementationRemoved, 'The unauthorized! method has been removed from CanCan, use authorize! instead.' - end - # Creates and returns the current user's ability and caches it. If you # want to override how the Ability is defined then this is the place. # Just define the method in the controller to change behavior. diff --git a/lib/cancan/controller_resource.rb b/lib/cancan/controller_resource.rb index 5b639db6..492584dd 100644 --- a/lib/cancan/controller_resource.rb +++ b/lib/cancan/controller_resource.rb @@ -14,11 +14,7 @@ def self.add_before_action(controller_class, method, *args) end def self.before_callback_name(options) - if ActiveSupport.respond_to?(:version) && ActiveSupport.version >= Gem::Version.new('4') - options.delete(:prepend) ? :prepend_before_action : :before_action - else - options.delete(:prepend) ? :prepend_before_filter : :before_filter - end + options.delete(:prepend) ? :prepend_before_action : :before_action end def initialize(controller, *args) @@ -26,12 +22,6 @@ def initialize(controller, *args) @params = controller.params @options = args.extract_options! @name = args.first - nested_err = 'The :nested option is no longer supported, instead use :through with separate load/authorize call.' - raise CanCan::ImplementationRemoved, nested_err if @options[:nested] - name_err = 'The :name option is no longer supported, instead pass the name as the first argument.' - raise CanCan::ImplementationRemoved, name_err if @options[:name] - resource_err = 'The :resource option has been renamed back to :class, use false if no class.' - raise CanCan::ImplementationRemoved, resource_err if @options[:resource] end def load_and_authorize_resource @@ -138,7 +128,7 @@ def parent_authorization_action end def id_param - @params[id_param_key].to_s if @params[id_param_key] + @params[id_param_key].to_s if @params[id_param_key].present? end def id_param_key @@ -205,13 +195,11 @@ def resource_base def resource_base_through if parent_resource - base = if @options[:singleton] - resource_class - else - parent_resource.send(@options[:through_association] || name.to_s.pluralize) - end - base = base.scoped if base.respond_to?(:scoped) && active_record_3? - base + if @options[:singleton] + resource_class + else + parent_resource.send(@options[:through_association] || name.to_s.pluralize) + end elsif @options[:shallow] resource_class else @@ -220,10 +208,6 @@ def resource_base_through end end - def active_record_3? - defined?(ActiveRecord) && ActiveRecord::VERSION::MAJOR == 3 - end - def parent_name @options[:through] && [@options[:through]].flatten.detect { |i| fetch_parent(i) } end @@ -313,11 +297,11 @@ def collection_actions end def new_actions - [:new, :create] + Array(@options[:new]) + %i[new create] + Array(@options[:new]) end def save_actions - [:create, :update] + %i[create update] end private diff --git a/lib/cancan/inherited_resource.rb b/lib/cancan/inherited_resource.rb deleted file mode 100644 index 61bd3331..00000000 --- a/lib/cancan/inherited_resource.rb +++ /dev/null @@ -1,20 +0,0 @@ -module CanCan - # For use with Inherited Resources - class InheritedResource < ControllerResource # :nodoc: - def load_resource_instance - if parent? - @controller.send :association_chain - @controller.instance_variable_get("@#{instance_name}") - elsif new_actions.include? @params[:action].to_sym - resource = @controller.send :build_resource - assign_attributes(resource) - else - @controller.send :resource - end - end - - def resource_base - @controller.send :end_of_association_chain - end - end -end diff --git a/lib/cancan/matchers.rb b/lib/cancan/matchers.rb index a42e205c..13110b57 100644 --- a/lib/cancan/matchers.rb +++ b/lib/cancan/matchers.rb @@ -9,7 +9,13 @@ Kernel.const_get(rspec_module)::Matchers.define :be_able_to do |*args| match do |ability| - ability.can?(*args) + actions = args.first + if actions.is_a? Array + break false if actions.empty? + actions.all? { |action| ability.can?(action, *args[1..-1]) } + else + ability.can?(*args) + end end # Check that RSpec is < 2.99 diff --git a/lib/cancan/model_adapters/active_record_3_adapter.rb b/lib/cancan/model_adapters/active_record_3_adapter.rb deleted file mode 100644 index 5527bb1c..00000000 --- a/lib/cancan/model_adapters/active_record_3_adapter.rb +++ /dev/null @@ -1,16 +0,0 @@ -module CanCan - module ModelAdapters - class ActiveRecord3Adapter < AbstractAdapter - include ActiveRecordAdapter - def self.for_class?(model_class) - model_class <= ActiveRecord::Base - end - - private - - def build_relation(*where_conditions) - @model_class.where(*where_conditions).includes(joins) - end - end - end -end diff --git a/lib/cancan/model_adapters/active_record_4_adapter.rb b/lib/cancan/model_adapters/active_record_4_adapter.rb index 627008f4..5a8f2ef9 100644 --- a/lib/cancan/model_adapters/active_record_4_adapter.rb +++ b/lib/cancan/model_adapters/active_record_4_adapter.rb @@ -8,9 +8,7 @@ def self.for_class?(model_class) # TODO: this should be private def self.override_condition_matching?(subject, name, _value) - # ActiveRecord introduced enums in version 4.1. - (ActiveRecord::VERSION::MAJOR > 4 || ActiveRecord::VERSION::MINOR >= 1) && - subject.class.defined_enums.include?(name.to_s) + subject.class.defined_enums.include?(name.to_s) end # TODO: this should be private diff --git a/lib/cancan/model_adapters/active_record_adapter.rb b/lib/cancan/model_adapters/active_record_adapter.rb index bce1c25f..91d230b7 100644 --- a/lib/cancan/model_adapters/active_record_adapter.rb +++ b/lib/cancan/model_adapters/active_record_adapter.rb @@ -144,6 +144,6 @@ def clean_joins(joins_hash) end end -ActiveRecord::Base.class_eval do - include CanCan::ModelAdditions +ActiveSupport.on_load(:active_record) do + send :include, CanCan::ModelAdditions end diff --git a/lib/cancan/model_adapters/mongoid_adapter.rb b/lib/cancan/model_adapters/mongoid_adapter.rb deleted file mode 100644 index 87eff16b..00000000 --- a/lib/cancan/model_adapters/mongoid_adapter.rb +++ /dev/null @@ -1,80 +0,0 @@ -module CanCan - module ModelAdapters - class MongoidAdapter < AbstractAdapter - def self.for_class?(model_class) - model_class <= Mongoid::Document - end - - def self.override_conditions_hash_matching?(subject, conditions) - conditions.any? do |k, _v| - key_is_not_symbol = -> { !k.is_a?(Symbol) } - subject_value_is_array = lambda do - subject.respond_to?(k) && subject.send(k).is_a?(Array) - end - - key_is_not_symbol.call || subject_value_is_array.call - end - end - - def self.matches_conditions_hash?(subject, conditions) - # To avoid hitting the db, retrieve the raw Mongo selector from - # the Mongoid Criteria and use Mongoid::Matchers#matches? - subject.matches?(subject.class.where(conditions).selector) - end - - def database_records - if @rules.empty? - @model_class.where(_id: { '$exists' => false, '$type' => 7 }) # return no records in Mongoid - elsif @rules.size == 1 && @rules[0].conditions.is_a?(Mongoid::Criteria) - @rules[0].conditions - else - # we only need to process can rules if - # there are no rules with empty conditions - database_records_from_multiple_rules - end - end - - def database_records_from_multiple_rules - rules = @rules.reject { |rule| rule.conditions.empty? && rule.base_behavior } - process_can_rules = @rules.count == rules.count - - rules.inject(@model_class.all) do |records, rule| - if process_can_rules && rule.base_behavior - records.or simplify_relations(@model_class, rule.conditions) - elsif !rule.base_behavior - records.excludes simplify_relations(@model_class, rule.conditions) - else - records - end - end - end - - private - - # Look for criteria on relations and replace with simple id queries - # eg. - # {user: {:tags.all => []}} becomes {"user_id" => {"$in" => [__, ..]}} - # {user: {:session => {:tags.all => []}}} becomes {"user_id" => {"session_id" => {"$in" => [__, ..]} }} - def simplify_relations(model_class, conditions) - model_relations = model_class.relations.with_indifferent_access - Hash[ - conditions.map do |k, v| - if (relation = model_relations[k]) - relation_class_name = relation[:class_name].blank? ? k.to_s.classify : relation[:class_name] - v = simplify_relations(relation_class_name.constantize, v) - relation_ids = relation_class_name.constantize.where(v).distinct(:_id) - k = "#{k}_id" - v = { '$in' => relation_ids } - end - [k, v] - end - ] - end - end - end -end - -# simplest way to add `accessible_by` to all Mongoid Documents -module Mongoid::Document::ClassMethods - include CanCan::ModelAdditions::ClassMethods -end diff --git a/lib/cancan/model_adapters/sequel_adapter.rb b/lib/cancan/model_adapters/sequel_adapter.rb deleted file mode 100644 index 5044a21d..00000000 --- a/lib/cancan/model_adapters/sequel_adapter.rb +++ /dev/null @@ -1,87 +0,0 @@ -module CanCan - module ModelAdapters - class SequelAdapter < AbstractAdapter - def self.for_class?(model_class) - model_class <= Sequel::Model - end - - def self.find(model_class, id) - model_class[id] - end - - def self.override_condition_matching?(_subject, _name, value) - value.is_a?(Hash) - end - - def self.matches_condition?(subject, name, value) - obj = subject.send(name) - if obj.nil? - false - else - value.each do |k, v| - if v.is_a?(Hash) - return false unless matches_condition?(obj, k, v) - elsif obj.send(k) != v - return false - end - end - end - end - - def database_records - if @rules.empty? - @model_class.where('1=0') - else - # only need to process can rules if there are no can rule with empty conditions - rules = @rules.reject { |rule| rule.base_behavior && rule.conditions.empty? } - rules.reject!(&:base_behavior) if rules.count < @rules.count - - can_condition_added = false - rules.reverse.inject(@model_class.dataset) do |records, rule| - normalized_conditions = normalize_conditions(rule.conditions) - if rule.base_behavior - if can_condition_added - records.or normalized_conditions - else - can_condition_added = true - records.where normalized_conditions - end - else - records.exclude normalized_conditions - end - end - end - end - - private - - def normalize_conditions(conditions, model_class = @model_class) - return conditions unless conditions.is_a? Hash - conditions.each_with_object({}) do |(name, value), result_hash| - if value.is_a? Hash - value = value.dup - association_class = model_class.association_reflection(name).associated_class - nested_resulted = value.each_with_object({}) do |(k, v), nested| - if v.is_a?(Hash) - value.delete(k) - nested_class = association_class.association_reflection(k).associated_class - nested[k] = nested_class.where(normalize_conditions(v, association_class)) - else - nested[k] = v - end - nested - end - result_hash[name] = association_class.where(nested_resulted) - else - result_hash[name] = value - end - result_hash - end - end - end - end -end - -Sequel::Model.class_eval do - include CanCan::ModelAdditions -end diff --git a/lib/cancan/rule.rb b/lib/cancan/rule.rb index b4645569..48642712 100644 --- a/lib/cancan/rule.rb +++ b/lib/cancan/rule.rb @@ -16,8 +16,8 @@ def initialize(base_behavior, action, subject, conditions, block) raise Error, both_block_and_hash_error if conditions.is_a?(Hash) && block @match_all = action.nil? && subject.nil? @base_behavior = base_behavior - @actions = [action].flatten - @subjects = [subject].flatten + @actions = Array(action) + @subjects = Array(subject) @conditions = conditions || {} @block = block end diff --git a/lib/cancan/version.rb b/lib/cancan/version.rb index 79171d34..332d4915 100644 --- a/lib/cancan/version.rb +++ b/lib/cancan/version.rb @@ -1,3 +1,3 @@ module CanCan - VERSION = '1.17.0'.freeze + VERSION = '2.0.0'.freeze end diff --git a/logo/cancancan.jpg b/logo/cancancan.jpg new file mode 100644 index 00000000..80026102 Binary files /dev/null and b/logo/cancancan.jpg differ diff --git a/logo/renuo.png b/logo/renuo.png new file mode 100644 index 00000000..2524bcfa Binary files /dev/null and b/logo/renuo.png differ diff --git a/spec/cancan/ability_spec.rb b/spec/cancan/ability_spec.rb index e9d5cb02..e2b630a7 100644 --- a/spec/cancan/ability_spec.rb +++ b/spec/cancan/ability_spec.rb @@ -158,7 +158,7 @@ end it 'is able to specify multiple actions and match any' do - @ability.can [:read, :update], :all + @ability.can %i[read update], :all expect(@ability.can?(:read, 123)).to be(true) expect(@ability.can?(:update, 123)).to be(true) expect(@ability.can?(:count, 123)).to be(false) @@ -188,9 +188,9 @@ expected_list = { can: { manage: ['all'], learn: ['Range'] }, - cannot: { read: %w(String Hash), - index: %w(String Hash), - show: %w(String Hash), + cannot: { read: %w[String Hash], + index: %w[String Hash], + show: %w[String Hash], preview: ['Array'] } } expect(@ability.permissions).to eq(expected_list) @@ -201,8 +201,8 @@ expect(@ability.can?(:read, :stats)).to be(true) expect(@ability.can?(:update, :stats)).to be(false) expect(@ability.can?(:read, :nonstats)).to be(false) - expect(@ability.can?(:read, any: [:stats, :nonstats])).to be(true) - expect(@ability.can?(:read, any: [:nonstats, :neitherstats])).to be(false) + expect(@ability.can?(:read, any: %i[stats nonstats])).to be(true) + expect(@ability.can?(:read, any: %i[nonstats neitherstats])).to be(false) end it 'checks ancestors of class' do @@ -218,7 +218,7 @@ @ability.cannot :read, Integer expect(@ability.can?(:read, 'foo')).to be(true) expect(@ability.can?(:read, 123)).to be(false) - expect(@ability.can?(:read, any: %w(foo bar))).to be(true) + expect(@ability.can?(:read, any: %w[foo bar])).to be(true) expect(@ability.can?(:read, any: [123, 'foo'])).to be(false) expect(@ability.can?(:read, any: [123, 456])).to be(false) end @@ -261,7 +261,7 @@ it 'appends aliased actions' do @ability.alias_action :update, to: :modify @ability.alias_action :destroy, to: :modify - expect(@ability.aliased_actions[:modify]).to eq([:update, :destroy]) + expect(@ability.aliased_actions[:modify]).to eq(%i[update destroy]) end it 'clears aliased actions' do @@ -400,7 +400,7 @@ class A it 'checks permissions correctly when passing a hash of subjects with multiple definitions' do @ability.can :read, Range, string: { length: 4 } - @ability.can [:create, :read], Range, string: { upcase: 'FOO' } + @ability.can %i[create read], Range, string: { upcase: 'FOO' } expect(@ability.can?(:read, 'foo' => Range)).to be(true) expect(@ability.can?(:read, 'foobar' => Range)).to be(false) @@ -418,7 +418,7 @@ class Container < Hash it "has initial attributes based on hash conditions of 'new' action" do @ability.can :manage, Range, foo: 'foo', hash: { skip: 'hashes' } - @ability.can :create, Range, bar: 123, array: %w(skip arrays) + @ability.can :create, Range, bar: 123, array: %w[skip arrays] @ability.can :new, Range, baz: 'baz', range: 1..3 @ability.cannot :new, Range, ignore: 'me' expect(@ability.attributes_for(:new, Range)).to eq(foo: 'foo', bar: 123, baz: 'baz') diff --git a/spec/cancan/controller_additions_spec.rb b/spec/cancan/controller_additions_spec.rb index 731de96a..f7349d3d 100644 --- a/spec/cancan/controller_additions_spec.rb +++ b/spec/cancan/controller_additions_spec.rb @@ -10,10 +10,6 @@ @controller_class.send(:include, CanCan::ControllerAdditions) end - it "raises ImplementationRemoved when attempting to call 'unauthorized!' on a controller" do - expect { @controller.unauthorized! }.to raise_error(CanCan::ImplementationRemoved) - end - it 'authorize! assigns @_authorized instance variable and pass args to current ability' do allow(@controller.current_ability).to receive(:authorize!).with(:foo, :bar) @controller.authorize!(:foo, :bar) @@ -65,10 +61,10 @@ expect(cancan_resource_class = double).to receive(:load_resource) allow(CanCan::ControllerResource).to receive(:new).with(@controller, nil, foo: :bar) { cancan_resource_class } expect(@controller_class) - .to receive(callback_action(:before_action)).with(only: [:show, :index], unless: false) do |_options, &block| + .to receive(callback_action(:before_action)).with(only: %i[show index], unless: false) do |_options, &block| block.call(@controller) end - @controller_class.load_resource foo: :bar, only: [:show, :index], unless: false + @controller_class.load_resource foo: :bar, only: %i[show index], unless: false end it 'skip_authorization_check setups a before filter which sets @_authorized to true' do @@ -117,11 +113,6 @@ expect(@controller.class.cancan_resource_class).to eq(CanCan::ControllerResource) end - it 'cancan_resource_class is InheritedResource when class includes InheritedResources::Actions' do - allow(@controller.class).to receive(:ancestors) { ['InheritedResources::Actions'] } - expect(@controller.class.cancan_resource_class).to eq(CanCan::InheritedResource) - end - it 'cancan_skipper is an empty hash with :authorize and :load options and remember changes' do expect(@controller_class.cancan_skipper).to eq(authorize: {}, load: {}) @controller_class.cancan_skipper[:load] = true @@ -129,27 +120,27 @@ end it 'skip_authorize_resource adds itself to the cancan skipper with given model name and options' do - @controller_class.skip_authorize_resource(:project, only: [:index, :show]) - expect(@controller_class.cancan_skipper[:authorize][:project]).to eq(only: [:index, :show]) - @controller_class.skip_authorize_resource(only: [:index, :show]) - expect(@controller_class.cancan_skipper[:authorize][nil]).to eq(only: [:index, :show]) + @controller_class.skip_authorize_resource(:project, only: %i[index show]) + expect(@controller_class.cancan_skipper[:authorize][:project]).to eq(only: %i[index show]) + @controller_class.skip_authorize_resource(only: %i[index show]) + expect(@controller_class.cancan_skipper[:authorize][nil]).to eq(only: %i[index show]) @controller_class.skip_authorize_resource(:article) expect(@controller_class.cancan_skipper[:authorize][:article]).to eq({}) end it 'skip_load_resource adds itself to the cancan skipper with given model name and options' do - @controller_class.skip_load_resource(:project, only: [:index, :show]) - expect(@controller_class.cancan_skipper[:load][:project]).to eq(only: [:index, :show]) - @controller_class.skip_load_resource(only: [:index, :show]) - expect(@controller_class.cancan_skipper[:load][nil]).to eq(only: [:index, :show]) + @controller_class.skip_load_resource(:project, only: %i[index show]) + expect(@controller_class.cancan_skipper[:load][:project]).to eq(only: %i[index show]) + @controller_class.skip_load_resource(only: %i[index show]) + expect(@controller_class.cancan_skipper[:load][nil]).to eq(only: %i[index show]) @controller_class.skip_load_resource(:article) 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 - @controller_class.skip_load_and_authorize_resource(:project, only: [:index, :show]) - expect(@controller_class.cancan_skipper[:load][:project]).to eq(only: [:index, :show]) - expect(@controller_class.cancan_skipper[:authorize][:project]).to eq(only: [:index, :show]) + @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]) end private diff --git a/spec/cancan/controller_resource_spec.rb b/spec/cancan/controller_resource_spec.rb index 0cc4da54..baedf28b 100644 --- a/spec/cancan/controller_resource_spec.rb +++ b/spec/cancan/controller_resource_spec.rb @@ -254,7 +254,7 @@ class Dashboard; end it 'does not build a single resource when on custom collection action even with id' do params.merge!(action: 'sort', id: '123') - resource = CanCan::ControllerResource.new(controller, collection: [:sort, :list]) + resource = CanCan::ControllerResource.new(controller, collection: %i[sort list]) resource.load_resource expect(controller.instance_variable_get(:@model)).to be_nil end @@ -390,7 +390,7 @@ class Dashboard; end controller.instance_variable_set(:@category, category) allow(category.models).to receive(:find).with('123') { :some_model } - resource = CanCan::ControllerResource.new(controller, through: [:category, :user]) + resource = CanCan::ControllerResource.new(controller, through: %i[category user]) resource.load_resource expect(controller.instance_variable_get(:@model)).to eq(:some_model) end @@ -412,6 +412,13 @@ class Dashboard; end expect(controller.instance_variable_get(:@model)).to be_nil end + it 'does not try to load resource for other action if params[:id] is blank' do + params.merge!(action: 'list', id: '') + resource = CanCan::ControllerResource.new(controller) + resource.load_resource + expect(controller.instance_variable_get(:@model)).to be_nil + end + it 'finds record through has_one association with :singleton and :shallow options' do model = Model.new allow(Model).to receive(:find).with('123') { model } @@ -577,26 +584,8 @@ class Section; end expect(resource.send(:resource_class)).to eq(Section) end - it 'raises ImplementationRemoved when adding :name option' do - expect do - CanCan::ControllerResource.new(controller, name: :foo) - end.to raise_error(CanCan::ImplementationRemoved) - end - - it 'raises ImplementationRemoved exception when specifying :resource option since it is no longer used' do - expect do - CanCan::ControllerResource.new(controller, resource: Model) - end.to raise_error(CanCan::ImplementationRemoved) - end - - it 'raises ImplementationRemoved exception when passing :nested option' do - expect do - CanCan::ControllerResource.new(controller, nested: :model) - end.to raise_error(CanCan::ImplementationRemoved) - end - it 'skips resource behavior for :only actions in array' do - allow(controller_class).to receive(:cancan_skipper) { { load: { nil => { only: [:index, :show] } } } } + allow(controller_class).to receive(:cancan_skipper) { { load: { nil => { only: %i[index show] } } } } params[:action] = 'index' expect(CanCan::ControllerResource.new(controller).skip?(:load)).to be(true) expect(CanCan::ControllerResource.new(controller, :some_resource).skip?(:load)).to be(false) @@ -616,7 +605,7 @@ class Section; end end it 'skips resource behavior :except actions in array' do - allow(controller_class).to receive(:cancan_skipper) { { load: { nil => { except: [:index, :show] } } } } + allow(controller_class).to receive(:cancan_skipper) { { load: { nil => { except: %i[index show] } } } } params[:action] = 'index' expect(CanCan::ControllerResource.new(controller).skip?(:load)).to be_falsey params[:action] = 'show' diff --git a/spec/cancan/inherited_resource_spec.rb b/spec/cancan/inherited_resource_spec.rb deleted file mode 100644 index 0b28747a..00000000 --- a/spec/cancan/inherited_resource_spec.rb +++ /dev/null @@ -1,71 +0,0 @@ -require 'spec_helper' - -describe CanCan::InheritedResource do - let(:ability) { Ability.new(nil) } - let(:params) { HashWithIndifferentAccess.new(controller: 'models') } - let(:controller_class) { Class.new } - let(:controller) { controller_class.new } - - before(:each) do - class Model - attr_accessor :name - - def initialize(attributes = {}) - attributes.each do |attribute, value| - send("#{attribute}=", value) - end - end - end - - allow(controller).to receive(:params) { params } - allow(controller).to receive(:current_ability) { ability } - allow(controller_class).to receive(:cancan_skipper) { { authorize: {}, load: {} } } - end - - it 'show loads resource through controller.resource' do - params.merge!(action: 'show', id: 123) - allow(controller).to receive(:resource) { :model_resource } - CanCan::InheritedResource.new(controller).load_resource - expect(controller.instance_variable_get(:@model)).to eq(:model_resource) - end - - it 'new loads through controller.build_resource' do - params[:action] = 'new' - allow(controller).to receive(:build_resource) { :model_resource } - CanCan::InheritedResource.new(controller).load_resource - expect(controller.instance_variable_get(:@model)).to eq(:model_resource) - end - - it 'index loads through controller.association_chain when parent' do - params[:action] = 'index' - allow(controller).to receive(:association_chain) { controller.instance_variable_set(:@model, :model_resource) } - CanCan::InheritedResource.new(controller, parent: true).load_resource - expect(controller.instance_variable_get(:@model)).to eq(:model_resource) - end - - it 'index loads through controller.end_of_association_chain' do - params[:action] = 'index' - allow(Model).to receive(:accessible_by).with(ability, :index) { :projects } - allow(controller).to receive(:end_of_association_chain) { Model } - CanCan::InheritedResource.new(controller).load_resource - expect(controller.instance_variable_get(:@models)).to eq(:projects) - end - - it 'builds a new resource with attributes from current ability' do - params[:action] = 'new' - ability.can(:create, Model, name: 'from conditions') - allow(controller).to receive(:build_resource) { Struct.new(:name).new } - resource = CanCan::InheritedResource.new(controller) - resource.load_resource - expect(controller.instance_variable_get(:@model).name).to eq('from conditions') - end - - it 'overrides initial attributes with params' do - params.merge!(action: 'new', model: { name: 'from params' }) - ability.can(:create, Model, name: 'from conditions') - allow(controller).to receive(:build_resource) { Struct.new(:name).new } - resource = CanCan::ControllerResource.new(controller) - resource.load_resource - expect(controller.instance_variable_get(:@model).name).to eq('from params') - end -end diff --git a/spec/cancan/matchers_spec.rb b/spec/cancan/matchers_spec.rb index 75e59204..0e81ee82 100644 --- a/spec/cancan/matchers_spec.rb +++ b/spec/cancan/matchers_spec.rb @@ -1,29 +1,56 @@ require 'spec_helper' describe 'be_able_to' do - it 'delegates to can?' do - expect(object = double).to receive(:can?).with(:read, 123) { true } - expect(object).to be_able_to(:read, 123) - end + subject { double } - it 'reports a nice failure message for should' do - expect(object = double).to receive(:can?).with(:read, 123) { false } - expect do - expect(object).to be_able_to(:read, 123) - end.to raise_error('expected to be able to :read 123') - end + context 'check single ability' do + it 'delegates to can?' do + is_expected.to receive(:can?).with(:read, 123) { true } + is_expected.to be_able_to(:read, 123) + end + + it 'reports a nice failure message for should' do + is_expected.to receive(:can?).with(:read, 123) { false } + expect { + is_expected.to be_able_to(:read, 123) + }.to raise_error('expected to be able to :read 123') + end + + it 'reports a nice failure message for should not' do + is_expected.to receive(:can?).with(:read, 123) { true } + expect { + is_expected.to_not be_able_to(:read, 123) + }.to raise_error('expected not to be able to :read 123') + end - it 'reports a nice failure message for should not' do - expect(object = double).to receive(:can?).with(:read, 123) { true } - expect do - expect(object).to_not be_able_to(:read, 123) - end.to raise_error('expected not to be able to :read 123') + it 'delegates additional arguments to can? and reports in failure message' do + is_expected.to receive(:can?).with(:read, 123, 456) { false } + expect { + is_expected.to be_able_to(:read, 123, 456) + }.to raise_error('expected to be able to :read 123 456') + end end - it 'delegates additional arguments to can? and reports in failure message' do - expect(object = double).to receive(:can?).with(:read, 123, 456) { false } - expect do - expect(object).to be_able_to(:read, 123, 456) - end.to raise_error('expected to be able to :read 123 456') + context 'check array of abilities' do + it 'delegates to can? with array of abilities with one action' do + is_expected.to receive(:can?).with(:read, 123) { true } + is_expected.to be_able_to([:read], 123) + end + + it 'delegates to can? with array of abilities with multiple actions' do + is_expected.to receive(:can?).with(:read, 123) { true } + is_expected.to receive(:can?).with(:update, 123) { true } + is_expected.to be_able_to(%i[read update], 123) + end + + it 'delegates to can? with array of abilities with empty array' do + is_expected.not_to be_able_to([], 123) + end + + it 'delegates to can? with array of abilities with only one eligable 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) + end 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 c041ab88..9016fdc1 100644 --- a/spec/cancan/model_adapters/active_record_4_adapter_spec.rb +++ b/spec/cancan/model_adapters/active_record_4_adapter_spec.rb @@ -48,7 +48,7 @@ class Child < ActiveRecord::Base end class Shape < ActiveRecord::Base - enum color: [:red, :green, :blue] + enum color: %i[red green blue] end red = Shape.create!(color: :red) @@ -86,7 +86,7 @@ class Shape < ActiveRecord::Base end class Disc < ActiveRecord::Base - enum color: [:red, :green, :blue] + enum color: %i[red green blue] enum shape: { triangle: 3, rectangle: 4 } end diff --git a/spec/cancan/model_adapters/active_record_adapter_spec.rb b/spec/cancan/model_adapters/active_record_adapter_spec.rb index 501a8093..17417b57 100644 --- a/spec/cancan/model_adapters/active_record_adapter_spec.rb +++ b/spec/cancan/model_adapters/active_record_adapter_spec.rb @@ -317,7 +317,7 @@ class User < ActiveRecord::Base 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([:company, :project].inspect) + 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 diff --git a/spec/cancan/model_adapters/mongoid_adapter_spec.rb b/spec/cancan/model_adapters/mongoid_adapter_spec.rb deleted file mode 100644 index 387d0940..00000000 --- a/spec/cancan/model_adapters/mongoid_adapter_spec.rb +++ /dev/null @@ -1,246 +0,0 @@ -require 'spec_helper' - -if defined? CanCan::ModelAdapters::MongoidAdapter - - class MongoidCategory - include Mongoid::Document - - references_many :mongoid_projects - end - - class MongoidProject - include Mongoid::Document - - referenced_in :mongoid_category - references_many :mongoid_sub_projects - end - - class MongoidSubProject - include Mongoid::Document - - referenced_in :mongoid_project - end - - Mongoid.configure do |config| - config.master = Mongo::Connection.new('127.0.0.1', 27_017).db('cancan_mongoid_spec') - end - - describe CanCan::ModelAdapters::MongoidAdapter do - context 'Mongoid defined' do - before(:each) do - (@ability = double).extend(CanCan::Ability) - end - - after(:each) do - Mongoid.master.collections.select do |collection| - collection.name !~ /system/ - end.each(&:drop) - end - - 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) - expect(CanCan::ModelAdapters::AbstractAdapter.adapter_class(MongoidProject)) - .to eq(CanCan::ModelAdapters::MongoidAdapter) - end - - it 'finds record' do - project = MongoidProject.create - expect(CanCan::ModelAdapters::MongoidAdapter.find(MongoidProject, project.id)).to eq(project) - end - - it 'compares properties on mongoid documents with the conditions hash' do - model = MongoidProject.new - @ability.can :read, MongoidProject, id: model.id - expect(@ability).to be_able_to(:read, model) - end - - it 'is able to read hashes when field is array' do - one_to_three = MongoidProject.create(numbers: %w(one two three)) - two_to_five = MongoidProject.create(numbers: %w(two three four five)) - - @ability.can :foo, MongoidProject, numbers: 'one' - expect(@ability).to be_able_to(:foo, one_to_three) - expect(@ability).not_to be_able_to(:foo, two_to_five) - end - - it 'returns [] when no ability is defined so no records are found' do - MongoidProject.create(title: 'Sir') - MongoidProject.create(title: 'Lord') - MongoidProject.create(title: 'Dude') - - expect(MongoidProject.accessible_by(@ability, :read).entries).to eq([]) - end - - it 'returns the correct records based on the defined ability' do - @ability.can :read, MongoidProject, title: 'Sir' - sir = MongoidProject.create(title: 'Sir') - MongoidProject.create(title: 'Lord') - MongoidProject.create(title: 'Dude') - - expect(MongoidProject.accessible_by(@ability, :read).entries).to eq([sir]) - end - - it 'returns the correct records when a mix of can and cannot rules in defined ability' do - @ability.can :manage, MongoidProject, title: 'Sir' - @ability.cannot :destroy, MongoidProject - - sir = MongoidProject.create(title: 'Sir') - MongoidProject.create(title: 'Lord') - MongoidProject.create(title: 'Dude') - - expect(MongoidProject.accessible_by(@ability, :destroy).entries).to eq([sir]) - end - - it 'is able to mix empty conditions and hashes' do - @ability.can :read, MongoidProject - @ability.can :read, MongoidProject, title: 'Sir' - MongoidProject.create(title: 'Sir') - MongoidProject.create(title: 'Lord') - - expect(MongoidProject.accessible_by(@ability, :read).count).to eq(2) - end - - it 'returns everything when the defined ability is access all' do - @ability.can :manage, :all - sir = MongoidProject.create(title: 'Sir') - lord = MongoidProject.create(title: 'Lord') - dude = MongoidProject.create(title: 'Dude') - - expect(MongoidProject.accessible_by(@ability, :read).entries).to eq([sir, lord, dude]) - end - - it 'allows a scope for conditions' do - @ability.can :read, MongoidProject, MongoidProject.where(title: 'Sir') - sir = MongoidProject.create(title: 'Sir') - MongoidProject.create(title: 'Lord') - MongoidProject.create(title: 'Dude') - - expect(MongoidProject.accessible_by(@ability, :read).entries).to eq([sir]) - end - - describe 'Mongoid::Criteria where clause Symbol extensions using MongoDB expressions' do - it 'handles :field.in' do - obj = MongoidProject.create(title: 'Sir') - @ability.can :read, MongoidProject, :title.in => %w(Sir Madam) - expect(@ability.can?(:read, obj)).to eq(true) - expect(MongoidProject.accessible_by(@ability, :read)).to eq([obj]) - - obj2 = MongoidProject.create(title: 'Lord') - expect(@ability.can?(:read, obj2)).to be(false) - end - - describe 'activates only when there are Criteria in the hash' do - it 'Calls where on the model class when there are criteria' do - obj = MongoidProject.create(title: 'Bird') - @conditions = { :title.nin => %w(Fork Spoon) } - - @ability.can :read, MongoidProject, @conditions - expect(@ability).to be_able_to(:read, obj) - end - it 'Calls the base version if there are no mongoid criteria' do - obj = MongoidProject.new(title: 'Bird') - @conditions = { id: obj.id } - @ability.can :read, MongoidProject, @conditions - expect(@ability).to be_able_to(:read, obj) - end - end - - it 'handles :field.nin' do - obj = MongoidProject.create(title: 'Sir') - @ability.can :read, MongoidProject, :title.nin => %w(Lord Madam) - expect(@ability.can?(:read, obj)).to eq(true) - expect(MongoidProject.accessible_by(@ability, :read)).to eq([obj]) - - obj2 = MongoidProject.create(title: 'Lord') - expect(@ability.can?(:read, obj2)).to be(false) - end - - it 'handles :field.size' do - obj = MongoidProject.create(titles: %w(Palatin Margrave)) - @ability.can :read, MongoidProject, :titles.size => 2 - expect(@ability.can?(:read, obj)).to eq(true) - expect(MongoidProject.accessible_by(@ability, :read)).to eq([obj]) - - obj2 = MongoidProject.create(titles: %w(Palatin Margrave Marquis)) - expect(@ability.can?(:read, obj2)).to be(false) - end - - it 'handles :field.exists' do - obj = MongoidProject.create(titles: %w(Palatin Margrave)) - @ability.can :read, MongoidProject, :titles.exists => true - expect(@ability.can?(:read, obj)).to eq(true) - expect(MongoidProject.accessible_by(@ability, :read)).to eq([obj]) - - obj2 = MongoidProject.create - expect(@ability.can?(:read, obj2)).to be(false) - end - - it 'handles :field.gt' do - obj = MongoidProject.create(age: 50) - @ability.can :read, MongoidProject, :age.gt => 45 - expect(@ability.can?(:read, obj)).to eq(true) - expect(MongoidProject.accessible_by(@ability, :read)).to eq([obj]) - - obj2 = MongoidProject.create(age: 40) - expect(@ability.can?(:read, obj2)).to be(false) - end - - it 'handles instance not saved to database' do - obj = MongoidProject.new(title: 'Sir') - @ability.can :read, MongoidProject, :title.in => %w(Sir Madam) - expect(@ability.can?(:read, obj)).to eq(true) - - # accessible_by only returns saved records - expect(MongoidProject.accessible_by(@ability, :read).entries).to eq([]) - - obj2 = MongoidProject.new(title: 'Lord') - expect(@ability.can?(:read, obj2)).to be(false) - end - end - - it 'calls where with matching ability conditions' do - obj = MongoidProject.create(foo: { bar: 1 }) - @ability.can :read, MongoidProject, foo: { bar: 1 } - expect(MongoidProject.accessible_by(@ability, :read).entries.first).to eq(obj) - end - - it 'excludes from the result if set to cannot' do - obj = MongoidProject.create(bar: 1) - MongoidProject.create(bar: 2) - @ability.can :read, MongoidProject - @ability.cannot :read, MongoidProject, bar: 2 - expect(MongoidProject.accessible_by(@ability, :read).entries).to eq([obj]) - end - - it 'combines the rules' do - obj = MongoidProject.create(bar: 1) - obj2 = MongoidProject.create(bar: 2) - MongoidProject.create(bar: 3) - @ability.can :read, MongoidProject, bar: 1 - @ability.can :read, MongoidProject, bar: 2 - expect(MongoidProject.accessible_by(@ability, :read).entries).to match_array([obj, obj2]) - end - - it 'does not allow to fetch records when ability with just block present' do - @ability.can :read, MongoidProject do - false - end - expect do - MongoidProject.accessible_by(@ability) - end.to raise_error(CanCan::Error) - end - - it 'can handle nested queries for accessible_by' do - @ability.can :read, MongoidSubProject, mongoid_project: { mongoid_category: { name: 'Authorization' } } - cat1 = MongoidCategory.create name: 'Authentication' - cat2 = MongoidCategory.create name: 'Authorization' - proj1 = cat1.mongoid_projects.create name: 'Proj1' - proj2 = cat2.mongoid_projects.create name: 'Proj2' - sub1 = proj1.mongoid_sub_projects.create name: 'Sub1' - proj2.mongoid_sub_projects.create name: 'Sub2' - expect(MongoidSubProject.accessible_by(@ability)).to match_array([sub1]) - end - end - end -end diff --git a/spec/cancan/model_adapters/sequel_adapter_spec.rb b/spec/cancan/model_adapters/sequel_adapter_spec.rb deleted file mode 100644 index 21063824..00000000 --- a/spec/cancan/model_adapters/sequel_adapter_spec.rb +++ /dev/null @@ -1,129 +0,0 @@ -require 'spec_helper' - -if defined? CanCan::ModelAdapters::SequelAdapter - describe CanCan::ModelAdapters::SequelAdapter do - DB = if RUBY_PLATFORM == 'java' - Sequel.connect('jdbc:sqlite:db.sqlite3') - else - Sequel.sqlite - end - - DB.create_table :users do - primary_key :id - String :name - end - - class User < Sequel::Model - one_to_many :articles - end - - DB.create_table :articles do - primary_key :id - String :name - TrueClass :published - TrueClass :secret - Integer :priority - foreign_key :user_id, :users - end - - class Article < Sequel::Model - many_to_one :user - one_to_many :comments - end - - DB.create_table :comments do - primary_key :id - TrueClass :spam - foreign_key :article_id, :articles - end - - class Comment < Sequel::Model - many_to_one :article - end - - before(:each) do - Comment.dataset.delete - Article.dataset.delete - User.dataset.delete - (@ability = double).extend(CanCan::Ability) - end - - it 'should be for only sequel model classes' do - expect(CanCan::ModelAdapters::SequelAdapter).to_not be_for_class(Object) - expect(CanCan::ModelAdapters::SequelAdapter).to be_for_class(Article) - expect(CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article)).to eq CanCan::ModelAdapters::SequelAdapter - end - - it 'should find record' do - article = Article.create - expect(CanCan::ModelAdapters::SequelAdapter.find(Article, article.id)).to eq article - end - - it 'should not fetch any records when no abilities are defined' do - Article.create - expect(Article.accessible_by(@ability).all).to be_empty - end - - it 'should fetch all articles when one can read all' do - @ability.can :read, Article - article = Article.create - expect(Article.accessible_by(@ability).all).to eq [article] - end - - it 'should fetch 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).all).to eq [article1] - end - - it 'should fetch 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).all).to eq([article1, article2, article3]) - end - - it 'should fetch 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).all).to eq [article1] - end - - it 'should only read 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).all).to eq [comment1] - end - - it "should only read comments for articles which are published and user is 'me'" do - @ability.can :read, Comment, article: { user: { name: 'me' }, published: true } - user1 = User.create(name: 'me') - comment1 = Comment.create(article: Article.create(published: true, user: user1)) - Comment.create(article: Article.create(published: true)) - Comment.create(article: Article.create(published: false, user: user1)) - expect(Comment.accessible_by(@ability).all).to eq [comment1] - end - - it 'should allow conditions in SQL and merge with hash conditions' do - @ability.can :read, Article, published: true - @ability.can :read, Article, ['secret=?', true], &:secret - @ability.cannot :read, Article, 'priority > 1' do |article| - article.priority > 1 - end - article1 = Article.create(published: true, secret: false, priority: 1) - article2 = Article.create(published: true, secret: true, priority: 1) - Article.create(published: true, secret: true, priority: 2) - Article.create(published: false, secret: false, priority: 2) - expect(Article.accessible_by(@ability).all).to eq [article1, article2] - end - end -end