From 5d5cac41151c361ca3940d0fcfa9d990bdaa09b2 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Mon, 2 Aug 2021 13:24:48 +0200 Subject: [PATCH 01/53] New documentation (#722) --- README.md | 176 ++--------- docs/Ability-Precedence.md | 49 --- docs/Ability-for-Other-Users.md | 34 --- docs/Accessing-request-data.md | 27 -- docs/Action-Aliases.md | 32 -- ...uthorization-for-Namespaced-Controllers.md | 51 ---- docs/Authorizing-controller-actions.md | 210 ------------- docs/Changing-Defaults.md | 46 --- docs/Checking-Abilities.md | 51 ---- docs/Controller-Authorization-Example.md | 70 ----- docs/Custom-Actions.md | 27 -- docs/Defining-Abilities-with-Blocks.md | 101 ------ docs/Defining-Abilities-with-Hashes.md | 5 - docs/Defining-Abilities.md | 173 ----------- docs/Defining-Abilities:-Best-Practices.md | 101 ------ docs/Devise.md | 5 +- docs/Ensure-Authorization.md | 40 --- docs/Fetching-Records.md | 47 --- docs/Issue-Collaborators.md | 27 -- docs/Link-Helpers.md | 39 --- docs/MetaWhere.md | 1 - docs/Mongoid.md | 17 -- docs/Multiple-can-definitions.textile | 35 --- docs/Non-RESTful-Controllers.md | 35 --- docs/Other-Authorization-Solutions.md | 7 - docs/README.md | 86 +++--- docs/Separate-Role-Model.md | 87 ------ docs/Share-Ability-Definitions.md | 9 - docs/Strong-Parameters.md | 140 --------- ...n-Database.md => abilities_in_database.md} | 0 docs/accessible_attributes.md | 37 +++ docs/accessing_request_data.md | 26 ++ docs/cannot.md | 14 + docs/changing_defaults.md | 289 ++++++++++++++++++ docs/check_abilities_mistakes.md | 48 +++ docs/combine_abilities.md | 59 ++++ docs/controller_helpers.md | 176 +++++++++++ docs/{Debugging-Abilities.md => debugging.md} | 21 +- docs/define_abilities_best_practices.md | 80 +++++ docs/define_abilities_with_blocks.md | 61 ++++ docs/define_check_abilities.md | 231 ++++++++++++++ docs/fetching_records.md | 78 +++++ .../{FriendlyId-support.md => friendly_id.md} | 9 +- ...-Handling.md => handling_access_denied.md} | 31 +- docs/hash_of_conditions.md | 78 +++++ ...ed-Resources.md => inherited_resources.md} | 4 +- docs/installation.md | 35 +++ ...ions-(i18n).md => internationalization.md} | 4 + docs/introduction.md | 33 ++ ...m-CanCanCan-2.x-to-3.0.md => migrating.md} | 4 + docs/{Model-Adapter.md => model_adapter.md} | 0 ...ested-Resources.md => nested_resources.md} | 8 +- ...ization.md => role_based_authorization.md} | 2 + ...es-compression.md => rules_compression.md} | 0 docs/split_ability.md | 78 +++++ docs/sql_strategies.md | 67 ++++ docs/{Testing-Abilities.md => testing.md} | 183 +++++------ .../cancan/ability/templates/ability.rb | 14 +- logo/new_relic.png | Bin 0 -> 60120 bytes 59 files changed, 1598 insertions(+), 1800 deletions(-) delete mode 100644 docs/Ability-Precedence.md delete mode 100644 docs/Ability-for-Other-Users.md delete mode 100644 docs/Accessing-request-data.md delete mode 100644 docs/Action-Aliases.md delete mode 100644 docs/Authorization-for-Namespaced-Controllers.md delete mode 100644 docs/Authorizing-controller-actions.md delete mode 100644 docs/Changing-Defaults.md delete mode 100644 docs/Checking-Abilities.md delete mode 100644 docs/Controller-Authorization-Example.md delete mode 100644 docs/Custom-Actions.md delete mode 100644 docs/Defining-Abilities-with-Blocks.md delete mode 100644 docs/Defining-Abilities-with-Hashes.md delete mode 100644 docs/Defining-Abilities.md delete mode 100644 docs/Defining-Abilities:-Best-Practices.md delete mode 100644 docs/Ensure-Authorization.md delete mode 100644 docs/Fetching-Records.md delete mode 100644 docs/Issue-Collaborators.md delete mode 100644 docs/Link-Helpers.md delete mode 100644 docs/MetaWhere.md delete mode 100644 docs/Mongoid.md delete mode 100644 docs/Multiple-can-definitions.textile delete mode 100644 docs/Non-RESTful-Controllers.md delete mode 100644 docs/Other-Authorization-Solutions.md delete mode 100644 docs/Separate-Role-Model.md delete mode 100644 docs/Share-Ability-Definitions.md delete mode 100644 docs/Strong-Parameters.md rename docs/{Abilities-in-Database.md => abilities_in_database.md} (100%) create mode 100644 docs/accessible_attributes.md create mode 100644 docs/accessing_request_data.md create mode 100644 docs/cannot.md create mode 100644 docs/changing_defaults.md create mode 100644 docs/check_abilities_mistakes.md create mode 100644 docs/combine_abilities.md create mode 100644 docs/controller_helpers.md rename docs/{Debugging-Abilities.md => debugging.md} (76%) create mode 100644 docs/define_abilities_best_practices.md create mode 100644 docs/define_abilities_with_blocks.md create mode 100644 docs/define_check_abilities.md create mode 100644 docs/fetching_records.md rename docs/{FriendlyId-support.md => friendly_id.md} (74%) rename docs/{Exception-Handling.md => handling_access_denied.md} (75%) create mode 100644 docs/hash_of_conditions.md rename docs/{Inherited-Resources.md => inherited_resources.md} (82%) create mode 100644 docs/installation.md rename docs/{Translations-(i18n).md => internationalization.md} (97%) create mode 100644 docs/introduction.md rename docs/{Migrating-from-CanCanCan-2.x-to-3.0.md => migrating.md} (96%) rename docs/{Model-Adapter.md => model_adapter.md} (100%) rename docs/{Nested-Resources.md => nested_resources.md} (97%) rename docs/{Role-Based-Authorization.md => role_based_authorization.md} (99%) rename docs/{Rules-compression.md => rules_compression.md} (100%) create mode 100644 docs/split_ability.md create mode 100644 docs/sql_strategies.md rename docs/{Testing-Abilities.md => testing.md} (54%) create mode 100644 logo/new_relic.png diff --git a/README.md b/README.md index bd5bf3ffb..526740022 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![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) | +[Developer guide](./docs) | [RDocs](http://rdoc.info/projects/CanCanCommunity/cancancan) | [Screencast 1](http://railscasts.com/episodes/192-authorization-with-cancan) | [Screencast 2](https://www.youtube.com/watch?v=cTYu-OjUgDw) @@ -24,25 +24,34 @@ and provides helpers to check for those permissions. 2. **Rails helpers** to simplify the code in Rails Controllers by performing the loading and checking of permissions of models automatically and reduce duplicated code. -## Sponsored by - +## Our sponsors +
- Renuo AG + Renuo AG

+
- Modern Treasury + Modern Treasury

+
- Bullet Train + Bullet Train

+
- Goboony + Goboony + +
+
+
+ + NewRelic

@@ -50,6 +59,8 @@ of models automatically and reduce duplicated code. Do you want to sponsor CanCanCan and show your logo here? Check our [Sponsors Page](https://github.com/sponsors/coorasse). +Head to our complete [Developer Guide](./docs) to learn how to use CanCanCan in details. + ## Installation Add this to your Gemfile: @@ -72,21 +83,15 @@ class Ability def initialize(user) can :read, Post, public: true - if user.present? # additional permissions for logged in users (they can read their own posts) - can :read, Post, user_id: user.id + return unless user.present? # additional permissions for logged in users (they can read their own posts) + can :read, Post, user: user - if user.admin? # additional permissions for administrators - can :read, Post - end - end + return unless user.admin? # additional permissions for administrators + can :read, Post end end ``` -See [Defining Abilities](./docs/Defining-Abilities.md) for details on how to -define your rules. - - ## Check Abilities The current user's permissions can then be checked using the `can?` and `cannot?` methods in views and controllers. @@ -97,9 +102,6 @@ The current user's permissions can then be checked using the `can?` and `cannot? <% end %> ``` -See [Checking Abilities](./docs/Checking-Abilities.md) for more information -on how you can use these helpers. - ## Fetching records One of the key features of CanCanCan, compared to other authorization libraries, @@ -107,20 +109,13 @@ is the possibility to retrieve all the objects that the user is authorized to ac The following: ```ruby - Post.accessible_by(current_ability) + @posts Post.accessible_by(current_ability) ``` will use your rules to ensure that the user retrieves only a list of posts that can be read. -See [Fetching records](./docs/Fetching-Records.md) for details. ## Controller helpers -CanCanCan expects a `current_user` method to exist in the controller. -First, set up some authentication (such as [Devise](https://github.com/plataformatec/devise) or [Authlogic](https://github.com/binarylogic/authlogic)). -See [Changing Defaults](./docs/Changing-Defaults.md) if you need a different behavior. - -### 3.1 Authorizations - The `authorize!` method in the controller will raise an exception if the user is not able to perform the given action. ```ruby @@ -130,8 +125,6 @@ def show end ``` -### 3.2 Loaders - Setting this for every action can be tedious, therefore the `load_and_authorize_resource` method is provided to automatically authorize all actions in a RESTful style resource controller. It will use a before action to load the resource into an instance variable and authorize it for every action. @@ -150,129 +143,14 @@ class PostsController < ApplicationController end ``` -See [Authorizing Controller Actions](./docs/Authorizing-controller-actions.md) -for more information. - - -### 3.3 Strong Parameters - -You have to sanitize inputs before saving the record, in actions such as `:create` and `:update`. - -For the `:update` action, CanCanCan will load and authorize the resource but *not* change it automatically, so the typical usage would be something like: - -```ruby -def update - if @post.update(post_params) - # hurray - else - render :edit - end -end -... - -def post_params - params.require(:post).permit(:body) -end -``` - -For the `:create` action, CanCanCan will try to initialize a new instance with sanitized input by seeing if your -controller will respond to the following methods (in order): - -1. `create_params` -2. `_params` such as `post_params` (this is the default convention in rails for naming your param method) -3. `resource_params` (a generically named method you could specify in each controller) - -Additionally, `load_and_authorize_resource` can now take a `param_method` option to specify a custom method in the controller to run to sanitize input. - -You can associate the `param_method` option with a symbol corresponding to the name of a method that will get called: - -```ruby -class PostsController < ApplicationController - load_and_authorize_resource param_method: :my_sanitizer - - def create - if @post.save - # hurray - else - render :new - end - end - - private - - def my_sanitizer - params.require(:post).permit(:name) - end -end -``` - -You can also use a string that will be evaluated in the context of the controller using `instance_eval` and needs to contain valid Ruby code. - - load_and_authorize_resource param_method: 'permitted_params.post' - -Finally, it's possible to associate `param_method` with a Proc object which will be called with the controller as the only argument: - - load_and_authorize_resource param_method: Proc.new { |c| c.params.require(:post).permit(:name) } - -See [Strong Parameters](./docs/Strong-Parameters.md) for more information. - -## Handle Unauthorized Access - -If the user authorization fails, a `CanCan::AccessDenied` exception will be raised. -You can catch this and modify its behavior in the `ApplicationController`. - -```ruby -class ApplicationController < ActionController::Base - rescue_from CanCan::AccessDenied do |exception| - respond_to do |format| - format.json { head :forbidden, content_type: 'text/html' } - format.html { redirect_to main_app.root_url, notice: exception.message } - format.js { head :forbidden, content_type: 'text/html' } - end - end -end -``` - -See [Exception Handling](./docs/Exception-Handling.md) for more information. - - -## Lock It Down - -If you want to ensure authorization happens on every action in your application, add `check_authorization` to your `ApplicationController`. - -```ruby -class ApplicationController < ActionController::Base - check_authorization -end -``` - -This will raise an exception if authorization is not performed in an action. -If you want to skip this, add `skip_authorization_check` to a controller subclass. -See [Ensure Authorization](./docs/Ensure-Authorization.md) for more information. - -## Wiki Docs - -* [Defining Abilities](./docs/Defining-Abilities.md) -* [Checking Abilities](./docs/Checking-Abilities.md) -* [Authorizing Controller Actions](./docs/Authorizing-controller-actions.md) -* [Exception Handling](./docs/Exception-Handling.md) -* [Changing Defaults](./docs/Changing-Defaults.md) -* [See more](./docs) - -## Mission - -This repo is a continuation of the dead [CanCan](https://github.com/ryanb/cancan) project. -Our mission is to keep CanCan alive and moving forward, with maintenance fixes and new features. -Pull Requests are welcome! - -Any help is greatly appreciated, feel free to submit pull-requests or open issues. +## Documentation +Head to our complete [Developer Guide](./docs) to learn how to use CanCanCan in details. ## Questions? If you have any question or doubt regarding CanCanCan which you cannot find the solution to in the -[documentation](./docs) or our -[mailing list](http://groups.google.com/group/cancancan), please +[documentation](./docs), please [open a question on Stackoverflow](http://stackoverflow.com/questions/ask?tags=cancancan) with tag [cancancan](http://stackoverflow.com/questions/tagged/cancancan) @@ -293,5 +171,5 @@ See the [CONTRIBUTING](./CONTRIBUTING.md) for more information. ## Special Thanks -Many thanks to the [CanCanCan contributors](https://github.com/CanCanCommunity/cancancan/contributors). +Thanks to our Sponsors and to all the [CanCanCan contributors](https://github.com/CanCanCommunity/cancancan/contributors). See the [CHANGELOG](https://github.com/CanCanCommunity/cancancan/blob/master/CHANGELOG.md) for the full list. diff --git a/docs/Ability-Precedence.md b/docs/Ability-Precedence.md deleted file mode 100644 index 5f76e394d..000000000 --- a/docs/Ability-Precedence.md +++ /dev/null @@ -1,49 +0,0 @@ -# Ability Precedence - -An ability rule will override a previous one. -For example, let's say we want the user to be able to do everything to projects except destroy them. - -This is the correct way: - -```ruby -can :manage, Project -cannot :destroy, Project -``` - -It is important that the `cannot :destroy` line comes after the `can :manage` line. If they were reversed, `cannot :destroy` would be overridden by `can :manage`. - -Adding `can` rules do not override prior rules, but instead are logically or'ed. - -```ruby -can :manage, Project, user_id: user.id -can :update, Project do |project| - !project.locked? -end -``` - -For the above, `can? :update` will always return true if the `user_id` equals `user.id`, even if the project is locked. - -This is also important when dealing with roles which have inherited behavior. For example, let's say we have two roles, moderator and admin. We want the admin to inherit the moderator's behavior. - -```ruby -if user.role? :moderator - can :manage, Project - cannot :destroy, Project - can :manage, Comment -end - -if user.role? :admin - can :destroy, Project -end -``` - -Here it is important the admin role be after the moderator so it can override the `cannot` behavior to give the admin more permissions. See [Role Based Authorization](./Role-Based-Authorization.md). - -If you are not getting the behavior you expect, please [post an issue](https://github.com/CanCanCommunity/cancancan/issues). - -## Additional Docs - -* [Defining Abilities](./Defining-Abilities.md) -* [Checking Abilities](./Checking-Abilities.md) -* [Debugging Abilities](./Debugging-Abilities.md) -* [Testing Abilities](./Testing-Abilities.md) diff --git a/docs/Ability-for-Other-Users.md b/docs/Ability-for-Other-Users.md deleted file mode 100644 index 598f5efe9..000000000 --- a/docs/Ability-for-Other-Users.md +++ /dev/null @@ -1,34 +0,0 @@ -What if you want to determine the abilities of a `User` record that is not the `current_user`? Maybe we want to see if another user can update an article. - -```ruby -some_user.ability.can? :update, @article -``` - -You can easily add an `ability` method in the `User` model. - -```ruby -def ability - @ability ||= Ability.new(self) -end -``` - -I also recommend adding delegation so `can?` can be called directly from the user. - -```ruby -class User < ActiveRecord::Base - delegate :can?, :cannot?, to: :ability - # ... -end - -some_user.can? :update, @article -``` - -Finally, if you're using this approach, it's best to override the `current_ability` method in the `ApplicationController` so it uses the same method. - -```ruby -def current_ability - @current_ability ||= current_user.ability -end -``` - -The downside of this approach is that [[Accessing Request Data]] is not as easy, so it depends on the needs of your application. \ No newline at end of file diff --git a/docs/Accessing-request-data.md b/docs/Accessing-request-data.md deleted file mode 100644 index 84e6ef72a..000000000 --- a/docs/Accessing-request-data.md +++ /dev/null @@ -1,27 +0,0 @@ -# Accessing request data - -What if you need to modify the permissions based on something outside of the User object? For example, let's say you want to blacklist certain IP addresses from creating comments. The IP address is accessible through request.remote_ip but the Ability class does not have access to this. It's easy to modify what you pass to the Ability object by overriding the current_ability method in ApplicationController. - -```ruby -class ApplicationController < ActionController::Base - #... - - private - - def current_ability - @current_ability ||= Ability.new(current_user, request.remote_ip) - end -end -``` -```ruby -class Ability - include CanCan::Ability - - def initialize(user, ip_address=nil) - can :create, Comment unless BLACKLIST_IPS.include? ip_address - end -end -``` -This concept can apply to session and cookies as well. - -You may wonder, why I pass only the IP Address instead of the entire request object? I prefer to pass only the information needed, this makes testing and debugging the behavior easier. diff --git a/docs/Action-Aliases.md b/docs/Action-Aliases.md deleted file mode 100644 index be139175f..000000000 --- a/docs/Action-Aliases.md +++ /dev/null @@ -1,32 +0,0 @@ -You will usually be working with four actions when [[defining|Defining Abilities]] and [[checking|Checking Abilities]] permissions: `:read`, `:create`, `:update`, `:destroy`. These aren't the same as the 7 RESTful actions in Rails. CanCanCan automatically adds some convenient aliases for mapping the controller actions. - -```ruby -alias_action :index, :show, :to => :read -alias_action :new, :to => :create -alias_action :edit, :to => :update -``` - -Notice the `edit` action is aliased to `update`. This means if the user is able to update a record he also has permission to edit it. You can define your own aliases in the `Ability` class. - -```ruby -class Ability - include CanCan::Ability - def initialize(user) - alias_action :update, :destroy, :to => :modify - can :modify, Comment - end -end - -# in controller or view -can? :update, Comment # => true -``` - -You are not restricted to just the 7 RESTful actions, you can use any action name. See [[Custom Actions]] for details. - -Please note that if you are changing the default alias_actions, the original actions associated with the alias will NOT be removed. For example, following statement will not have any effect on the alias :read, which points to :show and :index: - -```ruby -alias_action :show, :to => :read # this will have no effect on the alias :read! -``` - -If you want to change the default actions, you should use clear_aliased_actions method to remove ALL default aliases first. \ No newline at end of file diff --git a/docs/Authorization-for-Namespaced-Controllers.md b/docs/Authorization-for-Namespaced-Controllers.md deleted file mode 100644 index 14ffaf09e..000000000 --- a/docs/Authorization-for-Namespaced-Controllers.md +++ /dev/null @@ -1,51 +0,0 @@ -The default operation for CanCanCan is to authorize based on user and the object identified in `load_resource`. So if you have a `WidgetsController` and also an `Admin::WidgetsController`, you can use some different approaches. - -Just like in the example given for [[Accessing Request Data]], you **can** also create differing authorization rules that depend on the controller namespace. - -In this case, just override the `current_ability` method in `ApplicationController` to include the controller namespace, and create an `Ability` class that knows what to do with it. - -``` ruby -class Admin::WidgetsController < ActionController::Base - #... - - private - - def current_ability - # I am sure there is a slicker way to capture the controller namespace - controller_name_segments = params[:controller].split('/') - controller_name_segments.pop - controller_namespace = controller_name_segments.join('/').camelize - @current_ability ||= Ability.new(current_user, controller_namespace) - end -end - - -class Ability - include CanCan::Ability - - def initialize(user, controller_namespace) - case controller_namespace - when 'Admin' - can :manage, :all if user.has_role? 'admin' - else - # rules for non-admin controllers here - end - end -end -``` - -Another way to achieve the same is to use a completely different Ability class in this controller: - -``` ruby -class Admin::WidgetsController < ActionController::Base - #... - - private - - def current_ability - @current_ability ||= AdminAbility.new(current_user) - end -end -``` - -and follow the [Best Practice of splitting your Ability file into multiple files](https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities%3A-Best-Practices#split-your-abilityrb-file). \ No newline at end of file diff --git a/docs/Authorizing-controller-actions.md b/docs/Authorizing-controller-actions.md deleted file mode 100644 index 73840e9d1..000000000 --- a/docs/Authorizing-controller-actions.md +++ /dev/null @@ -1,210 +0,0 @@ -You can use the `authorize!` method to manually handle authorization in a controller action. This will raise a `CanCan::AccessDenied` exception when the user does not have permission. See [[Exception Handling]] for how to react to this. - -```ruby -def show - @project = Project.find(params[:project]) - authorize! :show, @project -end -``` - -However that can be tedious to apply to each action. Instead you can use the `load_and_authorize_resource` method in your controller to load the resource into an instance variable and authorize it automatically for every action in that controller. - -```ruby -class ProductsController < ActionController::Base - load_and_authorize_resource -end -``` - -This is the same as calling `load_resource` and `authorize_resource` because they are two separate steps and you can choose to use one or the other. - -```ruby -class ProductsController < ActionController::Base - load_resource - authorize_resource -end -``` - -As of CanCan 1.5 you can use the `skip_load_and_authorize_resource`, `skip_load_resource` or `skip_authorize_resource` methods to skip any of the applied behavior and specify specific actions like in a before filter. For example. - -```ruby -class ProductsController < ActionController::Base - load_and_authorize_resource - skip_authorize_resource :only => :new -end -``` - -**important notice about `:manage` rules** - -Using `load_and_authorize_resource` with a rule like `can :manage, Article, id: 23` will allow rendering the `new` method of the ArticlesController, which is unexpected because this rule naively reads as _"the user can manage the existing article with id 23"_, which should have nothing to do with creating new articles. - -But in reality the rule means _"the user can manage any article object with an id field set to 23"_, which includes creating a new Article with the id set to 23 like `Article.new(id: 23)`. - -Thus `load_and_authorize_resource` will initialize a model in the `:new` action and set its id to 23, and happily render the page. Saving will not work though. - -The correct intended rule to avoid `new` being allowed would be: - -``` ruby -can [:read, :update, :destroy], Article, id: 23 -``` - -Also see [[Controller Authorization Example]], [[Ensure Authorization]] and [[Non RESTful Controllers]]. - - -## Choosing Actions - -By default this will apply to **every action** in the controller even if it is not one of the 7 RESTful actions. The action name will be passed in when authorizing. For example, if we have a `discontinue` action on `ProductsController` it will have this behavior. - -```ruby -class ProductsController < ActionController::Base - load_and_authorize_resource - def discontinue - # Automatically does the following: - # @product = Product.find(params[:id]) - # authorize! :discontinue, @product - end -end -``` - -You can specify which actions to affect using the `:except` and `:only` options, just like a `before_action`. - -```ruby -load_and_authorize_resource :only => [:index, :show] -``` -### Choosing actions on nested resources - -For this you can pass a name to skip_authorize_resource. -For example: -```ruby -class CommentsController < ApplicationController - load_and_authorize_resource :post - load_and_authorize_resource :through => :post - - skip_authorize_resource :only => :show - skip_authorize_resource :post, :only => :show -end -``` - -The first skip_authorize_resource skips authorization check for comment and the second for post. Both are needed if you want to skip all authorization checks for an action. - -## load_resource - -### index action - -As of 1.4 the index action will load the collection resource using `accessible_by`. - -```ruby -def index - # @products automatically set to Product.accessible_by(current_ability) -end -``` - -If you want custom find options such as [[includes|https://github.com/ryanb/cancan/issues#issue/259]] or pagination, you can build on this further since it is a scope. - -```ruby -def index - @products = @products.includes(:category).page(params[:page]) -end -``` - -The `@products` variable will not be set initially if `Product` does not respond to `accessible_by` (such as if you aren't using a supported ORM). It will also not be set if you are only using a block in the `can` definitions because there is no way to determine which records to fetch from the database. - -### show, edit, update and destroy actions - -These member actions simply fetch the record directly. - -```ruby -def show - # @product automatically set to Product.find(params[:id]) -end -``` - -### new and create actions - -As of 1.4 these builder actions will initialize the resource with the attributes in the hash conditions. For example, if we have this `can` definition. - -```ruby -can :manage, Product, :discontinued => false -``` - -Then the product will be built with that attribute in the controller. - -```ruby -@product = Product.new(:discontinued => false) -``` - -This way it will pass authorization when the user accesses the `new` action. - -The attributes are then overridden by whatever is passed by the user in `params[:product]`. - -### Custom class - -If the model is named differently than the controller, then you may explicitly name the model that should be loaded; however, you must specify that it is not a parent in a nested routing situation, ie: - -```ruby -class ArticlesController < ApplicationController - load_and_authorize_resource :post, :parent => false -end -``` - -If the model class is namespaced differently than the controller you will need to specify the `:class` option. - -```ruby -class ProductsController < ApplicationController - load_and_authorize_resource :class => "Store::Product" -end -``` - - -### Custom find - -If you want to fetch a resource by something other than `id` it can be done so using the `find_by` option. - -```ruby -load_resource :find_by => :permalink # will use find_by_permalink!(params[:id]) -authorize_resource -``` - -### Override loading - -The resource will only be loaded into an instance variable if it hasn't been already. This allows you to easily override how the loading happens in a separate `before_action`. - -```ruby -class BooksController < ApplicationController - before_action :find_published_book, :only => :show - load_and_authorize_resource - - private - - def find_published_book - @book = Book.released.find(params[:id]) - end -end -``` - -It is important that any custom loading behavior happens **before** the call to `load_and_authorize_resource`. If you have `authorize_resource` in your `ApplicationController` then you need to use `prepend_before_action` to do the loading in the controller subclasses so it happens before authorization. - -## authorize_resource - -Adding `authorize_resource` will install a `before_action` callback that calls `authorize!`, passing the resource instance variable if it exists. If the instance variable isn't set (such as in the index action) it will pass in the class name. For example, if we have a `ProductsController` it will do this before each action. - -```ruby -authorize!(params[:action].to_sym, @product || Product) -``` - -## More info - -For additional information see the `load_resource` and `authorize_resource` methods in the [[RDoc|http://www.rubydoc.info/github/CanCanCommunity/cancancan]]. - -Also see [[Nested Resources]] and [[Non RESTful Controllers]]. - -## Resetting Current Ability - -If you ever update a User record which may be the current user, it will make the current ability for that request stale. This means any `can?` checks will use the user record before it was updated. You will need to reset the `current_ability` instance so it will be reloaded. Do the same for the `current_user` if you are caching that too. - -```ruby -if @user.update_attributes(params[:user]) - @current_ability = nil - @current_user = nil - # ... -end -``` \ No newline at end of file diff --git a/docs/Changing-Defaults.md b/docs/Changing-Defaults.md deleted file mode 100644 index eafe5d637..000000000 --- a/docs/Changing-Defaults.md +++ /dev/null @@ -1,46 +0,0 @@ -# Changing Defaults - -CanCanCan makes two assumptions about your application. - -* You have an `Ability` class which defines the permissions. -* You have a `current_user` method in the controller which returns the current user model. - -You can override both of these by defining the `current_ability` method in your `ApplicationController`. The current method looks like this. - -```ruby -def current_ability - @current_ability ||= Ability.new(current_user) -end -``` - -The `Ability` class and `current_user` method can easily be changed to something else. - -```ruby -# in ApplicationController -def current_ability - @current_ability ||= AccountAbility.new(current_account) -end -``` - -Sometimes you might have a gem in your project which provides its own Rails engine which also uses CanCanCan such as LocomotiveCMS. In this case the current_ability override in the ApplicationController can also be useful. - -```ruby -# in ApplicationController -def current_ability - if request.fullpath =~ /\/locomotive/ - @current_ability ||= Locomotive::Ability.new(current_user) - else - @current_ability ||= Ability.new(current_user) - end -end -``` - -If your method that returns the currently logged in user just has another name than `current_user`, it may be the easiest solution to simply alias the method in your ApplicationController like this: - -```ruby -class ApplicationController < ActionController::Base - alias_method :current_user, :name_of_your_method # Could be :current_member or :logged_in_user -end -``` - -That's it! See [Accessing Request Data](https://github.com/CanCanCommunity/cancancan/blob/develop/docs/Accessing-request-data.md) for a more complex example of what you can do here. diff --git a/docs/Checking-Abilities.md b/docs/Checking-Abilities.md deleted file mode 100644 index dd7951aa5..000000000 --- a/docs/Checking-Abilities.md +++ /dev/null @@ -1,51 +0,0 @@ -After [Defining Abilities](https://github.com/CanCanCommunity/cancancan/blob/develop/docs/Defining-Abilities.md), you can use the `can?` method in the controller or view to check the user's permission for a given action and object. - -```ruby -can? :destroy, @project -``` - -The `cannot?` method is for convenience and performs the opposite check of `can?` - -```ruby -cannot? :destroy, @project -``` - -Also see [[Authorizing Controller Actions]] and [[Custom Actions]]. - -## Checking with Class - -You can also pass the class instead of an instance (if you don't have one handy). - -```rhtml -<% if can? :create, Project %> - <%= link_to "New Project", new_project_path %> -<% end %> -``` - -**Important:** If a block or hash of conditions exist they will be ignored when checking on a class, and it will return `true`. For example: - -```ruby -can :read, Project, :priority => 3 -can? :read, Project # returns true -``` - -It is impossible to answer this `can?` question completely because not enough detail is given. Here the class does not have a `priority` attribute to check on. - -Think of it as asking "can the current user read **a** project?". The user can read a project, so this returns `true`. However it depends on which specific project you're talking about. If you are doing a class check, it is important you do another check once an instance becomes available so the hash of conditions can be used. - -The reason for this behavior is because of the controller `index` action. Since the `authorize_resource` before filter has no instance to check on, it will use the `Project` class. If the authorization failed at that point then it would be impossible to filter the results later when [[Fetching Records]]. - -That is why passing a class to `can?` will return `true`. - -The code answering the question "can the user update all the articles?" would be something like: - -``` ruby -Article.accessible_by(current_ability).count == Article.count -``` - -## Additional Docs - -* [Defining Abilities](https://github.com/CanCanCommunity/cancancan/blob/develop/docs/Defining-Abilities.md) -* [Ability Precedence](https://github.com/CanCanCommunity/cancancan/blob/develop/docs/Ability-Precedence.md) -* [Debugging Abilities](https://github.com/CanCanCommunity/cancancan/blob/develop/docs/Debugging-Abilities.md) -* [Testing Abilities](https://github.com/CanCanCommunity/cancancan/blob/develop/docs/Testing-Abilities.md) diff --git a/docs/Controller-Authorization-Example.md b/docs/Controller-Authorization-Example.md deleted file mode 100644 index 8a65c5531..000000000 --- a/docs/Controller-Authorization-Example.md +++ /dev/null @@ -1,70 +0,0 @@ -CanCan provides a convenient `load_and_authorize_resource` method in the controller, but what exactly is this doing? It sets up a before filter for every action to handle the loading and authorization of the controller. Let's say we have a typical RESTful controller with that line at the top. - -```ruby -class ProjectsController < ApplicationController - load_and_authorize_resource - # ... -end -``` - -It will add a before filter that has this behavior for the actions if they exist. This means you do not need to put code below in your controller. - -```ruby -class ProjectsController < ApplicationController - def index - authorize! :index, Project - @projects = Project.accessible_by(current_ability) - end - - def show - @project = Project.find(params[:id]) - authorize! :show, @project - end - - def new - @project = Project.new - current_ability.attributes_for(:new, Project).each do |key, value| - @project.send("#{key}=", value) - end - @project.attributes = params[:project] - authorize! :new, @project - end - - def create - @project = Project.new - current_ability.attributes_for(:create, Project).each do |key, value| - @project.send("#{key}=", value) - end - @project.attributes = params[:project] - authorize! :create, @project - end - - def edit - @project = Project.find(params[:id]) - authorize! :edit, @project - end - - def update - @project = Project.find(params[:id]) - authorize! :update, @project - end - - def destroy - @project = Project.find(params[:id]) - authorize! :destroy, @project - end - - def some_other_action - if params[:id] - @project = Project.find(params[:id]) - else - @projects = Project.accessible_by(current_ability) - end - authorize!(:some_other_action, @project || Project) - end -end -``` - -The most complex behavior is inside the new and create actions. There it is setting some initial attribute values based on what the given user has permission to access. For example, if the user is only allowed to create projects where the "visible" attribute is true, then it would automatically set this upon building it. - -See [[Authorizing Controller Actions]] for details on what options you can pass to the `load_and_authorize_resource`. \ No newline at end of file diff --git a/docs/Custom-Actions.md b/docs/Custom-Actions.md deleted file mode 100644 index fbdce20c9..000000000 --- a/docs/Custom-Actions.md +++ /dev/null @@ -1,27 +0,0 @@ -When you define a user's abilities for a given model, you are not restricted to the 7 RESTful actions (create, update, destroy, etc.), you can create your own. - -For example, in [[Role Based Authorization]] I showed you how to define separate roles for a given user. However, you don't want all users to be able to assign roles, only admins. How do you set these fine-grained controls? Well you need to come up with a new action name. Let's call it `assign_roles`. - -```ruby -# in models/ability.rb -can :assign_roles, User if user.admin? -``` - -We can then check if the user has permission to assign roles when displaying the role checkboxes and assigning them. - -```rhtml - -<% if can? :assign_roles, @user %> - -<% end %> -``` - -```ruby -# users_controller.rb -def update - authorize! :assign_roles, @user if params[:user][:assign_roles] - # ... -end -``` - -Now only admins will be able to assign roles to users. \ No newline at end of file diff --git a/docs/Defining-Abilities-with-Blocks.md b/docs/Defining-Abilities-with-Blocks.md deleted file mode 100644 index 1c16c114b..000000000 --- a/docs/Defining-Abilities-with-Blocks.md +++ /dev/null @@ -1,101 +0,0 @@ -If your conditions are too complex to define in a hash (as shown in [[Defining Abilities]] page), you can use a block to define them in Ruby. - -```ruby -can :update, Project do |project| - project.priority < 3 -end -``` - -If the block returns true then the user has that ability, otherwise he will be denied access. - -## Only for Object Attributes - -The block is **only** evaluated when an actual instance object is present. It is not evaluated when checking permissions on the class (such as in the `index` action). This means any conditions which are not dependent on the object attributes should be moved outside of the block. - -```ruby -# don't do this -can :update, Project do |project| - user.admin? # this won't be called for Project.accessible_by(current_ability, :update) -end - -# do this -can :update, Project if user.admin? -``` -Note that if you pass a block to a `can` or `cannot`, regardless of whether the block asks for parameters (ex. `|project|`) the block only executes if an instance of a class is passed to `can?` or `cannot?`. - -If you define a `can` or `cannot` with a block and an object is not passed, the check will pass. -```ruby -can :update, Project do |project| - false -end -``` -```ruby -can? :update, Project # returns true! -``` - -See [[Checking Abilities]] for more information. - -## Fetching Records - -A block's conditions are only executable through Ruby. If you are [[Fetching Records]] using `accessible_by` it will raise an exception. To fetch records from the database you need to supply an SQL string representing the condition. The SQL will go in the `WHERE` clause, if you need to do joins consider using sub-queries or scopes (below). - -```ruby -can :update, Project, ["priority < ?", 3] do |project| - project.priority < 3 -end -``` - -If you are using `load_resource` and don't supply this SQL argument, the instance variable will not be set for the `index` action since they cannot be translated to a database query. - - -## Block Conditions with Scopes - -It's also possible to pass a scope instead of an SQL string when using a block in an ability. - -```ruby -can :read, Article, Article.published do |article| - article.published_at <= Time.now -end -``` - -Generally, this breaks down to looks something like: - -```ruby -can [:ability], Model, Model.scope_to_select_on_index_action do |model_instance| - model_instance.condition_to_evaluate_for_new_create_edit_update_destroy -end -``` - -This is really useful if you have complex conditions which require `joins`. A couple of caveats: - -* You cannot use this with multiple `can` definitions that match the same action and model since it is not possible to combine them. An exception will be raised when that is the case. -* If you use this with `cannot`, the scope needs to be the inverse since it's passed directly through. For example, if you don't want someone to read discontinued products the scope will need to fetch non discontinued ones: - -```ruby -cannot :read, Product, Product.where(:discontinued => false) do |product| - product.discontinued? -end -``` - -It is only recommended to use scopes if a situation is too complex for a hash condition. - -## Overriding All Behavior - -You can override all `can` behaviour by passing no arguments, this is useful when permissions are defined outside of ruby such as when defining [[Abilities in Database]]. - -```ruby -can do |action, subject_class, subject| - # ... -end -``` - -Here the block will be triggered for every `can?` check, even when only a class is used in the check. - - -## Additional Docs - -* [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) diff --git a/docs/Defining-Abilities-with-Hashes.md b/docs/Defining-Abilities-with-Hashes.md deleted file mode 100644 index 860b25943..000000000 --- a/docs/Defining-Abilities-with-Hashes.md +++ /dev/null @@ -1,5 +0,0 @@ -This section has been moved to [[Defining Abilities]] under "Hash of Conditions". - -## Checking with Class - -This section has been moved to [[Checking Abilities]]. diff --git a/docs/Defining-Abilities.md b/docs/Defining-Abilities.md deleted file mode 100644 index a5144e820..000000000 --- a/docs/Defining-Abilities.md +++ /dev/null @@ -1,173 +0,0 @@ -# Defining Abilities - -The `Ability` class is where all user permissions are defined. An example class looks like this. - -```ruby -class Ability - include CanCan::Ability - - def initialize(user) - can :read, :all # permissions for every user, even if not logged in - if user.present? # additional permissions for logged in users (they can manage their posts) - can :manage, Post, user_id: user.id - if user.admin? # additional permissions for administrators - can :manage, :all - end - end - end -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](./Role-Based-Authorization.md) for an example. - -## The `can` Method - -The `can` method is used to define permissions and requires two arguments. The first one is the action you're setting the permission for, the second one is the class of object you're setting it on. - -```ruby -can :update, Article -``` - -You can pass `:manage` to represent any action and `:all` to represent any object. - -```ruby -can :manage, Article # user can perform any action on the article -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](./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. - -```ruby -can [:update, :destroy], [Article, Comment] -``` - - -**Important notice about :manage**. As you read above it represents ANY action on the object. So if you have something like: - -```ruby -can :manage, User -can :invite, User -``` - -you can get rid of the second line and the `:invite` permissions, because `:manage` represents **any** action on object and `:manage` is not just `:create`, `:read`, `:update`, `:destroy` on object. - -If you want only CRUD actions on object, you should create custom action that called `:crud` for example, and use it instead of `:manage`: - -```ruby -def initialize(user) - alias_action :create, :read, :update, :destroy, to: :crud - if user.present? - can :crud, User - can :invite, User - end -end -``` - -## Hash of Conditions - -A hash of conditions can be passed to further restrict which records this permission applies to. Here the user will only have permission to read active projects which they own. - -```ruby -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](./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. - -```ruby -can :read, Project, category: { visible: true } -``` - -The above will issue a query that performs an `LEFT JOIN` to query conditions on associated records. -The example below will use a scope that returns all Photos that do not belong to a group. - -```ruby -class Photo - has_and_belongs_to_many :groups - scope :unowned, -> { left_joins(:groups).where(groups: { id: nil }) } -end - -class Group - has_and_belongs_to_many :photos -end - -class Ability - def initialize(user) - can :read, Photo, Photo.unowned do |photo| - photo.groups.empty? - end - end -end -``` - -An array or range can be passed to match multiple values. Here the user can only read projects of priority 1 through 3. - -```ruby -can :read, Project, priority: 1..3 -``` - -Almost anything that you can pass to a hash of conditions in Active Record will work here. The only exception is working with model ids. You can't pass in the model objects directly, you must pass in the ids. - -```ruby -can :manage, Project, group: { id: user.group_ids } -``` - -If you have a complex case which cannot be done through a hash of conditions, see [Defining Abilities with Blocks](./Defining-Abilities-with-Blocks.md). - -## Traverse associations - -All associations can be traversed when defining a rule. - -```ruby -class User - belongs_to :account -end - -class Account - has_one :user - has_many :services -end - -class Service - belongs_to :account - has_many :parts -end - -class Part - belongs_to :service -end - -# Ability -can :manage, Part, service: { account: { user: { id: user.id } } } -``` - -## Combining Abilities - -It is possible to define multiple abilities for the same resource. Here the user will be able to read projects which are released OR available for preview. - -```ruby -can :read, Project, released: true -can :read, Project, preview: true -``` - -The `cannot` method takes the same arguments as `can` and defines which actions the user is unable to perform. This is normally done after a more generic `can` call. - -```ruby -can :manage, Project -cannot :destroy, Project -``` - -The order of these calls is important. See [Ability Precedence](./Ability-Precedence.md) for more details. - -## Additional Docs - -* [Defining Abilities: Best Practices](./Defining-Abilities:-Best-Practices.md) -* [Defining Abilities with Blocks](./Defining-Abilities-with-Blocks.md) -* [Checking Abilities](./Checking-Abilities.md) -* [Testing Abilities](./Testing-Abilities.md) -* [Debugging Abilities](./Debugging-Abilities.md) -* [Ability Precedence](./Ability-Precedence.md) diff --git a/docs/Defining-Abilities:-Best-Practices.md b/docs/Defining-Abilities:-Best-Practices.md deleted file mode 100644 index f7127664e..000000000 --- a/docs/Defining-Abilities:-Best-Practices.md +++ /dev/null @@ -1,101 +0,0 @@ -# Defining Abilities: Best Practices - -## Use hash conditions as much as possible - -Here's why: - -**1. Although scopes are fine for fetching, they pose a problem when authorizing a discrete action.** - - For example, this declaration in Ability: - - ```ruby - can :read, Article, Article.is_published - ``` - - causes this `CanCan::Error`: - - ``` - The can? and cannot? call cannot be used with a raw sql 'can' definition. - The checking code cannot be determined for :read #
. - ``` - - A better way to define the same is: - - ```ruby - can :read, Article, is_published: true - ``` - -**2. Hash conditions are DRYer.** - - By using hashes instead of blocks for all actions, you won't have to worry about translating blocks used for member controller actions (`:create`, `:destroy`, `:update`) to equivalent blocks for collection actions (`:index`, `:show`)—which require hashes anyway! - -**3. Hash conditions are OR'd in SQL, giving you maximum flexibilty.** - - Every time you define an ability with `can`, each `can` chains together with OR in the final SQL query for that model. - - So if, in addition to the `is_published` condition above, we want to allow authors to see their drafts: - - ```ruby - can :read, Article, author_id: @user.id, is_published: false - ``` - - Then the final SQL would be: - - ```sql - SELECT `articles`.* - FROM `articles` - WHERE `articles`.`is_published` = 1 - OR ( `articles`.`author_id` = 97 AND `articles`.`is_published` = 0 ) - ``` - -**4. For complex object graphs, hash conditions accommodate `joins` easily.** - - See https://github.com/CanCanCommunity/cancancan/blob/develop/docs/Defining-Abilities.md#hash-of-conditions. - -## Give permissions, don't take them away - -As I suggested in this [topic on Reddit](https://www.reddit.com/r/ruby/comments/6ytka8/refactoring_cancancan_abilities_brewing_bits/) you should, when possible, give increasing permissions to your users. -CanCanCan increases permissions, it starts by giving no permissions to nobody and then increases those permissions depending on the user. A properly written ability.rb looks like that: - -```ruby -class Ability - include CanCan::Ability - - def initialize(user) - can :read, Post # start by defining rules for all users, also not logged ones - return unless user.present? - can :manage, Post, user_id: user.id # if the user is logged in can manage it's own posts - can :create, Comment # logged in users can also create comments - return unless user.manager? # if the user is a manager we give additional permissions - can :manage, Comment # like managing all comments in the website - return unless user.admin? - can :manage, :all # finally we give all remaining permissions only to the admins - end -end -``` - -following this good practice will help you to keep your permissions clean and more readable. - -The risk of giving wrong permissions to the wrong users is also decreased. - -## Split your ability.rb file - -Another help, to make CanCanCan work more in a "pundit way" is to define a separate Ability file for each model, or controller, and then use - -```ruby -def current_ability - @current_ability ||= MyAbility.new(current_user) -end -``` - -To use a specific ability file: this way you don't have to load the whole ability.rb file on each request. - -Abilities files can always be merged together, so if you need two of them in one Controller, you can simply: - -```ruby -def current_ability - @current_ability ||= ReadAbility.new(current_user).merge(WriteAbility.new(current_user)) -end -``` - -You can read more about splitting the Ability file in [this article](https://medium.com/@coorasse/cancancan-that-scales-d4e526fced3d) diff --git a/docs/Devise.md b/docs/Devise.md index 4ea1528fb..e36363233 100644 --- a/docs/Devise.md +++ b/docs/Devise.md @@ -1,8 +1,11 @@ -You can bypass CanCanCan's authorization for Devise controllers: +# Devise + +You should bypass CanCanCan's authorization for Devise controllers: ```ruby class ApplicationController < ActionController::Base protect_from_forgery + check_authorization unless: :devise_controller? end ``` diff --git a/docs/Ensure-Authorization.md b/docs/Ensure-Authorization.md deleted file mode 100644 index 6722b3c54..000000000 --- a/docs/Ensure-Authorization.md +++ /dev/null @@ -1,40 +0,0 @@ -If you want to be certain authorization is not forgotten in some controller action, add `check_authorization` to your `ApplicationController`. - -```ruby -class ApplicationController < ActionController::Base - check_authorization -end -``` - -This will add an `after_action` to ensure authorization takes place in every inherited controller action. If no authorization happens it will raise a `CanCan::AuthorizationNotPerformed` exception. You can skip this check by adding `skip_authorization_check` to that controller. Both of these methods take the same arguments as `before_action` so you can exclude certain actions with `:only` and `:except`. - -```ruby -class UsersController < ApplicationController - skip_authorization_check :only => [:new, :create] - # ... -end -``` - -## Conditionally Check Authorization - -As of CanCan 1.6, the `check_authorization` method supports `:if` and `:unless` options. Either one takes a method name as a symbol. This method will be called to determine if the authorization check will be performed. This makes it very easy to skip this check on all Devise controllers since they provide a `devise_controller?` method. - -```ruby -class ApplicationController < ActionController::Base - check_authorization :unless => :devise_controller? -end -``` - -Here's another example where authorization is only ensured for the admin subdomain. - -```ruby -class ApplicationController < ActionController::Base - check_authorization :if => :admin_subdomain? - private - def admin_subdomain? - request.subdomain == "admin" - end -end -``` - -Note: The `check_authorization` only ensures that authorization is performed. If you have `authorize_resource` the authorization will still be performed no matter what is returned here. diff --git a/docs/Fetching-Records.md b/docs/Fetching-Records.md deleted file mode 100644 index 8f64115cc..000000000 --- a/docs/Fetching-Records.md +++ /dev/null @@ -1,47 +0,0 @@ -Sometimes you need to restrict which records are returned from the database based on what the user is able to access. This can be done with the `accessible_by` method on any Active Record model. Simply pass the current ability to find only the records which the user is able to `:index`. - -```ruby -# current_ability is a method made available by CanCanCan in your controllers -@articles = Article.accessible_by(current_ability) -``` - -You can change the action by passing it as the second argument. Here we find only the records the user has permission to update. - -```ruby -@articles = Article.accessible_by(current_ability, :update) -``` - -If you want to use the current controller's action, make sure to call `to_sym` on it: - -```ruby -@articles = Article.accessible_by(current_ability, params[:action].to_sym) -``` - -This is an Active Record scope so other scopes and pagination can be chained onto it. - -This works with multiple `can` definitions, which allows you to define complex permission logic and have it translated properly to SQL. - -Given the definition: -```ruby -class Ability - can :manage, User, manager_id: user.id - cannot :manage, User, self_managed: true - can :manage, User, id: user.id -end -``` -a call to User.accessible_by(current_ability) generates the following SQL - -```sql -SELECT * -FROM users -WHERE (id = 1) OR (not (self_managed = 't') AND (manager_id = 1)) -``` - -It will raise an exception if any requested model's ability definition is defined using just block. -You can define SQL fragment in addition to block (look for more examples in [[Defining Abilities with Blocks]]). - -If you are using something other than Active Record you can fetch the conditions hash directly from the current ability. - -```ruby -current_ability.model_adapter(TargetClass, :read).conditions -``` \ No newline at end of file diff --git a/docs/Issue-Collaborators.md b/docs/Issue-Collaborators.md deleted file mode 100644 index e989aa01f..000000000 --- a/docs/Issue-Collaborators.md +++ /dev/null @@ -1,27 +0,0 @@ -The CanCan issue tracker has gotten out of hand because I have not had time to work on it recently. I am bringing on several Issue Collaborators to help. My goal is to make CanCan the best it can be and getting the issue tracker under control will help give me a clear direction on where to take it in 2.0. - -**Note: even though issue collaborators have full commit access, please do not make any commits or merge in any pull requests.** I am just looking for help cleaning up the issue tracker at the moment. I will likely take on full collaborators in the future. - -### Guidelines - -* **Questions:** If someone has a question that can be solved with the [wiki docs](https://github.com/ryanb/cancan/wiki) please point them to the appropriate docs and close the issue. If the question is not clearly answered by the wiki please improve the wiki so that it is and close the issue. If you do not have time to add docs at the moment, tag it with `docs` and `help` labels and keep it open. - -* **Feature Requests:** If it is a feature request that could go in CanCan 2.0, please tag it with `2.0` and `feature` tags. If you are uncertain whether it's a good idea, add a `discuss` tag to get some feedback. I don't plan to add features to CanCan 1 at this point, if it only applies to that release please close it and add a comment saying so. - -* **Dormant Issues:** If you are uncertain if an issue is still applicable and do not want to spend time investigating it, just ask "Are you still having this problem?" and tag with `waiting`. If you do not get a response within a week or so, close the issue. Mention you can open the issue again if they respond. - -* **Duplicate Issues:** If it seems like a common issue, do a search and look for a duplicate. If it is, close the issue and link to the other original one. - -* **Bug Reports:** If CanCan is not behaving in a way that it is documented to, add the `bug` label. Please verify this bug by trying it on your own and add a `verified` label to it if you can duplicate the problem. - - If you would like to submit a pull request to fix this bug, assign the issue to yourself so others know you are working on it. If not, add a comment saying you are looking for someone to write a pull request and add a `help` label to it. When a pull request is available, close the original issue and link to it from the pull request. - -* **Pull Requests:** Please try pull requests on your local machine to see if the tests pass and the functionality works as described. If so, add a `verified` label. Also add a `bug` or `feature` label depending on the type of request. If it is urgent, add a `critical` tag and ping me at @rbates on Twitter and I'll try to get it pulled in quickly. - - I will be reluctant to merge pull requests that are large or have features I feel unnecessary. Please add a comment to pull requests explaining your thinking on if it should be merged in and if you can think of a better way to do it. - -It is a good idea to occasionally check the `help` and `discuss` tags to give your input on other issues. - -**Final note:** if you are ever uncertain about whether to close an issue or leave it open. Close it and add a note saying you will open it again if someone comments. If it is beyond your expertise, just tag it with `help` and move on. - -Thank you very much for your help in cleaning up the issue tracker. If you have any questions, send me an email and I'll update this. diff --git a/docs/Link-Helpers.md b/docs/Link-Helpers.md deleted file mode 100644 index 89225fd5c..000000000 --- a/docs/Link-Helpers.md +++ /dev/null @@ -1,39 +0,0 @@ -Generally you only want to show new/edit/destroy links when the user has permission to perform that action. You can do so like this in the view. - -```rhtml -<% if can? :update, @project %> - <%= link_to "Edit", edit_project_path(@project) %> -<% end %> -``` - -However if you find yourself repeating this pattern often you may want to add helper methods like this. - -```ruby -# in ApplicationHelper -def show_link(object, content = "Show") - link_to(content, object) if can?(:read, object) -end - -def edit_link(object, content = "Edit") - link_to(content, [:edit, object]) if can?(:update, object) -end - -def destroy_link(object, content = "Destroy") - link_to(content, object, :method => :delete, :confirm => "Are you sure?") if can?(:destroy, object) -end - -def create_link(object, content = "New") - if can?(:create, object) - object_class = (object.kind_of?(Class) ? object : object.class) - link_to(content, [:new, object_class.name.underscore.to_sym]) - end -end -``` - -Then a link is as simple as this. - -```rhtml -<%= edit_link @project %> -``` - -I only recommend doing this if you see this pattern a lot in your application. There are times when the view code is more complex where this doesn't fit well. \ No newline at end of file diff --git a/docs/MetaWhere.md b/docs/MetaWhere.md deleted file mode 100644 index d94bc6469..000000000 --- a/docs/MetaWhere.md +++ /dev/null @@ -1 +0,0 @@ -MetaWhere is not supported anymore diff --git a/docs/Mongoid.md b/docs/Mongoid.md deleted file mode 100644 index 971b61396..000000000 --- a/docs/Mongoid.md +++ /dev/null @@ -1,17 +0,0 @@ -** **Attention: Supported only on cancancan < 2.0!** ** - -CanCanCan supports [[Mongoid|http://mongoid.org]]. All you have to do is mention `mongoid` before `cancan` in your Gemfile so it is required first. - -```ruby -gem "mongoid" -gem "cancan" -``` - -That is it, you can now call `accessible_by` on any Mongoid document (which is done automatically in the `index` action). You can also use the query syntax that Mongoid provides when defining the abilities. - -```ruby -# in Ability -can :read, Article, :priority.lt => 5 -``` - -This is all done through a [[Model Adapter]]. See that page for more information and how you can add your own. \ No newline at end of file diff --git a/docs/Multiple-can-definitions.textile b/docs/Multiple-can-definitions.textile deleted file mode 100644 index ada51f951..000000000 --- a/docs/Multiple-can-definitions.textile +++ /dev/null @@ -1,35 +0,0 @@ -h2. Multiple `can` definitions - -It is possible to specify multiple `can` and `cannot` definitions with hashes and have it properly translate to a single SQL query. - -```ruby -# in ability.rb -can :manage, User, id: 1 -can :manage, User, manager_id: 1 -cannot :manage, User, self_managed: true -``` - -When using `accessible_by` it will translate to SQL conditions that look like this. - -```sql -not (self_managed = 't') AND ((manager_id = 1) OR (id = 1)) -``` - -If you have the following definition: - -```ruby -can :manage, User, id: user.id -can :assign_roles, User do - user.admin? -end -``` - -and you call `can? :assign_roles, some_user` it evaluates to `true` when `current_user == some_user` because it falls back to `can :manage, User, id: user.id`. - -Proper can definition should be now: - -```ruby -can :manage, User, id: user.id -cannot :assign_roles, User -can :assign_roles, User if user.admin? -``` \ No newline at end of file diff --git a/docs/Non-RESTful-Controllers.md b/docs/Non-RESTful-Controllers.md deleted file mode 100644 index 56035128a..000000000 --- a/docs/Non-RESTful-Controllers.md +++ /dev/null @@ -1,35 +0,0 @@ -You can use CanCan with controllers that do not follow the traditional show/new/edit/destroy actions, however you should not use the `load_and_authorize_resource` method since there is no resource to load. Instead you can call `authorize!` in each action separately. - -**NOTE:** This is **not** the same as having additional non-RESTful actions on a RESTful controller. See the Choosing Actions section of the [[Authorizing Controller Actions]] page for details. - -For example, let's say we have a controller which does some miscellaneous administration tasks such as rolling log files. We can use the `authorize!` method here. - -```ruby -class AdminController < ActionController::Base - def roll_logs - authorize! :roll, :logs - # roll the logs here - end -end -``` - -And then authorize that in the `Ability` class. - -```ruby -can :roll, :logs if user.admin? -``` - -Notice you can pass a symbol as the second argument to both `authorize!` and `can`. It doesn't have to be a model class or instance. Generally the first argument is the "action" one is trying to perform and the second argument is the "subject" the action is being performed on. It can be anything. - -## Alternative: authorize_resource - -Alternatively you can use the `authorize_resource` and specify that there's no class. This way it will pass the resource symbol instead. This is good if you still have a Resource-like controller but no model class backing it. - -```ruby -class ToolsController < ApplicationController - authorize_resource :class => false - def show - # automatically calls authorize!(:show, :tool) - end -end -``` \ No newline at end of file diff --git a/docs/Other-Authorization-Solutions.md b/docs/Other-Authorization-Solutions.md deleted file mode 100644 index eed470c4b..000000000 --- a/docs/Other-Authorization-Solutions.md +++ /dev/null @@ -1,7 +0,0 @@ -There are many authorization solutions available, and it is important to find one which best meets the application requirements. - -We try to keep CanCanCan minimal yet extendable so it can be used in many situations, but there are times it doesn't fit the best. - -If you find the conditions hash to be too limiting I encourage you to check out [[Pundit|https://github.com/elabs/pundit]] which offers a sophisticated DSL for handling more complex permission scenarios. This allows one to generate complex database queries based on the permissions but at the cost of a more complex DSL. - -Also consider, if you have very unique authorization requirements, the best choice may be to write your own solution instead of trying to shoe-horn an existing plugin. \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index bdee930b3..2bedc190d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,42 +1,44 @@ -### Getting Started - -* [Defining Abilities](./Defining-Abilities.md), [Best Practices](https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities%3A-Best-Practices) -* [Checking Abilities](./Checking-Abilities.md) -* [Authorizing Controller Actions](./Authorizing-controller-actions.md) -* [Exception Handling](./Exception-Handling.md) -* [Ensure Authorization](./Ensure-Authorization.md) -* [Changing Defaults](./Changing-Defaults.md) -* [Translations (i18n)](./Translations-(i18n).md) - -### More about Abilities - -* [Testing Abilities](./Testing-Abilities.md) -* [Debugging Abilities](./Debugging-Abilities.md) -* [Ability Precedence](./Ability-Precedence.md) -* [Fetching Records](./Fetching-Records.md) -* [Action Aliases](./Action-Aliases.md) -* [Custom Actions](./Custom-Actions.md) -* [Role Based Authorization](./Role-Based-Authorization.md) - - -### More about Controllers & Views - -* [Controller Authorization Example](./Controller-Authorization-Example.md) -* [Nested Resources](./Nested-Resources.md) -* [Strong Parameters](./Strong-Parameters.md) -* [Non RESTful Controllers](./Non-RESTful-Controllers.md) -* [Link Helpers](./Link-Helpers.md) - - -### Other Use Cases - -* [Inherited Resources](./Inherited-Resources.md) -* [Mongoid](./Mongoid.md) -* [Rails Admin](https://github.com/sferik/rails_admin/wiki/CanCanCan) -* [Devise](./Devise.md) -* [Accessing Request Data](./Accessing-request-data.md) -* [Abilities in Database](./Abilities-in-Database.md) -* [Ability for Other Users](./Ability-for-Other-Users.md) -* [Other Authorization Solutions](./Other-Authorization-Solutions.md) - -**Can't find what you're looking for? [Submit a Question on StackOverflow](http://stackoverflow.com/questions/ask?tags=cancancan) +# CanCanCan - Developer guide + +This is the official guide to CanCanCan. + +It will advance chapter by chapter and go more and more into details, advanced usages, and special cases. + +We will start by introducing basic concepts and features, and then dig deeper into configurations and implementation details in later chapters. + +You can skip the [Introduction](./introduction.md) where there's just some history and blablabla and go directly to [Installation](./installation) to start fast :rocket:. + +## Summary + +1. [Introduction](./introduction.md) +1. [Installation](./installation.md) +1. [Define and check abilities](./define_check_abilities.md) +1. [Controller helpers](./controller_helpers.md) +1. [Fetching records](./fetching_records.md) +1. [Cannot](./cannot.md) +1. [Hash of conditions](./hash_of_conditions.md) +1. [Combine Abilities](./combine_abilities.md) +1. [Check abilities - avoid mistakes](./check_abilities_mistakes.md) +1. [Handling CanCan::AccessDenied](./handling_access_denied.md) +1. [Customize controller helpers](./changing_defaults.md) +1. [Accessing request data](./accessing_request_data.md) +1. [SQL strategies](./sql_strategies.md) +1. [Accessible attributes](./accessible_attributes.md) +1. [Testing](/.testing.md) +1. [Internationalization](./internationalization.md) + +## Further topics + +In these topics, you will learn some best practices, but also how to solve specific integration issues with other libraries or how to extend CanCanCan. + +1. [Migrating](./migrating.md) +1. [Debugging Abilities](/.debugging.md) +1. [Split your ability file](./split_ability.md) +1. [Define Abilities - best practices](./define_abilities_best_practices.md) +1. [Abilities in database](./abilities_in_database.md) +1. [Role-based Authorization](./role_based_authorization.md) +1. [Model Adapter](./model_adapter.md) +1. [Rules compression](./rules_compression.md) +1. [Inherited Resources](./inherited_resources.md) +1. [Devise](./devise.md) +1. [FriendlyId](./friendly_id.md) \ No newline at end of file diff --git a/docs/Separate-Role-Model.md b/docs/Separate-Role-Model.md deleted file mode 100644 index a2f3f0cd6..000000000 --- a/docs/Separate-Role-Model.md +++ /dev/null @@ -1,87 +0,0 @@ -This approach uses a separate role and shows how to setup a many-to-many association, Assignment, between User and Role. Alternatively, [[Role Based Authorization]] describes a simple ruby based approach that defines the roles within ruby. - -```ruby -class User < ActiveRecord::Base - has_many :assignments - has_many :roles, :through => :assignments -end - -class Assignment < ActiveRecord::Base - belongs_to :user - belongs_to :role -end - -class Role < ActiveRecord::Base - has_many :assignments - has_many :users, :through => :assignments -end -``` - -You can assign roles using checkboxes when creating or updating a user model. - -```rhtml -<% for role in Role.all %> -
- <%= check_box_tag "user[role_ids][]", role.id, @user.roles.include?(role) %> - <%=h role.name %> -
-<% end %> -<%= hidden_field_tag "user[role_ids][]", "" %> -``` - -Or you may want to [[use Formtastic|http://railscasts.com/episodes/185-formtastic-part-2]] for this. - -Next you need to determine if a user is in a specific role. You can create a method in the User model for this. - -```ruby -# in models/user.rb -def has_role?(role_sym) - roles.any? { |r| r.name.underscore.to_sym == role_sym } -end -``` - -And then you can use this in your Ability. - -```ruby -# in models/ability.rb -def initialize(user) - user ||= User.new # in case of guest - if user.has_role? :admin - can :manage, :all - else - can :read, :all - end -end -``` - -That's it! - -## Role Inheritance Within Ability.rb - -You can use the Alternative Role Inheritance strategy described in [[Role Based Authorization|https://github.com/ryanb/cancan/wiki/Role-Based-Authorization]] with one minor modification: change "send(role)" to "send(role.name.downcase)" assuming name is the column describing the role's name in the database. - -```ruby -class Ability - include CanCan::Ability - - def initialize(user) - @user = user || User.new # for guest - @user.roles.each { |role| send(role.name.downcase) } - - if @user.roles.size == 0 - can :read, :all #for guest without roles - end - end - - def manager - can :manage, Employee - end - - def admin - manager - can :manage, Bill - end -end -``` - -Here each role is a separate method which is called. You can call one role inside another to define inheritance. This assumes you have a `User#roles` method which returns an array of all roles for that user. \ No newline at end of file diff --git a/docs/Share-Ability-Definitions.md b/docs/Share-Ability-Definitions.md deleted file mode 100644 index 456389aa4..000000000 --- a/docs/Share-Ability-Definitions.md +++ /dev/null @@ -1,9 +0,0 @@ -Let's say the ability of one action depends on the ability of another. For example, what if we have a `Project` which `has_many :tasks` and we want a task's update ability to be dependent on whether the user can update the project. We can perform the `can?` call within the ability definition to check the project permission. - -```ruby -can :update, Task do |task| - can?(:update, task.project) -end -``` - -With this it is easy to define one ability based on another. \ No newline at end of file diff --git a/docs/Strong-Parameters.md b/docs/Strong-Parameters.md deleted file mode 100644 index ccda8bc7b..000000000 --- a/docs/Strong-Parameters.md +++ /dev/null @@ -1,140 +0,0 @@ -CanCanCan supports Strong Parameters without controller workarounds. -When using strong_parameters or Rails 4+, you have to sanitize inputs before saving the record, in actions such as `:create` and `:update`. - -By default, CanCanCan will try to sanitize the input on `:create` and `:update` routes by seeing if your controller will respond to the following methods (in order): - -### By Action - -If you specify a `create_params` or `update_params` method, CanCan will run that method depending on the action you are performing. - -```ruby -class ArticlesController < ApplicationController - load_and_authorize_resource - - def create - if @article.save - # hurray - else - render :new - end - end - - def update - if @article.update_attributes(update_params) - # hurray - else - render :edit - end - end - - private - - def create_params - params.require(:article).permit(:name, :email) - end - - def update_params - params.require(:article).permit(:name) - end -end -``` - -### By Model Name - -If you follow the convention in rails for naming your param method after the applicable model's class `_params` such as `article_params`, CanCanCan will automatically detect and run that params method. - -```ruby -class ArticlesController < ApplicationController - load_and_authorize_resource - - def create - if @article.save - # hurray - else - render :new - end - end - - private - - def article_params - params.require(:article).permit(:name) - end -end -``` - -#### When Model and Controller names differ - -When you specify `class` option note that the method will still be `articles_params` and not `post_params`, since we are in `ArticlesController`. - -```ruby -class ArticlesController < ApplicationController - load_and_authorize_resource class: 'Post' - - def create - if @article.save - # hurray - else - render :new - end - end - - private - - def article_params - params.require(:article).permit(:name) - end -end -``` - -### By Static Method Name - -CanCanCan also recognizes a static method name: `resource_params`, as a general param method name you can use to standardize on. - -```ruby -class ArticlesController < ApplicationController - load_and_authorize_resource - - def create - if @article.save - # hurray - else - render :new - end - end - - private - - def resource_params - params.require(:article).permit(:name) - end -end -``` - -### By Custom Method - -Additionally, load_and_authorize_resource can now take a `param_method` option to specify a custom method in the controller to run to sanitize input. - -```ruby -class ArticlesController < ApplicationController - load_and_authorize_resource param_method: :my_sanitizer - - def create - if @article.save - # hurray - else - render :new - end - end - - private - - def my_sanitizer - params.require(:article).permit(:name) - end -end -``` - -### No Strong Parameters - -No problem, if your controllers do not respond to any of the above methods, it will ignore and continue execution as normal. \ No newline at end of file diff --git a/docs/Abilities-in-Database.md b/docs/abilities_in_database.md similarity index 100% rename from docs/Abilities-in-Database.md rename to docs/abilities_in_database.md diff --git a/docs/accessible_attributes.md b/docs/accessible_attributes.md new file mode 100644 index 000000000..d97273fda --- /dev/null +++ b/docs/accessible_attributes.md @@ -0,0 +1,37 @@ +# Accessible attributes + +CanCanCan gives you the possibility to define actions on single instances' attributes. + +Given you want users to only read a user first name and last name you can define: + +```ruby +can :read, User, :first_name, :last_name +``` + +and check it with: + +```ruby +can? :read, @user, :first_name +``` + +You can also ask for all the allowed attributes: + +```ruby +current_ability.permitted_attributes(:read, @user) +#=> [:first_name, :last_name] +``` + +This can be used, for example, to display a form: + +```ruby +current_ability.permitted_attributes(:read, @book).each do |attr| + = form.input attr +``` + +or in Strong Parameters: + +```ruby +params + .require(:book) + .permit(ability.permitted_attributes(:read, @book)) +``` \ No newline at end of file diff --git a/docs/accessing_request_data.md b/docs/accessing_request_data.md new file mode 100644 index 000000000..c5cbea389 --- /dev/null +++ b/docs/accessing_request_data.md @@ -0,0 +1,26 @@ +# Accessing request data + +What if you need to modify the permissions based on something outside of the User object? For example, let's say you want to forbid certain IP addresses from creating comments. The IP address is accessible through request.remote_ip but the Ability class does not have access to this. It's easy to modify what you pass to the Ability object by overriding the current_ability method in ApplicationController. + +```ruby +class ApplicationController < ActionController::Base + #... + + private + + def current_ability + @current_ability ||= Ability.new(current_user, request.remote_ip) + end +end +``` +```ruby +class Ability + include CanCan::Ability + + def initialize(user, ip_address=nil) + can :create, Comment unless DENYLIST_IPS.include? ip_address + end +end +``` + +This concept can apply to session and cookies as well. diff --git a/docs/cannot.md b/docs/cannot.md new file mode 100644 index 000000000..de6b2e646 --- /dev/null +++ b/docs/cannot.md @@ -0,0 +1,14 @@ +# Cannot + +Yes, sometimes you might need to **remove** permissions. Even if we said that CanCanCan assumes that by default no one has access to any resource, there are situations where you might need to remove an ability. + +The `cannot` method takes the same arguments as `can` and defines which actions the user is unable to perform. This is normally done after a more generic `can` call. + +```ruby +can :manage, Project +cannot :destroy, Project +``` + +will allow the user to do **any** action but destroy the project. + +Of course, there's a `cannot?` method to check abilities that is a simple alias for `!can?`. \ No newline at end of file diff --git a/docs/changing_defaults.md b/docs/changing_defaults.md new file mode 100644 index 000000000..7db84fd2f --- /dev/null +++ b/docs/changing_defaults.md @@ -0,0 +1,289 @@ +# Customize the controller helpers + +We now dig deeper in the customizations and options we have when working with [controller helpers](./controller_helpers.md) + +## current_ability and current_user + +CanCanCan makes two assumptions about your application: + +* You have an `Ability` class which defines the permissions. +* You have a `current_user` method in the controller which returns the current user model. + +You can override both of these by defining the `current_ability` method in your `ApplicationController`. The default method looks like this. + +```ruby +def current_ability + @current_ability ||= Ability.new(current_user) +end +``` + +The `Ability` class and `current_user` method can easily be changed to something else. + +```ruby +# in ApplicationController +def current_ability + @current_ability ||= AccountAbility.new(current_account) +end +``` + +Sometimes you might have a gem in your project which provides its own Rails engine which also uses CanCanCan, in this case the current_ability override in the ApplicationController can also be useful. + +```ruby +# in ApplicationController +def current_ability + if request.fullpath =~ /\/rails_admin/ + @current_ability ||= RailsAdmin::Ability.new(current_user) + else + @current_ability ||= Ability.new(current_user) + end +end +``` + +If your method that returns the currently logged in user just has another name than `current_user`, it may be the easiest solution to simply alias the method in your ApplicationController like this: + +```ruby +class ApplicationController < ActionController::Base + alias_method :current_user, :name_of_your_method # Could be :current_member or :logged_in_user +end +``` + +## Strong parameters + +If your parameters sanitization method does not follow the naming convention, `load_and_authorize_resource` takes a `param_method` option to specify a custom method in the controller to run to sanitize input. + +You can associate the `param_method` option with a symbol corresponding to the name of a method that will get called: + +```ruby +class ArticlesController < ApplicationController + load_and_authorize_resource param_method: :my_sanitizer + + def create + @article.save + end + + private + + def my_sanitizer + params.require(:article).permit(:name) + end +end +``` + +You can also use a string that will be evaluated in the context of the controller using `instance_eval` and needs to contain valid Ruby code. + +```ruby +load_and_authorize_resource param_method: 'permitted_params.post' +``` + +Finally, it's possible to associate `param_method` with a Proc object which will be called with the controller as the only argument: + +```ruby +load_and_authorize_resource param_method: -> { |c| c.params.require(:article).permit(:name) } +``` + +If your model name and controller name differ, you can specify a `class` option. + +> Note that the method will still be `articles_params` and not `post_params`, since we are in `ArticlesController`. + +```ruby +class ArticlesController < ApplicationController + load_and_authorize_resource class: 'Post' + + def create + @article.save + end + + private + + def article_params + params.require(:article).permit(:name) + end +end +``` + +## Non RESTful controllers + +You can use CanCanCan with controllers that do not follow the traditional REST actions, however you should not use the `load_and_authorize_resource` method since there is no resource to load. Instead you can call `authorize!` in each action separately. + + +For example, let's say we have a controller which does some miscellaneous administration tasks such as rolling log files. We can use the `authorize!` method here. + +```ruby +class AdminController < ActionController::Base + def roll_logs + authorize! :roll, :logs + # roll the logs here + end +end +``` + +And then authorize that in the `Ability` class. + +```ruby +can :roll, :logs if user.admin? +``` + +Notice you can pass a symbol as the second argument to both `authorize!` and `can`. It doesn't have to be a model class or instance. + +Alternatively you can use the `authorize_resource` and specify that there's no class. This way it will pass the resource symbol instead. This is good if you still have a Resource-like controller but no model class backing it. + +```ruby +class ToolsController < ApplicationController + authorize_resource class: false + + def show + # automatically calls authorize!(:show, :tool) + end +end +``` + +## skip load and authorize + +You can use the `skip_load_and_authorize_resource`, `skip_load_resource` or `skip_authorize_resource` methods to skip any of the applied behavior and specify specific actions like in a before filter. For example: + +```ruby +class ProductsController < ActionController::Base + load_and_authorize_resource + skip_authorize_resource only: :new +end +``` + +### Custom class name + +If the model is named differently than the controller, then you may explicitly name the model that should be loaded; however, you must specify that it is not a parent in a nested routing situation, ie: + +```ruby +class ArticlesController < ApplicationController + load_and_authorize_resource :post, parent: false +end +``` + +If the model class is namespaced differently than the controller you will need to specify the `:class` option. + +```ruby +class ProductsController < ApplicationController + load_and_authorize_resource class: "Store::Product" +end +``` + + +### Custom find + +If you want to fetch a resource by something other than `id` it can be done so using the `find_by` option. + +```ruby +load_resource find_by: => :permalink # will use find_by!(permalink: params[:id]) +authorize_resource +``` + +### Override loading + +The resource will only be loaded into an instance variable if it hasn't been already. This allows you to easily override how the loading happens in a separate `before_action`. + +```ruby +class BooksController < ApplicationController + before_action :find_published_book, only: :show + load_and_authorize_resource + + private + + def find_published_book + @book = Book.released.find(params[:id]) + end +end +``` + +## check_authorization + +If you want to be certain authorization is not forgotten in some controller action, add `check_authorization` to your `ApplicationController`. + +```ruby +class ApplicationController < ActionController::Base + check_authorization +end +``` + +This will add an `after_action` to ensure authorization takes place in every inherited controller action. If no authorization happens it will raise a `CanCan::AuthorizationNotPerformed` exception. You can skip this check by adding `skip_authorization_check` to that controller. Both of these methods take the same arguments as `before_action` so you can exclude certain actions with `:only` and `:except`. + +```ruby +class UsersController < ApplicationController + skip_authorization_check :only => [:new, :create] + # ... +end +``` + +The `check_authorization` method supports `:if` and `:unless` options. Either one takes a method name as a symbol. This method will be called to determine if the authorization check will be performed. This makes it very easy to skip this check on all Devise controllers since they provide a `devise_controller?` method. + +```ruby +class ApplicationController < ActionController::Base + check_authorization unless: :devise_controller? +end +``` + +Here's another example where authorization is only ensured for the admin subdomain. + +```ruby +class ApplicationController < ActionController::Base + check_authorization if: :admin_subdomain? + + private + + def admin_subdomain? + request.subdomain == "admin" + end +end +``` + +> Note: The `check_authorization` only ensures that authorization is performed. If you have `authorize_resource` the authorization will still be performed no matter what is returned here. + +The default operation for CanCanCan is to authorize based on user and the object identified in `load_resource`. So if you have a `WidgetsController` and also an `Admin::WidgetsController`, you can use some different approaches. + +# Overriding authorizations for Namespaced controllers + +You can create differing authorization rules that depend on the controller namespace. + +In this case, just override the `current_ability` method in `ApplicationController` to include the controller namespace, and create an `Ability` class that knows what to do with it. + +``` ruby +class Admin::WidgetsController < ActionController::Base + #... + + private + + def current_ability + # I am sure there is a slicker way to capture the controller namespace + controller_name_segments = params[:controller].split('/') + controller_name_segments.pop + controller_namespace = controller_name_segments.join('/').camelize + @current_ability ||= Ability.new(current_user, controller_namespace) + end +end + + +class Ability + include CanCan::Ability + + def initialize(user, controller_namespace) + case controller_namespace + when 'Admin' + can :manage, :all if user.has_role? 'admin' + else + # rules for non-admin controllers here + end + end +end +``` + +Another way to achieve the same is to use a completely different Ability class in this controller: + +``` ruby +class Admin::WidgetsController < ActionController::Base + #... + + private + + def current_ability + @current_ability ||= AdminAbility.new(current_user) + end +end +``` diff --git a/docs/check_abilities_mistakes.md b/docs/check_abilities_mistakes.md new file mode 100644 index 000000000..7b16f7787 --- /dev/null +++ b/docs/check_abilities_mistakes.md @@ -0,0 +1,48 @@ +# Check abilities - Avoid common mistakes + +You now know that you can use the `can?` method in the controller or view to check the user's permission for a given action and object. + +```ruby +can? :destroy, @article +``` + +and also that the `cannot?` method is for convenience and performs the opposite check of `can?` + +```ruby +cannot? :destroy, @article +``` + +What we want to explain you in this chapter is that you can also pass the class instead of a single instance: + +```rhtml +<% if can? :create, Project %> + <%= link_to "New Project", new_project_path %> +<% end %> +``` + +It's important to note here that if a block or hash of conditions exist they will be ignored when checking on a class, and it will return `true`. For example: + +```ruby +can :read, Project, priority: 3 +can? :read, Project # returns true +``` + +It is impossible to answer this `can?` question completely because not enough detail is given. Here the class does not have a `priority` attribute to check on. + +Think of it as asking + +> can the current user read **a** project?" + +The user can read a project, so this returns `true`. However it depends on which specific project you're talking about. + +If you are doing a class check, it is important you do another check once an instance becomes available so the hash of conditions can be used. + +The reason for this behavior is because of the controller `index` action. Since the `authorize_resource` before filter has no instance to check on, it will use the `Project` class. If the authorization failed at that point then it would be impossible to filter the results later when [Fetching Records](./fetching_records.md). + +That is why passing a class to `can?` will return `true`. + +The code answering the question "can the user update all the articles?" would be something like: + +``` ruby +Article.accessible_by(current_ability).count == Article.count +``` diff --git a/docs/combine_abilities.md b/docs/combine_abilities.md new file mode 100644 index 000000000..8cfaaf7bc --- /dev/null +++ b/docs/combine_abilities.md @@ -0,0 +1,59 @@ +# Combine abilities + +It is possible to define multiple abilities for the same resource. Here the user will be able to read projects which are released OR available for preview. + +```ruby +can :read, Project, released: true +can :read, Project, preview: true +``` + +The `cannot` method takes the same arguments as `can` and defines which actions the user is unable to perform. This is normally done after a more generic `can` call. + +```ruby +can :manage, Project +cannot :destroy, Project +``` + +The order of these calls is important. + +## Abilities precedence + +An ability rule will override a previous one. + +For example, let's say we want the user to be able to do everything to projects except destroy them. + +This is the correct way: + +```ruby +can :manage, Project +cannot :destroy, Project +``` + +It is important that the `cannot :destroy` line comes after the `can :manage` line. If they were reversed, `cannot :destroy` would be overridden by `can :manage`. + +Adding `can` rules does not override prior rules, but instead are logically or'ed. + +```ruby +can :manage, Project, user: user +can :update, Project, locked: false +``` + +For the above, `can? :update, @project` will return true if project owner is the user, even if the project is locked. + +This is also important when dealing with roles which have inherited behavior. For example, let's say we have two roles, moderator and admin. We want the admin to inherit the moderator's behavior. + +```ruby +if user.moderator? + can :manage, Project + cannot :destroy, Project + can :manage, Comment +end + +if user.admin? + can :destroy, Project +end +``` + +Here it is important for the admin permissions to be defined after the moderator ones, so it can override the `cannot` behavior to give the admin more permissions. + +Let's now check at a different way of defining abilities: [blocks](./define_abilities_with_blocks.md). \ No newline at end of file diff --git a/docs/controller_helpers.md b/docs/controller_helpers.md new file mode 100644 index 000000000..feeddf1ea --- /dev/null +++ b/docs/controller_helpers.md @@ -0,0 +1,176 @@ +# Controller helpers + +As mentioned in the chapter [Define and check abilities](./define_and_check_abilities.md), the `can?` method works at its bets in Rails controllers and views. +This of course doesn't mean that it cannot be used everywhere. + +We know already that in order to check if the user is allowed to perform a certain action we need to have a `current_user` method available and we can check the permission with `can? :update, @article`. + +We can easily protect the `edit` and `update` actions of our controller by checking for the permission. Here is a very simple example: + +```ruby +class ArticlesController < ApplicationController + def edit + @article = Article.find(params[:id]) + if can? :edit, @article + render :edit + else + head :forbidden + end + end +end +``` + +## authorize! + +CanCanCan provides us a `authorize!` helper that allows us to simplify the code above: + +```ruby +def edit + @article = Article.find(params[:id]) + authorize! :edit, @article + render :edit +end +``` + +`authorize!` will raise a `CanCan::AccessDenied` if the action is not permitted. + +You can have a global configuration on how to react to this exception in `config/application.rb`: + +```ruby +config.action_dispatch.rescue_responses.merge!('CanCan::AccessDenied' => :unauthorized) +``` + +The [Handling CanCan::AccessDenied Exception](./handling_exception.md) chapter digs deeper on how to handle the exception raised by `authorize!`. + +> `:unauthorized` might not be your favourite return status of you don't want to reveal to the user that the article exists. In such cases `:not_found` would be a better http status. + +# authorize_resource, load_resource, load_and_authorize_resource + +In a RESTful controller, calling `authorize! action` for every action can be tedious. Here we will show you, step by step, how to improve the code above. + +Add `authorize_resource` in your controller, to call automatically `authorize! action_name, @article` for every action. +The code above can be refactored like this: + +```ruby +class ArticlesController < ApplicationController + before_action :load_article + authorize_resource + + def edit; end + + protected + + def load_article + @article = Article.find(params[:id]) + end +end +``` + +the second helper method is `load_resource` that will perform the loading of the model automatically based on the name of the controller. The code above can be refactored like that: + +```ruby +class ArticlesController < ApplicationController + load_resource + authorize_resource + + def edit; end +end +``` + +and, clearly, `load_and_authorize_resource` allows to do the following: + +```ruby +class ArticlesController < ApplicationController + load_and_authorize_resource + + def edit; end +end +``` + +this means that a completely authorized `ArticlesController` would look as follow: + +```ruby +class ArticlesController < ApplicationController + load_and_authorize_resource + + def index + # @articles are already loaded...see details in later chapter + end + + def show + # the @article to show is already loaded and authorized + end + + def create + # the @article to create is already loaded, authorized, and params set from article_params + @article.create + end + + def edit + # the @article to edit is already loaded and authorized + end + + def update + # the @article to update is already loaded and authorized + @article.update(article_params) + end + + def destroy + # the @article to destroy is already loaded and authorized + @article.destroy + end + + protected + + def article_params + params.require(:article).permit(:body) + end +end +``` + +## Strong parameters + +You have to sanitize inputs before saving the record, in actions such as `:create` and `:update`. + +For the `:update` action, CanCanCan will load and authorize the resource but **not** change it automatically, so the typical usage would be something like: + +```ruby +def update + if @article.update(article_params) + # hurray + else + render :edit + end +end +... + +def article_params + params.require(:article).permit(:body) +end +``` + +For the `:create` action, CanCanCan will try to initialize a new instance with sanitized input by seeing if your controller will respond to the following methods (in order): + +1. `create_params` +2. `_params` such as `article_params` (this is the default convention in Rails for naming your param method) +3. `resource_params` (a generic named method you could specify in each controller) + +The typical usage will then be the following: + +```ruby +def create + if @article.save + # hurray + else + render :new + end +end +``` + +> If you specify a `create_params` or `update_params` method, CanCan will run that method depending on the action you are performing. + +In the chapter dedicated to [Customize controller helpers](./changing_defaults.md) we will see more details and customizations for controllers. + +There's a dedicated chapter to [Nested resources](./nested_resources.md). + +Now that we know how Rails controllers should be protected, we can learn about the most powerful CanCanCan feature: [fetching records](./fetching_records.md). diff --git a/docs/Debugging-Abilities.md b/docs/debugging.md similarity index 76% rename from docs/Debugging-Abilities.md rename to docs/debugging.md index 829722d71..772016dc1 100644 --- a/docs/Debugging-Abilities.md +++ b/docs/debugging.md @@ -1,4 +1,8 @@ -What do you do when permissions you defined in the Ability class don't seem to be working properly? First try to duplicate this problem in the `rails console` or better yet, see [[Testing Abilities]]. +# Debugging Abilities + +What do you do when permissions you defined in the Ability class don't seem to be working properly? + +Have you already read the [Testing](./testing.md) section? You can now try to reproduce this problem in the `rails console`. ## Debugging Member Actions @@ -16,7 +20,7 @@ Note: this assumes that the model instance is being loaded properly. If you are ability.can?(:create, Project) ``` -## Debugging index Action +## Debugging `index` Action ```ruby # in rails console or test @@ -34,9 +38,6 @@ can :update, Project, ["priority < ?", 3] do |project| project.priority < 3 end ``` - -See [[issue #213|https://github.com/ryanb/cancan/issues#issue/213]] for a more complex example. - ## Logging AccessDenied Exception If you think the `CanCan::AccessDenied` exception is being raised and you are not sure why, you can log this behavior to help debug what is triggering it. @@ -51,11 +52,5 @@ end ## Issue Tracker -If you are still unable to resolve the issue, please post on the [[issue tracker|https://github.com/ryanb/cancan/issues]] - -## Additional Docs - -* [[Defining Abilities]] -* [[Checking Abilities]] -* [[Ability Precedence]] -* [[Testing Abilities]] \ No newline at end of file +If you are still unable to resolve the issue, [open a question on Stackoverflow](http://stackoverflow.com/questions/ask?tags=cancancan) with tag +[cancancan](http://stackoverflow.com/questions/tagged/cancancan). \ No newline at end of file diff --git a/docs/define_abilities_best_practices.md b/docs/define_abilities_best_practices.md new file mode 100644 index 000000000..a1af3fdc5 --- /dev/null +++ b/docs/define_abilities_best_practices.md @@ -0,0 +1,80 @@ +# Defining Abilities: Best Practices + +## Use hash conditions as much as possible + +### Although scopes are fine for fetching, they pose a problem when authorizing a discrete action. + +For example, this declaration in Ability: + +```ruby +can :read, Article, Article.is_published +``` + +causes this `CanCan::Error`: + +``` +The can? and cannot? call cannot be used with a raw sql 'can' definition. +The checking code cannot be determined for :read #
. +``` + +A better way to define the same is: + +```ruby +can :read, Article, is_published: true +``` + +### Hash conditions are DRYer. + +By using hashes instead of blocks for all actions, you won't have to worry about translating blocks used for member controller actions (`:create`, `:destroy`, `:update`) to equivalent blocks for collection actions (`:index`, `:show`)—which require hashes anyway! + +### Hash conditions are OR'd in SQL, giving you maximum flexibilty. + +Every time you define an ability with `can`, each `can` chains together with OR in the final SQL query for that model. + +So if, in addition to the `is_published` condition above, we want to allow authors to see their drafts: + +```ruby +can :read, Article, author_id: @user.id, is_published: false +``` + +Then the final SQL would be: + +```sql +SELECT `articles`.* +FROM `articles` +WHERE `articles`.`is_published` = 1 +OR ( `articles`.`author_id` = 97 AND `articles`.`is_published` = 0 ) +``` + +### For complex object graphs, hash conditions accommodate `joins` easily. + +See [Hash of Conditions Chapter](./hash_of_conditions.md). + +### Give permissions, don't take them away + +As suggested in this [topic on Reddit](https://www.reddit.com/r/ruby/comments/6ytka8/refactoring_cancancan_abilities_brewing_bits/) you should, when possible, give increasing permissions to your users. + +CanCanCan increases permissions: it starts by giving no permissions to nobody and then increases those permissions depending on the user. + +A properly written `ability.rb` looks like that: + +```ruby +class Ability + include CanCan::Ability + + def initialize(user) + can :read, Post # start by defining rules for all users, also not logged ones + return unless user.present? + can :manage, Post, user_id: user.id # if the user is logged in can manage it's own posts + can :create, Comment # logged in users can also create comments + return unless user.manager? # if the user is a manager we give additional permissions + can :manage, Comment # like managing all comments in the website + return unless user.admin? + can :manage, :all # finally we give all remaining permissions only to the admins + end +end +``` + +following this good practice will help you to keep your permissions clean and more readable. + +The risk of giving wrong permissions to the wrong users is also decreased. diff --git a/docs/define_abilities_with_blocks.md b/docs/define_abilities_with_blocks.md new file mode 100644 index 000000000..634652957 --- /dev/null +++ b/docs/define_abilities_with_blocks.md @@ -0,0 +1,61 @@ +# Define abilities with blocks + +If your conditions are too complex to define in a [hash of conditions](./hash_of_conditions.md), you can use a block to define them in Ruby. + +```ruby +can :update, Project do |project| + project.priority < 3 +end +``` + +Note that if you pass a block to a `can` or `cannot`, the block only executes if an instance of a class is passed to `can?` or `cannot?` calls. + +If you define a `can` or `cannot` with a block and an object is not passed, the check will pass. + +```ruby +can :update, Project do |project| + false +end +``` + +```ruby +can? :update, Project # returns true! +``` + +## Fetching Records + +A block's conditions are only executable through Ruby. If you are [Fetching Records](./fetching_records.md) using `accessible_by` it will raise an exception. + +To fetch records from the database you need to supply an SQL string representing the condition. The SQL will go in the `WHERE` clause: + +```ruby +can :update, Project, ["priority < ?", 3] do |project| + project.priority < 3 +end +``` + +> If you are using `load_resource` and don't supply this SQL argument, the instance variable will not be set for the `index` action since they cannot be translated to a database query. + + +## Block Conditions with ActiveRecord Scopes + +It's also possible to pass a scope instead of an SQL string when using a block in an ability. + +```ruby +can :read, Article, Article.published do |article| + article.published_at <= Time.now +end +``` + +This is really useful if you have complex conditions which require `joins`. A couple of caveats: + +* You cannot use this with multiple `can` definitions that match the same action and model since it is not possible to combine them. An exception will be raised when that is the case. +* If you use this with `cannot`, the scope needs to be the inverse since it's passed directly through. For example, if you don't want someone to read discontinued products the scope will need to fetch non discontinued ones: + +```ruby +cannot :read, Product, Product.where(discontinued: false) do |product| + product.discontinued? +end +``` + +It is only recommended to use scopes if a situation is too complex for a hash condition. diff --git a/docs/define_check_abilities.md b/docs/define_check_abilities.md new file mode 100644 index 000000000..0f5bd4143 --- /dev/null +++ b/docs/define_check_abilities.md @@ -0,0 +1,231 @@ +# Define and Check abilities + +CanCanCan is an authorization library and therefore the first and most interesting thing to learn is how to define and check abilities. During the [installation](./installation.md) you generated an `ability.rb` file but you don't know yet how to use it. + +There are two basic methods in CanCanCan that you will use: + +```ruby +can actions, subjects, conditions +# without the question mark +``` + +is how you define who can **perform** certain `actions` on certain `subjects`. + + +```ruby +can? action, subject +``` + +will be the method that you will use to **check** if the user is authorized to perform a certain `action` on a certain `subject`. + +We don't want to be too abstract here so let's start with a very concrete example. + +> We have a blog with articles and the first thing you want to control is "who can edit an article?" + +```ruby +class Article + belongs_to :user +end +``` + +The answer to this question is that +> "only the author can edit an article." + +We can define the permissions in the `ability.rb`: + +```ruby +class Ability + include CanCan::Ability + + def initialize(user) + can :update, Article, user: user + end +end + +# from here on we will skip the ability.rb file structure +``` + +And we can easily check with the following call: + +```ruby +@article = Article.find(params[:id]) + +can? :update, @article # => true +``` + +But how does CanCanCan know who is the `user`? +When you use the `can?` method in a Rails controller or view, CanCanCan expects that there's a `current_user` method defined. So if you are using something like [devise](https://github.com/heartcombo/devise) for your authentication, you don't need to do anything special. + +By default, CanCanCan assumes no permissions: no one can do any action on any object. + +`can :update, Article, user: user` is stating that the user can update an article, if it is its author. + +Regarding the `Article` there are actually more permissions to check: +* who can read them? +* what can the administrator do? + +A complete example looks like the following: + +```ruby +can :read, Article, published: true + +return unless user.present? + +can :read, Article, user: user +can :update, Article, user: user + +return unless user.admin? + +can :read, Article +can :update, Article +``` + +The code above is stating the following: + +* users that are not logged in, can read published articles +* logged in users can **also** read and update their own articles +* administrators can read and update all the articles. + +> CanCanCan works, at its best, when defining increasing permissions. + +The code above can be simplified like this: + +```ruby +can :read, Article, published: true + +return unless user.present? + +can [:read, :update], Article, user: user + +return unless user.admin? + +can [:read, :update], Article +``` + +Now that we know the basics of defining and checking abilities, let's check what are the possible actions. + +## Can Actions + +CanCanCan offers four aliases: `:read`, `:create`, `:update`, `:destroy` for the actions. These aren't the same as the seven Restful actions in Rails. CanCanCan automatically adds some convenient aliases for mapping the controller actions. + +```ruby +read: [:index, :show] +create: [:new, :create] +update: [:edit, :update] +destroy: [:destroy] +``` + +this means that when you define `can :read, Article`, you can also check: + +```ruby +can? :show, @article +``` + +when you define `can :update, Article`, you can also check: + +```ruby +can? :edit, @article +``` + +This will be very convenient when we will authorize the Rails Controller actions. + +For now, what you need to know, is that these four will be your most used, basic actions. + +One last action is `manage`. This action means that you have full permissions on the subject and you can perform any possible action. Knowing that, we can now rewrite our ability.rb example: + +``` +can :read, Article, published: true + +return unless user.present? + +can [:read, :update], Article, user: user + +return unless user.admin? + +can :manage, Article +``` + +and say that the administrators are able to perform any action on the articles. + +```ruby +can? :edit, @article # => true +can? :destroy, @article # => true +``` + +Now that we learned about actions and their aliases let's see what we can do with the subjects + +# Can subjects + +The subject of an action is usually a Ruby class. Most of the times you want to define your permissions on specific classes, but this is not your only option. + +You can actually use any subject, and one of the most common cases is to just use a symbol. +An admin dashboard could be protected by definining: + +``` +can :read, :admin_dashboard +``` + +and checked with `can? :read, :admin_dashboard`. + +One special symbol is `:all`. All will allow an action on all possible subjects. + +In our example, it would not be uncommon to see the following: + +```ruby +can :read, Article, published: true + +return unless user.present? + +can [:read, :update], Article, user: user + +return unless user.admin? + +can :manage, :all +``` + +and give all possible permissions to the administrator. + +Note that the code above allows the administrator to also `:read, :admin_dashboard`. :manage means literally **any** action, not only CRUD ones. + +> You **must and should** always check for specific permisssions, but you don't need to define all of them if not needed. + +If at some point you have a new page reserved to the administrators, where they can traslate articles, you should check for `can? :translate, @article`, but you don't need to define the ability, since the administrators can already do any action. It will be easy in the future to give the possibility for authors to translate their own articles by changing your permissions file: + +```ruby +can :read, Article, published: true + +return unless user.present? + +can [:read, :update, :translate], Article, user: user + +return unless user.admin? + +can :manage, :all +``` + +# Checking other users abilities + +What if you want to determine the abilities of a `User` record that is not the `current_user`? Maybe we want to see if another user can update an article. + +```ruby +Ability.new(some_user).can? :update, @article +``` + +You can also add an `ability` method in the `User` model and delegate the `can?` method: + +```ruby +# app/models/user.rb +class User + delegate :can?, :cannot?, to: :ability + + def ability + @ability ||= Ability.new(self) + end +end + +some_user.can? :update, @article +``` + +That's everything you know about defining and checking abilities. The DSL is very easy but yet very powerful. There's still a lot you need/should learn about defining abilities. You can [dig deeper](./hash_of_conditions.md) now, but we would suggest to stop, digest it, and proceed on a more Rails-specific topic: [Controller helpers](./controller_helpers.md) where you will learn how to secure your Rails application. + +Or you could already take a look at the session about [testing](./testing.md). \ No newline at end of file diff --git a/docs/fetching_records.md b/docs/fetching_records.md new file mode 100644 index 000000000..886b35bda --- /dev/null +++ b/docs/fetching_records.md @@ -0,0 +1,78 @@ +# Fetching records + +One of the key features of CanCanCan, compared to other authorization libraries, is the possibility to retrieve all the objects that the user is authorized to access. The following: + +```ruby +Article.accessible_by(current_ability) +``` + +will use the rules you already defined to ensure that the users retrieve only a list of articles that they can read. + +This tool is very powerful and magic at the same time. + +Given the following ability file: + +```ruby +can :read, Article, published: true + +return unless user.present? + +can :read, Article, user: user + +return unless user.admin? + +can :manage, :all +``` + +you will not only be able to check if the user `can? :read, @article` on a single article, but also to limit the articles fetched from the database, to only the ones that they can read. + +In an `index` action the following will just work: + +```ruby +@articles = Article.accessible_by(current_ability) +``` + +`current_ability` is already made available by CanCanCan in your controller and the default action of `accessible_by` is `:index`, which is aliased by `:read`. + +You can change the action by passing it as the second argument. Here we find only the records the user has permission to update. + +```ruby +@articles = Article.accessible_by(current_ability, :update) +``` + +And this is just an ActiveRecord scope so other scopes and pagination can be chained onto it. + +## Under the hood + +The call to accessible_by in the example above will generate the proper SQL to limit the records fetched. + + +This works also with multiple `can` definitions, which allows you to define complex permission logic and have it translated properly to SQL. + +Given the definition: +```ruby +class Ability + can :read, Article, public: true + cannot :read, User, self_managed: true + can :read, Article, user: user +end +``` +a call to Article.accessible_by(current_ability) generates the following SQL + +```sql +SELECT * +FROM articles +WHERE (user_id = 1) OR (not (self_managed = 'true') AND (public = 'true')) +``` + +The generation of the SQL query is a very complex task and probably the most powerful feature of CanCanCan. + +Even if the default behaviour will suffice at the beginning, larger databases or more complex rules, might lead to very complex SQL queries. This might result in a slow fetching of records. This is why is possible to use different strategies to generate the SQL. +You will see that in one of the last chapters: [SQL strategies](./sql_strategies.ms) + +# Blocks + +We haven't spoken about block abilities yet, but the SQL generation will not be possible if you have even a single rule that is defined using just a block. +You can define SQL fragments in addition to block to fix that. But we'll see that in the [Define Abilities with Blocks](./define_abilities_with_blocks.md) chapter. +``` + diff --git a/docs/FriendlyId-support.md b/docs/friendly_id.md similarity index 74% rename from docs/FriendlyId-support.md rename to docs/friendly_id.md index 8ea6cdbe5..f013d5725 100644 --- a/docs/FriendlyId-support.md +++ b/docs/friendly_id.md @@ -1,10 +1,13 @@ -If you are using FriendlyId you will probably like something to make cancan compatible with it. +# FriendlyId + +If you are using [FriendlyId](https://github.com/norman/friendly_id) you will probably like something to make CanCanCan compatible with it. You do not have to write `find_by :slug` or something like that, that is always error prone. -You just need to create a `config/initizializers/cancan.rb` file with: +You just need to create a `config/initizializers/cancancan.rb` file with: + ```ruby -if defined?(CanCan) +if defined?(CanCanCan) class Object def metaclass class << self; self; end diff --git a/docs/Exception-Handling.md b/docs/handling_access_denied.md similarity index 75% rename from docs/Exception-Handling.md rename to docs/handling_access_denied.md index 62407ff43..b232b3a3f 100644 --- a/docs/Exception-Handling.md +++ b/docs/handling_access_denied.md @@ -1,4 +1,10 @@ -The `CanCan::AccessDenied` exception is raised when calling `authorize!` in the controller and the user is not able to perform the given action. A message can optionally be provided. +# Handling CanCan::AccessDenied + +In the [Controller helpers] chapter was saw that when a resource is not authorized, a `CanCan::AccessDenied` exception is raised, and we offered a basic handling through `config/application.rb`. Let's now see what else we can do. + +The `CanCan::AccessDenied` exception is raised when calling `authorize!` in the controller and the user is not able to perform the given action. + +A message can optionally be provided. ```ruby authorize! :read, Article, :message => "Unable to read this article." @@ -21,9 +27,11 @@ en: user: "Not allowed to manage other user accounts." update: project: "Not allowed to update this project." + action_name: + model_name: "..." ``` -Notice `manage` and `all` can be used to generalize the subject and actions. Also `%{action}` and `%{subject}` can be used as variables in the message. +Notice `manage` and `all` can be used to generalize the subject and actions. Also `%{action}` and `%{subject}` can be used as interpolated variables in the message. You can catch the exception and modify its behavior in the `ApplicationController`. The behavior may vary depending on the request format. For example here we set the error message to a flash and redirect to the home page for HTML requests and return `403 Forbidden` for JSON requests. @@ -32,7 +40,7 @@ class ApplicationController < ActionController::Base rescue_from CanCan::AccessDenied do |exception| respond_to do |format| format.json { head :forbidden } - format.html { redirect_to main_app.root_url, :alert => exception.message } + format.html { redirect_to root_path, alert: exception.message } end end end @@ -52,23 +60,6 @@ exception.default_message = "Default error message" exception.message # => "Default error message" ``` -If you prefer to return the 403 Forbidden HTTP code, create a `public/403.html` file and write a rescue_from statement like this example in `ApplicationController`: - -```ruby -class ApplicationController < ActionController::Base - rescue_from CanCan::AccessDenied do |exception| - render :file => "#{Rails.root}/public/403.html", :status => 403, :layout => false - ## to avoid deprecation warnings with Rails 3.2.x (and incidentally using Ruby 1.9.3 hash syntax) - ## this render call should be: - # render file: "#{Rails.root}/public/403", formats: [:html], status: 403, layout: false - end -end -``` - -`403.html` must be pure HTML, CSS, and JavaScript--not a template. The fields of the exception are not available to it. - -If you are getting unexpected behavior when rescuing from the exception it is best to add some logging . See [[Debugging Abilities]] for details. - ## Rescuing exceptions for XML responses If your web application provides a web service which returns XML or JSON responses then you will likely want to handle Authorization properly with a 403 response. You can do so by rendering a response when rescuing from the exception. diff --git a/docs/hash_of_conditions.md b/docs/hash_of_conditions.md new file mode 100644 index 000000000..0c6bcf6c4 --- /dev/null +++ b/docs/hash_of_conditions.md @@ -0,0 +1,78 @@ +# Defining abilities - Hash of conditions + +Let's start our journey into the abilities definition by explaining the CanCanCan Hash of conditions mechanism. + +In the chapter [Define and Check Abilities](./define_and_check_abilities.md) we defined + +```ruby +can :update, @article, user: user +``` + +to say that an Article can be updated only by it's author. But how does it work? + +The third argument of the `can` method (`{ user: user }`) is the hash of conditions for this rule. + +A hash of conditions can be passed to further restrict which records this permission applies to. + +In the example below the user will only have permission to read active projects which they own. + +```ruby +can :read, Project, active: true, user_id: user.id +``` + +When defining a condition, the key should always be either a database column of the model, or the association name. In the example above, if the Project has defined + +```ruby +belongs_to :owner, class_name: 'User', foreign_key: :user_id +``` +the rule can also be written as: + +```ruby +can :read, Project, active: true, owner: user +``` + +so by using the association `owner` instead of the database column `user_id`. + + +You can nest conditionsassociations. Here the project can only be read if the category it belongs to is visible. + +```ruby +can :read, Project, category: { visible: true } +``` + +An array or range can be passed to match multiple values. Here the user can only read projects of priority 1 through 3. + +```ruby +can :read, Project, priority: 1..3 +``` + +Almost anything that you can pass to a hash of conditions in ActiveRecord will work here as well. + +## Traverse associations + +All associations can be traversed when defining a rule. + +```ruby +class User + belongs_to :account +end + +class Account + has_one :user + has_many :services +end + +class Service + belongs_to :account + has_many :parts +end + +class Part + belongs_to :service +end + +# Ability +can :manage, Part, service: { account: { user: user } } +``` + +Let's now quickly see how to [Combine Abilities](./combine_abilities.md) diff --git a/docs/Inherited-Resources.md b/docs/inherited_resources.md similarity index 82% rename from docs/Inherited-Resources.md rename to docs/inherited_resources.md index 6ac2bd6d8..fa11e6296 100644 --- a/docs/Inherited-Resources.md +++ b/docs/inherited_resources.md @@ -1,7 +1,9 @@ +# Inherited Resources + **This guide is for cancancan < 2.0 only. If you want to use Inherited Resources and cancancan 2.0 please check for extensions like https://github.com/TylerRick/cancan-inherited_resources** -The `load_and_authorize_resource` call will automatically detect if you are using [[Inherited Resources|http://github.com/josevalim/inherited_resources]] and load the resource through that. The `load` part in CanCan is still necessary since Inherited Resources does lazy loading. This will also ensure the behavior is identical to normal loading. +The `load_and_authorize_resource` call will automatically detect if you are using [Inherited Resources](http://github.com/josevalim/inherited_resources) and load the resource through that. The `load` part in CanCan is still necessary since Inherited Resources does lazy loading. This will also ensure the behavior is identical to normal loading. ```ruby class ProjectsController < InheritedResources::Base diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 000000000..da0f1d464 --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,35 @@ +# Installation + +Add this to your Gemfile: + +```ruby +gem 'cancancan' +``` + +and run the `bundle install` command. + +Use the provided command to generate a template for your abilities file: + +```bash +rails generate cancan:ability +``` + +This will generate the following file: + +```ruby +# /app/models/ability.rb + +class Ability + include CanCan::Ability + + def initialize(user) + end +end +``` + +This is everything you need to start. :boom: + +All the permissions will be defined in this file. +You can of course split it into multiple files if your application grows, but we'll cover that in a [later chapter](./split_ability.md). + +Let's now start with the basic concepts: [define and check abilities](./define_check_abilities.md). \ No newline at end of file diff --git a/docs/Translations-(i18n).md b/docs/internationalization.md similarity index 97% rename from docs/Translations-(i18n).md rename to docs/internationalization.md index 8fc75b968..41af3d7a8 100644 --- a/docs/Translations-(i18n).md +++ b/docs/internationalization.md @@ -1,4 +1,7 @@ +# Internationalization + To use translations in your app define some yaml like this: + ```yaml # en.yml en: @@ -6,6 +9,7 @@ en: manage: all: "You have no access to this resource" ``` + ## Translation for individual abilities If you want to customize messages for some model or even for some ability define translation like this: diff --git a/docs/introduction.md b/docs/introduction.md new file mode 100644 index 000000000..1a0e6eab2 --- /dev/null +++ b/docs/introduction.md @@ -0,0 +1,33 @@ +# Introduction + +Hi everyone :wave:,
+I am [Alessandro](https://github.com/coorasse), the maintainer of CanCanCan since 2015. I want to present you this new guide for developers. + +Since I took over the CanCanCan project in 2015, I felt one of the most urgent things to do was rewriting the documentation: +the previous documentation was in a Wiki, but giving free access to everyone to edit it, ended up in a big mess after so many years. +**Information was all there, but in a very unstructured way.** + +One of the first things I did was to block the Wiki from free editing and after that, I moved all the wiki within the git repository, so that every update had to go through a Pull Request and code review. + +**I think documentation is as important as the source code, and therefore should follow the same approval process of the code.** + + +After one year from this decision, I realised that this was not sufficient: I still didn't like the status of the documentation, +and I had to explain too often to my colleagues at [Renuo](https://renuo.ch) the basic mechanisms of this library. +And all my colleagues are all very smart people!! +So I understood that we were still missing a well structured developer documentation. + +I hope you'll appreciate this work and you'll now be able to use CanCanCan at it's maximum potential. + +I suggest also experienced CanCanCan developers to read it, since you might find out interesting features that have been recently introduced and you might not be aware of, since they were not previously documented. + +Thanks again to all my sponsors, who allowed me to take the time to properly write this documentation, and thanks to all the contributors for your help with this wonderful library :heart:. + +If you'd like to sponsor this library, head to https://github.com/sponsors/coorasse. It means a lot. + +Thank you, + +Alessandro Rodi + + +Head to the [Installation](./installation.md) chapter. \ No newline at end of file diff --git a/docs/Migrating-from-CanCanCan-2.x-to-3.0.md b/docs/migrating.md similarity index 96% rename from docs/Migrating-from-CanCanCan-2.x-to-3.0.md rename to docs/migrating.md index f7886ea7b..c6532a078 100644 --- a/docs/Migrating-from-CanCanCan-2.x-to-3.0.md +++ b/docs/migrating.md @@ -1,3 +1,7 @@ +# Migration Guide + +## From 2.x to 3.x + ### Breaking changes * **Defining abilities without a subject is not allowed anymore.** diff --git a/docs/Model-Adapter.md b/docs/model_adapter.md similarity index 100% rename from docs/Model-Adapter.md rename to docs/model_adapter.md diff --git a/docs/Nested-Resources.md b/docs/nested_resources.md similarity index 97% rename from docs/Nested-Resources.md rename to docs/nested_resources.md index 18d37d1ce..96b910143 100644 --- a/docs/Nested-Resources.md +++ b/docs/nested_resources.md @@ -1,3 +1,5 @@ +# Nested Resources + Let's say we have nested resources set up in our routes. ```ruby @@ -190,11 +192,11 @@ class UsersController < ApplicationController in ability.rb ```ruby -can :create, User, groups_users: {group: {CONDITION_ON_GROUP} } +can :create, User, groups_users: { group: { CONDITION_ON_GROUP } } ``` Don't forget the **inverse_of** option, it is the trick to make it work correctly. -Remember to define the ability through the **groups_users** model (i.e. don't write `can :create, User, groups: {CONDITION_ON_GROUP}`) +Remember to define the ability through the **groups_users** model (i.e. don't write `can :create, User, groups: { CONDITION_ON_GROUP }`) -You will be able to persist the association just calling `@user.save` instead of `@group.save` +You will be able to persist the association just calling `@user.save` instead of `@group.save`. diff --git a/docs/Role-Based-Authorization.md b/docs/role_based_authorization.md similarity index 99% rename from docs/Role-Based-Authorization.md rename to docs/role_based_authorization.md index 86d6deda1..49fc55f6e 100644 --- a/docs/Role-Based-Authorization.md +++ b/docs/role_based_authorization.md @@ -1,3 +1,5 @@ +# Role-based Authorization + CanCanCan is decoupled from how you implement roles in the User model, but how might one set up basic role-based authorization? The pros and cons are described [here](https://github.com/kristianmandrup/cantango/wiki/CanCan-vs-CanTango). The following approach allows you to simply define the role abilities in Ruby and does not need a role model. Alternatively, [[Separate Role Model]] describes how to define the roles and mappings in a database. diff --git a/docs/Rules-compression.md b/docs/rules_compression.md similarity index 100% rename from docs/Rules-compression.md rename to docs/rules_compression.md diff --git a/docs/split_ability.md b/docs/split_ability.md new file mode 100644 index 000000000..9c4016929 --- /dev/null +++ b/docs/split_ability.md @@ -0,0 +1,78 @@ +# Split the ability file + +When the application becomes more complex and many abilities are defined, you might want to start splitting your ability file into multiple files. + +We will show here an example on how to split your ability file on a “per-model” basis. + +Imagine the following scenario: + +```ruby +# app/models/ability.rb +class Ability + include CanCan::Ability + def initialize(user) + can :edit, User, id: user.id + can :read, Book, published: true + can :edit, Book, user_id: user.id + can :manage, Comment, user_id: user.id + end +end +``` + +This is, of course, not too complicated, and in an real world application we would not split this file, but for didactic reasons we want to split this file “per-model”. + +We suggest to have an app/abilities folder and create a separate file for each model (exactly as you would do with Pundit). + +```ruby +# app/abilities/user_ability.rb +class UserAbility + include CanCan::Ability + def initialize(user) + can :edit, User, id: user.id + end +end + +# app/abilities/comment_ability.rb +class CommentAbility + include CanCan::Ability + def initialize(user) + can :manage, Comment, user_id: user.id + end +end + +# app/abilities/book_ability.rb +class BookAbility + include CanCan::Ability + def initialize(user) + can :read, Book, published: true + can :edit, Book, user_id: user.id + end +end +``` + +Now you can override the `current_ability` method in you controller. For example: + +```ruby +# app/controllers/books_controller.rb +class BooksController + def current_ability + @current_ability ||= BookAbility.new(current_user) + end +end +``` + +Using this technique you have all the power of CanCanCan ability files, that allows you define your permissions with hash of conditions. This means you can check permissions on a single instance of a model, but also retrieve automatically all the instances where you are authorized to perform a certain action. + +You can call `can? :read, @book` but also `Book.accessible_by(current_ability, :read)` that will return all the books you can read. + +When your controller is executed, it will read only the ability file that you need, saving time and memory. + +## Merge ability files + +Abilities files can always be merged together, so if you need two of them in one Controller, you can simply: + +```ruby +def current_ability + @current_ability ||= ReadAbility.new(current_user).merge(WriteAbility.new(current_user)) +end +``` \ No newline at end of file diff --git a/docs/sql_strategies.md b/docs/sql_strategies.md new file mode 100644 index 000000000..736b06705 --- /dev/null +++ b/docs/sql_strategies.md @@ -0,0 +1,67 @@ +# SQL Strategies + +When [fetching records](./fetching_records.md) from the database, CanCanCan generates the SQL for you. + +The generated SQL, although correct, might not be performant. + +In the history of CanCanCan we had many issues with different versions of the generated SQL and we finally reached to the conclusion that there's no single solutions that fits all the needs. + +That's why in the latest versions of CanCanCan, you are given the possibility to customize how the SQL is generated and choose from multiple options. + +You can customize the SQL startegy globally with: + +```ruby +# config/initializers/cancancan.rb + +CanCan.accessible_by_strategy = :subquery # :left_join is the default +``` + +or on a single `accessible_by` call: + +```ruby +Article.accessible_by(current_ability, strategy: :subquery) # :left_join is, again, the default +``` + +or on a group of queries: + +```ruby +CanCan.with_accessible_by_strategy(:subquery) do + Article.accessible_by(current_ability) + # ... +end +``` + +Here is a complete list of the available strategies, explained by examples. + +Given the following permissions: + +```ruby +can :read, Article, mentions: { user: { name: u.name } } +``` + +## :left_join + +Note that in the default strategy, we use the `DISTINCT` clasue which might cause performance issues. + +```sql +SELECT DISTINCT "articles".* +FROM "articles" +LEFT OUTER JOIN "mentions" ON "mentions"."article_id" = "articles"."id" +LEFT OUTER JOIN "users" ON "users"."id" = "mentions"."user_id" +WHERE "users"."name" = 'pippo' +``` + +## :subquery + +By using the `:subquery` strategy, the `DISTINCT` clause can be removed. + +```sql +SELECT "articles".* +FROM "articles" +WHERE "articles"."id" IN + (SELECT "articles"."id" + FROM "articles" + LEFT OUTER JOIN "mentions" ON "mentions"."article_id" = "articles"."id" + LEFT OUTER JOIN "users" ON "users"."id" = "legacy_mentions"."user_id" + WHERE "users"."name" = 'pippo') +``` diff --git a/docs/Testing-Abilities.md b/docs/testing.md similarity index 54% rename from docs/Testing-Abilities.md rename to docs/testing.md index 76dc431e0..32f5b1a85 100644 --- a/docs/Testing-Abilities.md +++ b/docs/testing.md @@ -1,99 +1,84 @@ -It can be difficult to thoroughly test user permissions at the functional/integration level because there are often many branching possibilities. Since CanCanCan handles all permission logic in `Ability` classes this makes it easy to have a solid set of unit test for complete coverage. - -The `can?` method can be called directly on any `Ability` (like you would in the controller or view) so it is easy to test permission logic. - -```ruby -test "user can only destroy projects which they own" do - user = User.create! - ability = Ability.new(user) - assert ability.can?(:destroy, Project.new(user: user)) - assert ability.cannot?(:destroy, Project.new) -end -``` - - -## RSpec - -If you are testing the `Ability` class through RSpec there is a `be_able_to` matcher available. This checks if the `can?` method returns `true`. - -```ruby -require "cancan/matchers" -# ... -ability.should be_able_to(:destroy, Project.new(user: user)) -ability.should_not be_able_to(:destroy, Project.new) -``` - -Pro way ;) - -```ruby -require "cancan/matchers" -# ... -describe "User" do - describe "abilities" do - subject(:ability) { Ability.new(user) } - let(:user){ nil } - - context "when is an account manager" do - let(:user){ Factory(:accounts_manager) } - - it { is_expected.to be_able_to(:manage, Account.new) } - end - end -end -``` - -## Cucumber - -By default, Cucumber will ignore the `rescue_from` call in the `ApplicationController` and report the `CanCan::AccessDenied` exception when running the features. If you want full integration testing you can change this behavior so the exception is caught by Rails. You can do so by setting this in the `env.rb` file. - -```ruby -# in features/support/env.rb -ActionController::Base.allow_rescue = true -``` - -Alternatively, if you don't want to allow rescue on everything, you can tag individual scenarios with `@allow-rescue` tag. - -```ruby -@allow-rescue -Scenario: Update Article -``` - -Here the `rescue_from` block will take effect only in this scenario. - - -## Controller Testing - -If you want to test authorization functionality at the controller level one option is to log-in the user who has the appropriate permissions. - -```ruby -user = User.create!(admin: true) -session[:user_id] = user.id # log in user however you like, alternatively stub `current_user` method -get :index -assert_template :index # render the template since they should have access -``` - -Alternatively, if you want to test the controller behaviour independently from what is inside the `Ability` class, it is easy to stub out the ability with any behaviour you want. - -```ruby -def setup - @ability = Object.new - @ability.extend(CanCan::Ability) - @controller.stubs(:current_ability).returns(@ability) -end - -test "render index if have read ability on project" do - @ability.can :read, Project - get :index - assert_template :index -end -``` - -If you have very complex permissions it can lead to many branching possibilities. If these are all tested in the controller layer then it can lead to slow and bloated tests. -Instead I recommend keeping controller authorization tests light and testing the authorization functionality more thoroughly in the Ability model through unit tests as shown at the top. - -## Additional Docs - -* [[Defining Abilities]] -* [[Checking Abilities]] -* [[Debugging Abilities]] -* [[Ability Precedence]] \ No newline at end of file +# Testing + +This is an authorization library. Testing the permissions you defined is not important, **is essential**. + +Be really careful when defining your abilities, and be even more careful when testing them. + +It can be difficult to thoroughly test user permissions at the functional/integration level because there are often many branching possibilities. Since CanCanCan handles all permission logic in `Ability` classes this makes it easy to have a solid set of unit test for complete coverage. + +The `can?` method can be called directly on any `Ability` (like you would in the controller or view) so it is easy to test permission logic. + +```ruby +test "user can only destroy projects which they own" do + user = User.create! + project = Project.new(user: user) + ability = Ability.new(user) + assert ability.can?(:destroy, project) + assert ability.cannot?(:destroy, Project.new) +end +``` + +## RSpec + +If you are testing the `Ability` class through RSpec there is a `be_able_to` matcher available. This checks if the `can?` method returns `true`. + +```ruby +require "cancan/matchers" +ability = Ability.new(user) +expect(ability).to be_able_to(:destroy, Project.new(user: user)) +expect(ability).not_to be_able_to(:destroy, Project.new) +``` + +Pro way 😉 + +```ruby +require "cancan/matchers" + +describe "User" do + describe "abilities" do + subject(:ability) { Ability.new(user) } + let(:user){ nil } + + context "when is an account manager" do + let(:user){ create(:account_manager) } + + it { is_expected.to be_able_to(:manage, Account.new) } + end + end +end +``` + +## Cucumber + +By default, Cucumber will ignore the `rescue_from` call in the `ApplicationController` and report the `CanCan::AccessDenied` exception when running the features. If you want full integration testing you can change this behavior so the exception is caught by Rails. You can do so by setting this in the `env.rb` file. + +```ruby +# in features/support/env.rb +ActionController::Base.allow_rescue = true +``` + +Alternatively, if you don't want to allow rescue on everything, you can tag individual scenarios with `@allow-rescue` tag. + +```ruby +@allow-rescue +Scenario: Update Article +``` + +Here the `rescue_from` block will take effect only in this scenario. + + +## Request Testing + +If you want to test authorization functionality at the request level one option is to log-in the user who has the appropriate permissions. + +```ruby +user = User.create!(admin: true) +article = Article.create! +login user, as: :user # in devise +get article_path(article) +expect(response).to have_http_status(:ok) +``` + +If you have very complex permissions it can lead to many branching possibilities. If these are all tested in the request layer then it can lead to slow and bloated tests. + +Instead we recommend keeping request authorization tests light and testing the authorization functionality more thoroughly in the Ability model through unit tests as shown at the top. diff --git a/lib/generators/cancan/ability/templates/ability.rb b/lib/generators/cancan/ability/templates/ability.rb index ef26a8c38..6afd442d9 100644 --- a/lib/generators/cancan/ability/templates/ability.rb +++ b/lib/generators/cancan/ability/templates/ability.rb @@ -4,14 +4,12 @@ class Ability include CanCan::Ability def initialize(user) - # Define abilities for the passed in user here. For example: + # Define abilities for the user here. For example: # - # user ||= User.new # guest user (not logged in) - # if user.admin? - # can :manage, :all - # else - # can :read, :all - # end + # return unless user.present? + # can :read, :all + # return unless user.admin? + # can :manage, :all # # The first argument to `can` is the action you are giving the user # permission to do. @@ -26,7 +24,7 @@ def initialize(user) # objects. # For example, here the user can only update published articles. # - # can :update, Article, :published => true + # can :update, Article, published: true # # See the wiki for details: # https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities diff --git a/logo/new_relic.png b/logo/new_relic.png new file mode 100644 index 0000000000000000000000000000000000000000..6b9f27c11bd27a9581aee586066b5bbd40ac946d GIT binary patch literal 60120 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 Date: Tue, 3 Aug 2021 13:26:58 +0200 Subject: [PATCH 02/53] New sponsor logo (#723) --- README.md | 10 ++++++++-- logo/in_cloud_counsel.png | Bin 0 -> 21364 bytes 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 logo/in_cloud_counsel.png diff --git a/README.md b/README.md index 526740022..a11fd5937 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ of models automatically and reduce duplicated code.

- Modern Treasury + Modern Treasury

@@ -51,7 +51,13 @@ of models automatically and reduce duplicated code.

- NewRelic + NewRelic + +
+
+
+ + InCloudCounsel

diff --git a/logo/in_cloud_counsel.png b/logo/in_cloud_counsel.png new file mode 100644 index 0000000000000000000000000000000000000000..33ad83b41c5a83f46ecf740425b33a363db8bcf0 GIT binary patch literal 21364 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 QCjbBd07*qoM6N<$f>XN8IRF3v literal 0 HcmV?d00001 From a2c7acb8e765371dd64ea6b17692c1be5b77c67b Mon Sep 17 00:00:00 2001 From: Benoit Daloze Date: Tue, 3 Aug 2021 15:29:52 +0200 Subject: [PATCH 03/53] Avoid using break in RSpec match which uses define_method (#724) --- lib/cancan/matchers.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/cancan/matchers.rb b/lib/cancan/matchers.rb index 7e5b206b7..ccc8e0b56 100644 --- a/lib/cancan/matchers.rb +++ b/lib/cancan/matchers.rb @@ -13,9 +13,11 @@ match do |ability| actions = args.first if actions.is_a? Array - break false if actions.empty? - - actions.all? { |action| ability.can?(action, *args[1..-1]) } + if actions.empty? + false + else + actions.all? { |action| ability.can?(action, *args[1..-1]) } + end else ability.can?(*args) end From 70aba636d95f6f275172a57373558b810198b51e Mon Sep 17 00:00:00 2001 From: Simon Isler Date: Fri, 6 Aug 2021 14:41:24 +0200 Subject: [PATCH 04/53] Correct documentation and fix broken links (#725) --- README.md | 2 +- docs/README.md | 4 ++-- docs/handling_access_denied.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a11fd5937..f45b9b2d3 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ is the possibility to retrieve all the objects that the user is authorized to ac The following: ```ruby - @posts Post.accessible_by(current_ability) + @posts = Post.accessible_by(current_ability) ``` will use your rules to ensure that the user retrieves only a list of posts that can be read. diff --git a/docs/README.md b/docs/README.md index 2bedc190d..43583145c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -24,7 +24,7 @@ You can skip the [Introduction](./introduction.md) where there's just some histo 1. [Accessing request data](./accessing_request_data.md) 1. [SQL strategies](./sql_strategies.md) 1. [Accessible attributes](./accessible_attributes.md) -1. [Testing](/.testing.md) +1. [Testing](./testing.md) 1. [Internationalization](./internationalization.md) ## Further topics @@ -32,7 +32,7 @@ You can skip the [Introduction](./introduction.md) where there's just some histo In these topics, you will learn some best practices, but also how to solve specific integration issues with other libraries or how to extend CanCanCan. 1. [Migrating](./migrating.md) -1. [Debugging Abilities](/.debugging.md) +1. [Debugging Abilities](./debugging.md) 1. [Split your ability file](./split_ability.md) 1. [Define Abilities - best practices](./define_abilities_best_practices.md) 1. [Abilities in database](./abilities_in_database.md) diff --git a/docs/handling_access_denied.md b/docs/handling_access_denied.md index b232b3a3f..00cc9b033 100644 --- a/docs/handling_access_denied.md +++ b/docs/handling_access_denied.md @@ -1,6 +1,6 @@ # Handling CanCan::AccessDenied -In the [Controller helpers] chapter was saw that when a resource is not authorized, a `CanCan::AccessDenied` exception is raised, and we offered a basic handling through `config/application.rb`. Let's now see what else we can do. +In the [Controller helpers](./controller_helpers.md) chapter was saw that when a resource is not authorized, a `CanCan::AccessDenied` exception is raised, and we offered a basic handling through `config/application.rb`. Let's now see what else we can do. The `CanCan::AccessDenied` exception is raised when calling `authorize!` in the controller and the user is not able to perform the given action. From 723128a2964c462998a383c05a5d2dc7e69d4546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nick=20Fl=C3=BCckiger?= Date: Fri, 6 Aug 2021 14:41:46 +0200 Subject: [PATCH 05/53] Update adapter creation guide (#701) * Update adapter creation guide * Change links --- docs/model_adapter.md | 177 +++++++++++++++++++++++++++++++----------- 1 file changed, 133 insertions(+), 44 deletions(-) diff --git a/docs/model_adapter.md b/docs/model_adapter.md index 8b529e055..45d4ade15 100644 --- a/docs/model_adapter.md +++ b/docs/model_adapter.md @@ -1,79 +1,140 @@ # Model Adapter -CanCan includes a model adapter layer which allows it to change behavior depending on the model used. The current adapters are. +CanCanCan includes a model adapter system that allows developers to add their own adapters for +handling behaviour depending on the model used. + +CanCanCan provides maintained adapters for the following model types: * ActiveRecord (native in `cancancan` gem) + * ActiveRecord 4 + * ActiveRecord 5 * [Mongoid](https://github.com/CanCanCommunity/cancancan-mongoid) ## Creating a Model Adapter -It is easy to make your own adapter if one is not provided. Here I'll walk you through the steps to recreate the Mongoid adapter. +Due to it's flexible and extendable system of adapters, it is easy to implement a custom adapter +if the currently provided adapters do not suffice. -### The Specs +To facilitate an easy implementation of a new adapter CanCanCan provides you with an +[Abstract Adapter](https://github.com/CanCanCommunity/cancancan/blob/develop/lib/cancan/model_adapters/abstract_adapter.rb) +you can extend and build upon. This design allows for dynamic adapter handling and a decoupled +handling of information. + +## The Abstract Adapter + +The abstract adapter has multiple methods that one has to overwrite in order to +match the behaviour that is expected. It is used by the system to delegate the handling of fetching entries +base on defined rules and conditions. + +#### for_class + +The ```for_class?``` method is a static method on the abstract adapter that has to be overwritten +in your adapter. + +This method is used to determine whether a model should be passed to the adapter or not. + +If your ```for_class?``` implementation returns true, the adapter will be provided +with the model to build and match the rules defined. + +Otherwise the adapter will be skipped and the other subclasses of the abstract adapter will be checked. + +#### database_records + +Used to implement the loading of entries from the database, by a developer defined handling of the +given rules for a model. + +### Dependencies +Because cancancan wants to provide an easy method of writing and testing your own adapters +it uses appraisals to test the code against different versions of dependencies. -First, fork the CanCanCan GitHub project and clone that repo. Next, add the necessary gems to the Gemfile for working with the adapter in the specs. +[Appraisals](https://github.com/thoughtbot/appraisal) +Thus you can add your own entry for your gems and dependencies. + +An example could look like: + +cancancan/Appraisals ```ruby -case ENV["MODEL_ADAPTER"] -# ... -when "mongoid" - gem "bson_ext", "~> 1.1" - gem "mongoid", "~> 2.0.0.beta.20" -# ... + +appraise 'cancancan_custom_adapter' do + gem 'activerecord', '~> 5.0.2', require: 'active_record' + + gemfile.platforms :jruby do + gem 'jdbc-postgres' + end + + gemfile.platforms :ruby, :mswin, :mingw do + gem 'pg', '~> 0.21' + end end ``` -Next create a spec for the adapter which tests basic behavior. For example, here's a simple Mongoid spec that would go under `spec/cancan/model_adapters/mongoid_adapter_spec.rb` +You would have to replace the dependencies with ones that fit your custom adapter. + +After creating your dependency definition run + + bundle exec appraisal install + +to install dependencies for your adapter. + +### The Specs + +To illustrate what a test for an adapter could look like, we will use [Mongoid](https://github.com/CanCanCommunity/cancancan-mongoid) as an example. + +In good TDD fashion we create a spec / test for the new adapter to later confirm our implementation. ```ruby -if ENV["MODEL_ADAPTER"] == "mongoid" - require "spec_helper" - class MongoidProject - include Mongoid::Document - end +RSpec.describe CanCan::ModelAdapters::MongoidAdapter do - Mongoid.configure do |config| - config.master = Mongo::Connection.new('127.0.0.1', 27017).db("cancan_mongoid_spec") + it 'is for only Mongoid classes' do + expect(CanCan::ModelAdapters::MongoidAdapter).not_to be_for_class(Object) + expect(CanCan::ModelAdapters::MongoidAdapter).to be_for_class(MongoidProject) end - describe CanCan::ModelAdapters::MongoidAdapter do - context "Mongoid defined" do - before(:each) do - @ability = Object.new - @ability.extend(CanCan::Ability) - end + it 'finds record' do + project = MongoidProject.create + expect(CanCan::ModelAdapters::MongoidAdapter.find(MongoidProject, project.id)).to eq(project) + end - it "should return the correct records based on the defined ability" do + it "should return the correct records based on the defined ability" do @ability.can :read, MongoidProject, :title => "Sir" sir = MongoidProject.create(:title => 'Sir') lord = MongoidProject.create(:title => 'Lord') MongoidProject.accessible_by(@ability, :read).entries.should == [sir] - end - end end + end ``` +In this case ```MonoidProject``` is a decendant of ```MogoidDocument```. The implementation of this class +will not be shown as it only acts as an example. -You will need many more specs for full coverage but add them one at a time. To run the specs execute the following commands. +### Running tests -```bash -MODEL_ADAPTER=mongoid bundle -MODEL_ADAPTER=mongoid rake -``` +You can run tests for the project by running + + bundle exec appraisal rake + +or you can run tests only for your adapter with + + bundle exec appraisal adpater_name rake -That will fail since we have not added the implementation. +File specific tests can be run with: + + bundle exec appraisal adpater_name rspec spec/cancan/model_adapters/adapter_name.rb + +**Because we haven't implemented any functionality yet, the tests will fail.** ### The Implementation -First add a line to `lib/cancan.rb` for including the adapter only when Mongoid is present. +First add a line to lib/cancan.rb to include the adapter if a condition is met. In this case +we check if Mongoid is present. ```ruby require 'cancan/model_adapters/mongoid_adapter' if defined? Mongoid ``` - -Next create that adapter under `lib/cancan/model_adapters/mongoid_adapter.rb`. +And after that create a new adapter in model_adapters: ```ruby module CanCan @@ -105,18 +166,26 @@ module Mongoid::Document::ClassMethods end ``` -The class method called `for_class?` is used to determine if this adapter should be used for a given class. Here we just see if that model is a Mongoid document. +As mentioned before, there are methods that have to be overwritten in order to pass as a valid adapter. -The `database_records` method is used in the `accessible_by` call. Here we fetch records from `@model_class` which match the `@rules`. If there are no rules then we return a query which fetches no records. +In this case we overwrite the ```for_class?``` method to validate that the given model is a +descendant of MogoidDocument. The adapter will only be used if ```for_class?``` evalues to true. -Otherwise we start with all the records and apply each of the rule conditions to them. The `rule.base_behavior` defines whether this rule should be additive or subtractive. It is `true` for a `can` call and `false` for a `cannot` call. +And in ```database_records``` we define the way data is loaded from the storage device. +This message is used in ```accessible_by```. In this example we fetch all entries for a model that match +a given rule. -The last three lines add the `accessible_by` method to all Mongoid classes. I expect this to not be necessary in CanCan 2.0 (see [[issue #235|https://github.com/ryanb/cancan/issues#issue/235]]). +**If no rules for an object are defined, a query will be run that returns no results** -Some models add additional features to the conditions hash. With Mongoid you can do something like `:age.gt => 13`. To get this working a couple more methods need to be added to the adapter to override how conditions are checked. +If rules are present, we apply each of the rule conditions to them. The rule.base_behavior defines whether +the rule should be additive or subtractive. It will result in false for :cannot and true for :can. + +Some model types add additional features to the conditions hash. With Mongoid, for example, +you can do something like :age.gt => 13. +Because the abstract adapter has no knowledge of this, we have to overwrite the provided methods +in the new adapter. ```ruby -# in MongoidAdapter def self.override_conditions_hash_matching?(subject, conditions) conditions.any? { |k,v| !k.kind_of?(Symbol) } end @@ -126,6 +195,26 @@ def self.matches_conditions_hash?(subject, conditions) end ``` -The first one returns `true` when there's a conditions option which is not a Symbol (such as `:age.gt`). The second method will be called by CanCan when the first one returns true to check if the given subject matches the hash of conditions. +### Additional Examples + +Eventhough CanCanCan tries to make the implementation of custom adapters easy and flexible, +it can be hard task. + +Thus you'd probably be best served with inspecting the actual implementation of the activerecord adapter to get +a better overview how a battle tested adapter is structured and implemented. -See the actual [[mongoid_adapter_spec.rb|https://github.com/ryanb/cancan/blob/master/spec/cancan/model_adapters/mongoid_adapter_spec.rb]] and [[mongoid_adapter.rb|https://github.com/ryanb/cancan/blob/master/lib/cancan/model_adapters/mongoid_adapter.rb]] files for the full code. + +#### Implementation + +* [ActiveRecord Base](../lib/cancan/model_adapters/active_record_adapter.rb) +* [ActiveRecord 4](../lib/cancan/model_adapters/active_record_4_adapter.rb) +* [ActiveRecord 5](../lib/cancan/model_adapters/active_record_5_adapter.rb) + +#### Tests / Specs + +* [ActiveRecord Base](../spec/cancan/model_adapters/active_record_adapter_spec.rb) +* [ActiveRecord 4](../spec/cancan/model_adapters/active_record_4_adapter_spec.rb) +* [ActiveRecord 5](../spec/cancan/model_adapters/active_record_5_adapter_spec.rb) + +**Mondoid, the adapter used in this entry as an example, can be found at:** +* [Mongoid](https://github.com/CanCanCommunity/cancancan-mongoid) From 4a8b720e10afdc0db6e38e1e909c154f233535c0 Mon Sep 17 00:00:00 2001 From: Joe Green Date: Thu, 2 Sep 2021 18:38:14 +0100 Subject: [PATCH 06/53] bets -> best (#735) --- docs/controller_helpers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/controller_helpers.md b/docs/controller_helpers.md index feeddf1ea..b9f4318de 100644 --- a/docs/controller_helpers.md +++ b/docs/controller_helpers.md @@ -1,6 +1,6 @@ # Controller helpers -As mentioned in the chapter [Define and check abilities](./define_and_check_abilities.md), the `can?` method works at its bets in Rails controllers and views. +As mentioned in the chapter [Define and check abilities](./define_and_check_abilities.md), the `can?` method works at its best in Rails controllers and views. This of course doesn't mean that it cannot be used everywhere. We know already that in order to check if the user is allowed to perform a certain action we need to have a `current_user` method available and we can check the permission with `can? :update, @article`. From 8699ead54f620e4430e22997737267831b1ff783 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Prod=27homme?= Date: Tue, 14 Sep 2021 20:51:55 +0200 Subject: [PATCH 07/53] fix link not found (#736) --- docs/controller_helpers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/controller_helpers.md b/docs/controller_helpers.md index b9f4318de..f9b5f4337 100644 --- a/docs/controller_helpers.md +++ b/docs/controller_helpers.md @@ -40,7 +40,7 @@ You can have a global configuration on how to react to this exception in `config config.action_dispatch.rescue_responses.merge!('CanCan::AccessDenied' => :unauthorized) ``` -The [Handling CanCan::AccessDenied Exception](./handling_exception.md) chapter digs deeper on how to handle the exception raised by `authorize!`. +The [Handling CanCan::AccessDenied Exception](./handling_access_denied.md) chapter digs deeper on how to handle the exception raised by `authorize!`. > `:unauthorized` might not be your favourite return status of you don't want to reveal to the user that the article exists. In such cases `:not_found` would be a better http status. From 8a126220e6e4e7a1c63ce1982caf5d086d081b2b Mon Sep 17 00:00:00 2001 From: Peter Nagy Date: Thu, 7 Oct 2021 11:16:08 +0200 Subject: [PATCH 08/53] Updated Devise.md as exception is changed CanCan::Unathorized exception not exists anymore, CanCan::AccessDenied is used. Also added responses for different content types as a recommendation. --- docs/Devise.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/Devise.md b/docs/Devise.md index e36363233..bf398becc 100644 --- a/docs/Devise.md +++ b/docs/Devise.md @@ -13,13 +13,16 @@ end It may be a good idea to specify the rescue from action: ```ruby -rescue_from CanCan::Unauthorized do |exception| +rescue_from CanCan::AccessDenied do |exception| if current_user.nil? session[:next] = request.fullpath redirect_to login_url, alert: 'You have to log in to continue.' else - # render file: "#{Rails.root}/public/403.html", status: 403 - redirect_back(fallback_location: root_path) + respond_to do |format| + format.json { render nothing: true, status: :not_found } + format.html { redirect_to main_app.root_url, alert: exception.message } + format.js { render nothing: true, status: :not_found } + end end end -``` \ No newline at end of file +``` From 367bb7c536aa1513e40c415155875e8e347b5607 Mon Sep 17 00:00:00 2001 From: saguiguilid Date: Sat, 23 Oct 2021 20:08:19 +0800 Subject: [PATCH 09/53] Documentation fixes and improvements - fix typo, punctuations, and grammar - fix broken and misformatted links - fix code sample --- docs/accessible_attributes.md | 4 ++-- docs/define_check_abilities.md | 4 ++-- docs/fetching_records.md | 7 ++++--- docs/hash_of_conditions.md | 3 +-- docs/inherited_resources.md | 6 ++++-- docs/role_based_authorization.md | 11 +++++------ docs/testing.md | 4 ++-- 7 files changed, 20 insertions(+), 19 deletions(-) diff --git a/docs/accessible_attributes.md b/docs/accessible_attributes.md index d97273fda..bc69eb2f9 100644 --- a/docs/accessible_attributes.md +++ b/docs/accessible_attributes.md @@ -33,5 +33,5 @@ or in Strong Parameters: ```ruby params .require(:book) - .permit(ability.permitted_attributes(:read, @book)) -``` \ No newline at end of file + .permit(current_ability.permitted_attributes(:read, @book)) +``` diff --git a/docs/define_check_abilities.md b/docs/define_check_abilities.md index 0f5bd4143..99dfb77e5 100644 --- a/docs/define_check_abilities.md +++ b/docs/define_check_abilities.md @@ -185,11 +185,11 @@ can :manage, :all and give all possible permissions to the administrator. -Note that the code above allows the administrator to also `:read, :admin_dashboard`. :manage means literally **any** action, not only CRUD ones. +Note that the code above allows the administrator to also `:read, :admin_dashboard`. `:manage` means literally **any** action, not only CRUD ones. > You **must and should** always check for specific permisssions, but you don't need to define all of them if not needed. -If at some point you have a new page reserved to the administrators, where they can traslate articles, you should check for `can? :translate, @article`, but you don't need to define the ability, since the administrators can already do any action. It will be easy in the future to give the possibility for authors to translate their own articles by changing your permissions file: +If at some point you have a new page reserved to the administrators, where they can translate articles, you should check for `can? :translate, @article`, but you don't need to define the ability, since the administrators can already do any action. It will be easy in the future to give the possibility for authors to translate their own articles by changing your permissions file: ```ruby can :read, Article, published: true diff --git a/docs/fetching_records.md b/docs/fetching_records.md index 886b35bda..31485b06d 100644 --- a/docs/fetching_records.md +++ b/docs/fetching_records.md @@ -46,18 +46,19 @@ And this is just an ActiveRecord scope so other scopes and pagination can be cha The call to accessible_by in the example above will generate the proper SQL to limit the records fetched. - This works also with multiple `can` definitions, which allows you to define complex permission logic and have it translated properly to SQL. Given the definition: + ```ruby class Ability can :read, Article, public: true - cannot :read, User, self_managed: true + cannot :read, Article, self_managed: true can :read, Article, user: user end ``` -a call to Article.accessible_by(current_ability) generates the following SQL + +a call to `Article.accessible_by(current_ability)` generates the following SQL ```sql SELECT * diff --git a/docs/hash_of_conditions.md b/docs/hash_of_conditions.md index 0c6bcf6c4..1b03c416a 100644 --- a/docs/hash_of_conditions.md +++ b/docs/hash_of_conditions.md @@ -33,8 +33,7 @@ can :read, Project, active: true, owner: user so by using the association `owner` instead of the database column `user_id`. - -You can nest conditionsassociations. Here the project can only be read if the category it belongs to is visible. +You can nest conditions associations. Here the project can only be read if the category it belongs to is visible. ```ruby can :read, Project, category: { visible: true } diff --git a/docs/inherited_resources.md b/docs/inherited_resources.md index fa11e6296..11b1d1216 100644 --- a/docs/inherited_resources.md +++ b/docs/inherited_resources.md @@ -1,7 +1,7 @@ # Inherited Resources **This guide is for cancancan < 2.0 only. -If you want to use Inherited Resources and cancancan 2.0 please check for extensions like https://github.com/TylerRick/cancan-inherited_resources** +If you want to use Inherited Resources and cancancan 2.0 please check for extensions like [cancan-inherited_resources](https://github.com/TylerRick/cancan-inherited_resources). The `load_and_authorize_resource` call will automatically detect if you are using [Inherited Resources](http://github.com/josevalim/inherited_resources) and load the resource through that. The `load` part in CanCan is still necessary since Inherited Resources does lazy loading. This will also ensure the behavior is identical to normal loading. @@ -20,11 +20,13 @@ class TasksController < InheritedResources::Base load_and_authorize_resource :task, :through => :project end ``` + Please note that even for a `has_many :tasks` association, the `load_and_authorize_resource` needs the singular name of the associated model... -**Warning**: when overwriting the `collection` method in a controller the `load` part of a `load_and_authorize_resource` call will not work correctly. See https://github.com/ryanb/cancan/issues/274 for the discussions. +**Warning**: when overwriting the `collection` method in a controller the `load` part of a `load_and_authorize_resource` call will not work correctly. See for the discussions. In this case you can override collection like + ```ruby skip_load_and_authorize_resource :only => :index diff --git a/docs/role_based_authorization.md b/docs/role_based_authorization.md index 49fc55f6e..d9e6e3c89 100644 --- a/docs/role_based_authorization.md +++ b/docs/role_based_authorization.md @@ -51,10 +51,9 @@ It's then very simple to determine the role of the user in the Ability class. can :manage, :all if user.role == "admin" ``` - ## Many roles per user -It is possible to assign multiple roles to a user and store it into a single integer column using a [[bitmask|http://en.wikipedia.org/wiki/Mask_(computing)]]. First add a `roles_mask` integer column to your `users` table. +It is possible to assign multiple roles to a user and store it into a single integer column using a [bitmask](). First add a `roles_mask` integer column to your `users` table. ```bash rails generate migration add_roles_mask_to_users roles_mask:integer @@ -83,9 +82,10 @@ If you're using devise, don't forget to add `attr_accessible :roles` to your use before_action :configure_permitted_parameters, if: :devise_controller? protected def configure_permitted_parameters - devise_parameter_sanitizer.for(:sign_up) { |u| u.permit( :email, :password, :password_confirmation, roles: [] ) } + devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:email, :password, :password_confirmation, roles: []) } end ``` + You can use checkboxes in the view for setting these roles. ```rhtml @@ -110,11 +110,10 @@ can :manage, :all if user.has_role? :admin See [[Custom Actions]] for a way to restrict which users can assign roles to other users. -This functionality has also been extracted into a little gem called [[role_model|http://rubygems.org/gems/role_model]] ([[code & howto|http://github.com/martinrehfeld/role_model]]). +This functionality has also been extracted into a little gem called [role_model](http://rubygems.org/gems/role_model) ([code & howto](http://github.com/martinrehfeld/role_model)). If you do not like this bitmask solution, see [[Separate Role Model]] for an alternative way to handle this. - ## Role Inheritance Sometimes you want one role to inherit the behavior of another role. For example, let's say there are three roles: moderator, admin, superadmin and you want each one to inherit the abilities of the one before. There is also a "role" string column in the User model. You should create a method in the User model which has the inheritance logic. @@ -144,7 +143,7 @@ end Here a superadmin will be able to manage all three classes but a moderator can only manage the one. Of course you can change the role logic to fit your needs. You can add complex logic so certain roles only inherit from others. And if a given user can have multiple roles you can decide whether the lowest role takes priority or the highest one does. Or use other attributes on the user model such as a "banned", "activated", or "admin" column. -This functionality has been extracted into a gem called [[canard|http://rubygems.org/gems/canard]] ([[code & howto|http://github.com/james2m/canard]]). +This functionality has been extracted into a gem called [canard](http://rubygems.org/gems/canard) ([code & howto](http://github.com/james2m/canard)). ## Alternative Role Inheritance diff --git a/docs/testing.md b/docs/testing.md index 32f5b1a85..7c2d1155d 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -37,10 +37,10 @@ require "cancan/matchers" describe "User" do describe "abilities" do subject(:ability) { Ability.new(user) } - let(:user){ nil } + let(:user) { nil } context "when is an account manager" do - let(:user){ create(:account_manager) } + let(:user) { create(:account_manager) } it { is_expected.to be_able_to(:manage, Account.new) } end From 10bd7991c79dc8952b03f7bd7230357b0308c3d8 Mon Sep 17 00:00:00 2001 From: saguiguilid Date: Sat, 23 Oct 2021 20:08:59 +0800 Subject: [PATCH 10/53] Format documentation/guides and fix linting issues The motivation of the changes is to make these documents neater when reading offline using a text or code editor. Reading the documentation offline is faster, and the source code is readily available for further learning. The changes do not affect the meaning of each documentation or instruction. The following changes were made: - remove extra and trailing spaces - add new lines to separate the title, explanation, code samples, etc - fix headings, links - specify code block language (bash, ruby) --- docs/Devise.md | 4 +- docs/README.md | 6 +- docs/abilities_in_database.md | 13 ++- docs/accessing_request_data.md | 1 + docs/cannot.md | 2 +- docs/changing_defaults.md | 22 +++-- docs/check_abilities_mistakes.md | 4 +- docs/combine_abilities.md | 10 +-- docs/controller_helpers.md | 10 +-- docs/debugging.md | 5 +- docs/define_abilities_best_practices.md | 6 +- docs/define_abilities_with_blocks.md | 11 ++- docs/define_check_abilities.md | 31 +++---- docs/fetching_records.md | 8 +- docs/friendly_id.md | 2 +- docs/handling_access_denied.md | 8 +- docs/hash_of_conditions.md | 9 +- docs/inherited_resources.md | 3 +- docs/installation.md | 4 +- docs/internationalization.md | 12 ++- docs/introduction.md | 18 ++-- docs/migrating.md | 20 ++--- docs/model_adapter.md | 108 +++++++++++------------- docs/nested_resources.md | 18 ++-- docs/role_based_authorization.md | 5 +- docs/rules_compression.md | 11 ++- docs/split_ability.md | 4 +- docs/sql_strategies.md | 2 +- docs/testing.md | 7 +- 29 files changed, 182 insertions(+), 182 deletions(-) diff --git a/docs/Devise.md b/docs/Devise.md index e36363233..177be0d2f 100644 --- a/docs/Devise.md +++ b/docs/Devise.md @@ -5,7 +5,7 @@ You should bypass CanCanCan's authorization for Devise controllers: ```ruby class ApplicationController < ActionController::Base protect_from_forgery - + check_authorization unless: :devise_controller? end ``` @@ -22,4 +22,4 @@ rescue_from CanCan::Unauthorized do |exception| redirect_back(fallback_location: root_path) end end -``` \ No newline at end of file +``` diff --git a/docs/README.md b/docs/README.md index 43583145c..3770b0d83 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,8 +10,8 @@ You can skip the [Introduction](./introduction.md) where there's just some histo ## Summary -1. [Introduction](./introduction.md) -1. [Installation](./installation.md) +1. [Introduction](./introduction.md) +1. [Installation](./installation.md) 1. [Define and check abilities](./define_check_abilities.md) 1. [Controller helpers](./controller_helpers.md) 1. [Fetching records](./fetching_records.md) @@ -41,4 +41,4 @@ In these topics, you will learn some best practices, but also how to solve speci 1. [Rules compression](./rules_compression.md) 1. [Inherited Resources](./inherited_resources.md) 1. [Devise](./devise.md) -1. [FriendlyId](./friendly_id.md) \ No newline at end of file +1. [FriendlyId](./friendly_id.md) diff --git a/docs/abilities_in_database.md b/docs/abilities_in_database.md index 603d576eb..b1890ad25 100644 --- a/docs/abilities_in_database.md +++ b/docs/abilities_in_database.md @@ -1,9 +1,9 @@ # Abilities in Database -What if you or a client, wants to change permissions without having to re-deploy the application? +What if you or a client, wants to change permissions without having to re-deploy the application? In that case, it may be best to store the permission logic in a database: it is very easy to use the database records when defining abilities. -We will need a model called `Permission`. +We will need a model called `Permission`. Each user `has_many :permissions`, and each permission has `action`, `subject_class` and `subject_id` columns. The last of which is optional. @@ -40,7 +40,6 @@ The actual details will depend largely on your application requirements, but hop You can mix-and-match this with defining permissions in the code as well. This way you can keep the more complex logic in the code so you don't need to shoe-horn every kind of permission rule into an overly-abstract database. - You can also create a `Permission` model containing all possible permissions in your app. Use that code to create a rake task that fills a `Permission` table: (The code below is not fully tested) @@ -71,7 +70,7 @@ def setup_actions_controllers_db end # You can change ApplicationController for a super-class used by your restricted controllers ApplicationController.subclasses.each do |controller| - if controller.respond_to?(:permission) + if controller.respond_to?(:permission) klass, description = controller.permission write_permission(klass, "manage", description, "All operations") controller.action_methods.each do |action| @@ -82,7 +81,7 @@ def setup_actions_controllers_db end end end - + end @@ -108,11 +107,11 @@ def eval_cancan_action(action) end def write_permission(class_name, cancan_action, name, description, force_id_1 = false) - permission = Permission.find(:first, :conditions => ["subject_class = ? and action = ?", class_name, cancan_action]) + permission = Permission.find(:first, :conditions => ["subject_class = ? and action = ?", class_name, cancan_action]) if not permission permission = Permission.new permission.id = 1 if force_id_1 - permission.subject_class = class_name + permission.subject_class = class_name permission.action = cancan_action permission.name = name permission.description = description diff --git a/docs/accessing_request_data.md b/docs/accessing_request_data.md index c5cbea389..238389d01 100644 --- a/docs/accessing_request_data.md +++ b/docs/accessing_request_data.md @@ -13,6 +13,7 @@ class ApplicationController < ActionController::Base end end ``` + ```ruby class Ability include CanCan::Ability diff --git a/docs/cannot.md b/docs/cannot.md index de6b2e646..5d1fc4d69 100644 --- a/docs/cannot.md +++ b/docs/cannot.md @@ -11,4 +11,4 @@ cannot :destroy, Project will allow the user to do **any** action but destroy the project. -Of course, there's a `cannot?` method to check abilities that is a simple alias for `!can?`. \ No newline at end of file +Of course, there's a `cannot?` method to check abilities that is a simple alias for `!can?`. diff --git a/docs/changing_defaults.md b/docs/changing_defaults.md index 7db84fd2f..5420b4c94 100644 --- a/docs/changing_defaults.md +++ b/docs/changing_defaults.md @@ -6,8 +6,8 @@ We now dig deeper in the customizations and options we have when working with [c CanCanCan makes two assumptions about your application: -* You have an `Ability` class which defines the permissions. -* You have a `current_user` method in the controller which returns the current user model. +- You have an `Ability` class which defines the permissions. +- You have a `current_user` method in the controller which returns the current user model. You can override both of these by defining the `current_ability` method in your `ApplicationController`. The default method looks like this. @@ -58,7 +58,7 @@ class ArticlesController < ApplicationController load_and_authorize_resource param_method: :my_sanitizer def create - @article.save + @article.save end private @@ -81,7 +81,7 @@ Finally, it's possible to associate `param_method` with a Proc object which will load_and_authorize_resource param_method: -> { |c| c.params.require(:article).permit(:name) } ``` -If your model name and controller name differ, you can specify a `class` option. +If your model name and controller name differ, you can specify a `class` option. > Note that the method will still be `articles_params` and not `post_params`, since we are in `ArticlesController`. @@ -105,7 +105,6 @@ end You can use CanCanCan with controllers that do not follow the traditional REST actions, however you should not use the `load_and_authorize_resource` method since there is no resource to load. Instead you can call `authorize!` in each action separately. - For example, let's say we have a controller which does some miscellaneous administration tasks such as rolling log files. We can use the `authorize!` method here. ```ruby @@ -166,7 +165,6 @@ class ProductsController < ApplicationController end ``` - ### Custom find If you want to fetch a resource by something other than `id` it can be done so using the `find_by` option. @@ -225,9 +223,9 @@ Here's another example where authorization is only ensured for the admin subdoma ```ruby class ApplicationController < ActionController::Base check_authorization if: :admin_subdomain? - + private - + def admin_subdomain? request.subdomain == "admin" end @@ -236,15 +234,15 @@ end > Note: The `check_authorization` only ensures that authorization is performed. If you have `authorize_resource` the authorization will still be performed no matter what is returned here. -The default operation for CanCanCan is to authorize based on user and the object identified in `load_resource`. So if you have a `WidgetsController` and also an `Admin::WidgetsController`, you can use some different approaches. +The default operation for CanCanCan is to authorize based on user and the object identified in `load_resource`. So if you have a `WidgetsController` and also an `Admin::WidgetsController`, you can use some different approaches. # Overriding authorizations for Namespaced controllers -You can create differing authorization rules that depend on the controller namespace. +You can create differing authorization rules that depend on the controller namespace. In this case, just override the `current_ability` method in `ApplicationController` to include the controller namespace, and create an `Ability` class that knows what to do with it. -``` ruby +```ruby class Admin::WidgetsController < ActionController::Base #... @@ -276,7 +274,7 @@ end Another way to achieve the same is to use a completely different Ability class in this controller: -``` ruby +```ruby class Admin::WidgetsController < ActionController::Base #... diff --git a/docs/check_abilities_mistakes.md b/docs/check_abilities_mistakes.md index 7b16f7787..a9118eabb 100644 --- a/docs/check_abilities_mistakes.md +++ b/docs/check_abilities_mistakes.md @@ -33,7 +33,7 @@ Think of it as asking > can the current user read **a** project?" -The user can read a project, so this returns `true`. However it depends on which specific project you're talking about. +The user can read a project, so this returns `true`. However it depends on which specific project you're talking about. If you are doing a class check, it is important you do another check once an instance becomes available so the hash of conditions can be used. @@ -43,6 +43,6 @@ That is why passing a class to `can?` will return `true`. The code answering the question "can the user update all the articles?" would be something like: -``` ruby +```ruby Article.accessible_by(current_ability).count == Article.count ``` diff --git a/docs/combine_abilities.md b/docs/combine_abilities.md index 8cfaaf7bc..204341d8d 100644 --- a/docs/combine_abilities.md +++ b/docs/combine_abilities.md @@ -14,13 +14,13 @@ can :manage, Project cannot :destroy, Project ``` -The order of these calls is important. +The order of these calls is important. ## Abilities precedence -An ability rule will override a previous one. +An ability rule will override a previous one. -For example, let's say we want the user to be able to do everything to projects except destroy them. +For example, let's say we want the user to be able to do everything to projects except destroy them. This is the correct way: @@ -38,7 +38,7 @@ can :manage, Project, user: user can :update, Project, locked: false ``` -For the above, `can? :update, @project` will return true if project owner is the user, even if the project is locked. +For the above, `can? :update, @project` will return true if project owner is the user, even if the project is locked. This is also important when dealing with roles which have inherited behavior. For example, let's say we have two roles, moderator and admin. We want the admin to inherit the moderator's behavior. @@ -56,4 +56,4 @@ end Here it is important for the admin permissions to be defined after the moderator ones, so it can override the `cannot` behavior to give the admin more permissions. -Let's now check at a different way of defining abilities: [blocks](./define_abilities_with_blocks.md). \ No newline at end of file +Let's now check at a different way of defining abilities: [blocks](./define_abilities_with_blocks.md). diff --git a/docs/controller_helpers.md b/docs/controller_helpers.md index f9b5f4337..9280b8416 100644 --- a/docs/controller_helpers.md +++ b/docs/controller_helpers.md @@ -42,9 +42,9 @@ config.action_dispatch.rescue_responses.merge!('CanCan::AccessDenied' => :unauth The [Handling CanCan::AccessDenied Exception](./handling_access_denied.md) chapter digs deeper on how to handle the exception raised by `authorize!`. -> `:unauthorized` might not be your favourite return status of you don't want to reveal to the user that the article exists. In such cases `:not_found` would be a better http status. +> `:unauthorized` might not be your favourite return status if you don't want to reveal to the user that the article exists. In such cases, `:not_found` would be a better http status. -# authorize_resource, load_resource, load_and_authorize_resource +## authorize_resource, load_resource, load_and_authorize_resource In a RESTful controller, calling `authorize! action` for every action can be tedious. Here we will show you, step by step, how to improve the code above. @@ -83,11 +83,11 @@ and, clearly, `load_and_authorize_resource` allows to do the following: class ArticlesController < ApplicationController load_and_authorize_resource - def edit; end + def edit; end end ``` -this means that a completely authorized `ArticlesController` would look as follow: +this means that a completely authorized `ArticlesController` would look as follow: ```ruby class ArticlesController < ApplicationController @@ -107,7 +107,7 @@ class ArticlesController < ApplicationController end def edit - # the @article to edit is already loaded and authorized + # the @article to edit is already loaded and authorized end def update diff --git a/docs/debugging.md b/docs/debugging.md index 772016dc1..7a3a27baf 100644 --- a/docs/debugging.md +++ b/docs/debugging.md @@ -1,6 +1,6 @@ # Debugging Abilities -What do you do when permissions you defined in the Ability class don't seem to be working properly? +What do you do when permissions you defined in the Ability class don't seem to be working properly? Have you already read the [Testing](./testing.md) section? You can now try to reproduce this problem in the `rails console`. @@ -38,6 +38,7 @@ can :update, Project, ["priority < ?", 3] do |project| project.priority < 3 end ``` + ## Logging AccessDenied Exception If you think the `CanCan::AccessDenied` exception is being raised and you are not sure why, you can log this behavior to help debug what is triggering it. @@ -53,4 +54,4 @@ end ## Issue Tracker If you are still unable to resolve the issue, [open a question on Stackoverflow](http://stackoverflow.com/questions/ask?tags=cancancan) with tag -[cancancan](http://stackoverflow.com/questions/tagged/cancancan). \ No newline at end of file +[cancancan](http://stackoverflow.com/questions/tagged/cancancan). diff --git a/docs/define_abilities_best_practices.md b/docs/define_abilities_best_practices.md index a1af3fdc5..86d266dc5 100644 --- a/docs/define_abilities_best_practices.md +++ b/docs/define_abilities_best_practices.md @@ -28,7 +28,7 @@ can :read, Article, is_published: true By using hashes instead of blocks for all actions, you won't have to worry about translating blocks used for member controller actions (`:create`, `:destroy`, `:update`) to equivalent blocks for collection actions (`:index`, `:show`)—which require hashes anyway! ### Hash conditions are OR'd in SQL, giving you maximum flexibilty. - + Every time you define an ability with `can`, each `can` chains together with OR in the final SQL query for that model. So if, in addition to the `is_published` condition above, we want to allow authors to see their drafts: @@ -54,7 +54,7 @@ See [Hash of Conditions Chapter](./hash_of_conditions.md). As suggested in this [topic on Reddit](https://www.reddit.com/r/ruby/comments/6ytka8/refactoring_cancancan_abilities_brewing_bits/) you should, when possible, give increasing permissions to your users. -CanCanCan increases permissions: it starts by giving no permissions to nobody and then increases those permissions depending on the user. +CanCanCan increases permissions: it starts by giving no permissions to nobody and then increases those permissions depending on the user. A properly written `ability.rb` looks like that: @@ -75,6 +75,6 @@ class Ability end ``` -following this good practice will help you to keep your permissions clean and more readable. +following this good practice will help you to keep your permissions clean and more readable. The risk of giving wrong permissions to the wrong users is also decreased. diff --git a/docs/define_abilities_with_blocks.md b/docs/define_abilities_with_blocks.md index 634652957..60b25480a 100644 --- a/docs/define_abilities_with_blocks.md +++ b/docs/define_abilities_with_blocks.md @@ -8,9 +8,9 @@ can :update, Project do |project| end ``` -Note that if you pass a block to a `can` or `cannot`, the block only executes if an instance of a class is passed to `can?` or `cannot?` calls. +Note that if you pass a block to a `can` or `cannot`, the block only executes if an instance of a class is passed to `can?` or `cannot?` calls. -If you define a `can` or `cannot` with a block and an object is not passed, the check will pass. +If you define a `can` or `cannot` with a block and an object is not passed, the check will pass. ```ruby can :update, Project do |project| @@ -24,7 +24,7 @@ can? :update, Project # returns true! ## Fetching Records -A block's conditions are only executable through Ruby. If you are [Fetching Records](./fetching_records.md) using `accessible_by` it will raise an exception. +A block's conditions are only executable through Ruby. If you are [Fetching Records](./fetching_records.md) using `accessible_by` it will raise an exception. To fetch records from the database you need to supply an SQL string representing the condition. The SQL will go in the `WHERE` clause: @@ -36,7 +36,6 @@ end > If you are using `load_resource` and don't supply this SQL argument, the instance variable will not be set for the `index` action since they cannot be translated to a database query. - ## Block Conditions with ActiveRecord Scopes It's also possible to pass a scope instead of an SQL string when using a block in an ability. @@ -49,8 +48,8 @@ end This is really useful if you have complex conditions which require `joins`. A couple of caveats: -* You cannot use this with multiple `can` definitions that match the same action and model since it is not possible to combine them. An exception will be raised when that is the case. -* If you use this with `cannot`, the scope needs to be the inverse since it's passed directly through. For example, if you don't want someone to read discontinued products the scope will need to fetch non discontinued ones: +- You cannot use this with multiple `can` definitions that match the same action and model since it is not possible to combine them. An exception will be raised when that is the case. +- If you use this with `cannot`, the scope needs to be the inverse since it's passed directly through. For example, if you don't want someone to read discontinued products the scope will need to fetch non discontinued ones: ```ruby cannot :read, Product, Product.where(discontinued: false) do |product| diff --git a/docs/define_check_abilities.md b/docs/define_check_abilities.md index 99dfb77e5..afd80e932 100644 --- a/docs/define_check_abilities.md +++ b/docs/define_check_abilities.md @@ -5,13 +5,12 @@ CanCanCan is an authorization library and therefore the first and most interesti There are two basic methods in CanCanCan that you will use: ```ruby -can actions, subjects, conditions +can actions, subjects, conditions # without the question mark ``` is how you define who can **perform** certain `actions` on certain `subjects`. - ```ruby can? action, subject ``` @@ -28,7 +27,8 @@ class Article end ``` -The answer to this question is that +The answer to this question is that + > "only the author can edit an article." We can define the permissions in the `ability.rb`: @@ -56,13 +56,14 @@ can? :update, @article # => true But how does CanCanCan know who is the `user`? When you use the `can?` method in a Rails controller or view, CanCanCan expects that there's a `current_user` method defined. So if you are using something like [devise](https://github.com/heartcombo/devise) for your authentication, you don't need to do anything special. -By default, CanCanCan assumes no permissions: no one can do any action on any object. +By default, CanCanCan assumes no permissions: no one can do any action on any object. `can :update, Article, user: user` is stating that the user can update an article, if it is its author. Regarding the `Article` there are actually more permissions to check: -* who can read them? -* what can the administrator do? + +- who can read them? +- what can the administrator do? A complete example looks like the following: @@ -82,9 +83,9 @@ can :update, Article The code above is stating the following: -* users that are not logged in, can read published articles -* logged in users can **also** read and update their own articles -* administrators can read and update all the articles. +- users that are not logged in, can read published articles +- logged in users can **also** read and update their own articles +- administrators can read and update all the articles. > CanCanCan works, at its best, when defining increasing permissions. @@ -115,7 +116,7 @@ update: [:edit, :update] destroy: [:destroy] ``` -this means that when you define `can :read, Article`, you can also check: +this means that when you define `can :read, Article`, you can also check: ```ruby can? :show, @article @@ -133,7 +134,7 @@ For now, what you need to know, is that these four will be your most used, basic One last action is `manage`. This action means that you have full permissions on the subject and you can perform any possible action. Knowing that, we can now rewrite our ability.rb example: -``` +```ruby can :read, Article, published: true return unless user.present? @@ -154,14 +155,14 @@ can? :destroy, @article # => true Now that we learned about actions and their aliases let's see what we can do with the subjects -# Can subjects +## Can subjects The subject of an action is usually a Ruby class. Most of the times you want to define your permissions on specific classes, but this is not your only option. You can actually use any subject, and one of the most common cases is to just use a symbol. An admin dashboard could be protected by definining: -``` +```ruby can :read, :admin_dashboard ``` @@ -203,7 +204,7 @@ return unless user.admin? can :manage, :all ``` -# Checking other users abilities +## Checking other users abilities What if you want to determine the abilities of a `User` record that is not the `current_user`? Maybe we want to see if another user can update an article. @@ -228,4 +229,4 @@ some_user.can? :update, @article That's everything you know about defining and checking abilities. The DSL is very easy but yet very powerful. There's still a lot you need/should learn about defining abilities. You can [dig deeper](./hash_of_conditions.md) now, but we would suggest to stop, digest it, and proceed on a more Rails-specific topic: [Controller helpers](./controller_helpers.md) where you will learn how to secure your Rails application. -Or you could already take a look at the session about [testing](./testing.md). \ No newline at end of file +Or you could already take a look at the session about [testing](./testing.md). diff --git a/docs/fetching_records.md b/docs/fetching_records.md index 31485b06d..c7fbc1e7e 100644 --- a/docs/fetching_records.md +++ b/docs/fetching_records.md @@ -68,12 +68,10 @@ WHERE (user_id = 1) OR (not (self_managed = 'true') AND (public = 'true')) The generation of the SQL query is a very complex task and probably the most powerful feature of CanCanCan. -Even if the default behaviour will suffice at the beginning, larger databases or more complex rules, might lead to very complex SQL queries. This might result in a slow fetching of records. This is why is possible to use different strategies to generate the SQL. -You will see that in one of the last chapters: [SQL strategies](./sql_strategies.ms) +Even if the default behaviour will suffice at the beginning, larger databases or more complex rules, might lead to very complex SQL queries. This might result in a slow fetching of records. This is why it is possible to use different strategies to generate the SQL. +You will see that in one of the last chapters: [SQL strategies](./sql_strategies.md) -# Blocks +## Blocks We haven't spoken about block abilities yet, but the SQL generation will not be possible if you have even a single rule that is defined using just a block. You can define SQL fragments in addition to block to fix that. But we'll see that in the [Define Abilities with Blocks](./define_abilities_with_blocks.md) chapter. -``` - diff --git a/docs/friendly_id.md b/docs/friendly_id.md index f013d5725..8f2f92a38 100644 --- a/docs/friendly_id.md +++ b/docs/friendly_id.md @@ -30,4 +30,4 @@ if defined?(CanCanCan) end end end -``` \ No newline at end of file +``` diff --git a/docs/handling_access_denied.md b/docs/handling_access_denied.md index 00cc9b033..e5c6a32bb 100644 --- a/docs/handling_access_denied.md +++ b/docs/handling_access_denied.md @@ -1,8 +1,8 @@ # Handling CanCan::AccessDenied -In the [Controller helpers](./controller_helpers.md) chapter was saw that when a resource is not authorized, a `CanCan::AccessDenied` exception is raised, and we offered a basic handling through `config/application.rb`. Let's now see what else we can do. +In the [Controller helpers](./controller_helpers.md) chapter, we saw that when a resource is not authorized, a `CanCan::AccessDenied` exception is raised, and we offered a basic handling through `config/application.rb`. Let's now see what else we can do. -The `CanCan::AccessDenied` exception is raised when calling `authorize!` in the controller and the user is not able to perform the given action. +The `CanCan::AccessDenied` exception is raised when calling `authorize!` in the controller and the user is not able to perform the given action. A message can optionally be provided. @@ -28,7 +28,7 @@ en: update: project: "Not allowed to update this project." action_name: - model_name: "..." + model_name: "..." ``` Notice `manage` and `all` can be used to generalize the subject and actions. Also `%{action}` and `%{subject}` can be used as interpolated variables in the message. @@ -103,4 +103,4 @@ class ApplicationController < ActionController::Base end end end -``` \ No newline at end of file +``` diff --git a/docs/hash_of_conditions.md b/docs/hash_of_conditions.md index 1b03c416a..c5caa0467 100644 --- a/docs/hash_of_conditions.md +++ b/docs/hash_of_conditions.md @@ -12,7 +12,7 @@ to say that an Article can be updated only by it's author. But how does it work? The third argument of the `can` method (`{ user: user }`) is the hash of conditions for this rule. -A hash of conditions can be passed to further restrict which records this permission applies to. +A hash of conditions can be passed to further restrict which records this permission applies to. In the example below the user will only have permission to read active projects which they own. @@ -25,6 +25,7 @@ When defining a condition, the key should always be either a database column of ```ruby belongs_to :owner, class_name: 'User', foreign_key: :user_id ``` + the rule can also be written as: ```ruby @@ -45,7 +46,7 @@ An array or range can be passed to match multiple values. Here the user can only can :read, Project, priority: 1..3 ``` -Almost anything that you can pass to a hash of conditions in ActiveRecord will work here as well. +Almost anything that you can pass to a hash of conditions in ActiveRecord will work here as well. ## Traverse associations @@ -57,7 +58,7 @@ class User end class Account - has_one :user + has_one :user has_many :services end @@ -66,7 +67,7 @@ class Service has_many :parts end -class Part +class Part belongs_to :service end diff --git a/docs/inherited_resources.md b/docs/inherited_resources.md index 11b1d1216..a8ad377bf 100644 --- a/docs/inherited_resources.md +++ b/docs/inherited_resources.md @@ -36,6 +36,7 @@ end ``` ## Mongoid + With mongoid it is necessary to reference `:project_id` instead of just `:project` ```ruby @@ -43,4 +44,4 @@ class TasksController < InheritedResources::Base ... load_and_authorize_resource :task, :through => :project_id end -``` \ No newline at end of file +``` diff --git a/docs/installation.md b/docs/installation.md index da0f1d464..78c06f023 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -30,6 +30,6 @@ end This is everything you need to start. :boom: All the permissions will be defined in this file. -You can of course split it into multiple files if your application grows, but we'll cover that in a [later chapter](./split_ability.md). +You can of course split it into multiple files if your application grows, but we'll cover that in a [later chapter](./split_ability.md). -Let's now start with the basic concepts: [define and check abilities](./define_check_abilities.md). \ No newline at end of file +Let's now start with the basic concepts: [define and check abilities](./define_check_abilities.md). diff --git a/docs/internationalization.md b/docs/internationalization.md index 41af3d7a8..578f930cb 100644 --- a/docs/internationalization.md +++ b/docs/internationalization.md @@ -11,7 +11,8 @@ en: ``` ## Translation for individual abilities -If you want to customize messages for some model or even for some ability define translation like this: + +If you want to customize messages for some model or even for some ability, define translation like this: ```ruby # models/ability.rb @@ -19,6 +20,7 @@ If you want to customize messages for some model or even for some ability define can :create, Article ... ``` + ```yaml # en.yml en: @@ -28,13 +30,16 @@ en: ``` ### Translating custom abilities + Also translations is available for your custom abilities: + ```ruby # models/ability.rb ... can :vote, Article ... ``` + ```yaml # en.yml en: @@ -42,12 +47,15 @@ en: vote: article: "Only users which have one or more article can vote" ``` + ## Variables for translations + Finally you may use `action`(which contain ability like 'create') and `subject`(for example 'article') variables in your translation: + ```yaml # en.yml en: unauthorized: manage: all: "You do not have access to %{action} %{subject}!" -``` \ No newline at end of file +``` diff --git a/docs/introduction.md b/docs/introduction.md index 1a0e6eab2..74ee76121 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -3,31 +3,29 @@ Hi everyone :wave:,
I am [Alessandro](https://github.com/coorasse), the maintainer of CanCanCan since 2015. I want to present you this new guide for developers. -Since I took over the CanCanCan project in 2015, I felt one of the most urgent things to do was rewriting the documentation: -the previous documentation was in a Wiki, but giving free access to everyone to edit it, ended up in a big mess after so many years. +Since I took over the CanCanCan project in 2015, I felt one of the most urgent things to do was rewriting the documentation: +the previous documentation was in a Wiki, but giving free access to everyone to edit it, ended up in a big mess after so many years. **Information was all there, but in a very unstructured way.** One of the first things I did was to block the Wiki from free editing and after that, I moved all the wiki within the git repository, so that every update had to go through a Pull Request and code review. **I think documentation is as important as the source code, and therefore should follow the same approval process of the code.** - -After one year from this decision, I realised that this was not sufficient: I still didn't like the status of the documentation, -and I had to explain too often to my colleagues at [Renuo](https://renuo.ch) the basic mechanisms of this library. -And all my colleagues are all very smart people!! +After one year from this decision, I realised that this was not sufficient: I still didn't like the status of the documentation, +and I had to explain too often to my colleagues at [Renuo](https://renuo.ch) the basic mechanisms of this library. +And all my colleagues are all very smart people!! So I understood that we were still missing a well structured developer documentation. I hope you'll appreciate this work and you'll now be able to use CanCanCan at it's maximum potential. -I suggest also experienced CanCanCan developers to read it, since you might find out interesting features that have been recently introduced and you might not be aware of, since they were not previously documented. +I suggest also experienced CanCanCan developers to read it, since you might find out interesting features that have been recently introduced and you might not be aware of, since they were not previously documented. Thanks again to all my sponsors, who allowed me to take the time to properly write this documentation, and thanks to all the contributors for your help with this wonderful library :heart:. -If you'd like to sponsor this library, head to https://github.com/sponsors/coorasse. It means a lot. +If you'd like to sponsor this library, head to . It means a lot. Thank you, Alessandro Rodi - -Head to the [Installation](./installation.md) chapter. \ No newline at end of file +Head to the [Installation](./installation.md) chapter. diff --git a/docs/migrating.md b/docs/migrating.md index c6532a078..a669947ba 100644 --- a/docs/migrating.md +++ b/docs/migrating.md @@ -4,16 +4,16 @@ ### Breaking changes -* **Defining abilities without a subject is not allowed anymore.** -For example, `can :dashboard` is not going to be accepted anymore and will raise an exception. -All these kind of rules need to be rethought in terms of `can action, subject`. `can :read, :dashboard` for example. +- **Defining abilities without a subject is not allowed anymore.** + For example, `can :dashboard` is not going to be accepted anymore and will raise an exception. + All these kind of rules need to be rethought in terms of `can action, subject`. `can :read, :dashboard` for example. -* **Eager loading is not automatic.** If you relied on CanCanCan to avoid N+1 queries, this will not be the case anymore. -From now on, all necessary `includes`, `preload` or `eager_load` need to be explicitly written. We strongly suggest to have -`bullet` gem installed to identify your possible N+1 issues. +- **Eager loading is not automatic.** If you relied on CanCanCan to avoid N+1 queries, this will not be the case anymore. + From now on, all necessary `includes`, `preload` or `eager_load` need to be explicitly written. We strongly suggest to have + `bullet` gem installed to identify your possible N+1 issues. -* **Use of distinct.** Uniqueness of the results is guaranteed by using the `distinct` clause in the final query. -This may cause issues with some existing queries when using clauses like `group by` or `order` on associations. -Adding a custom `select` may be necessary in these cases. +- **Use of distinct.** Uniqueness of the results is guaranteed by using the `distinct` clause in the final query. + This may cause issues with some existing queries when using clauses like `group by` or `order` on associations. + Adding a custom `select` may be necessary in these cases. -* **aliases are now merged.** When using the method to merge different Ability files, the aliases are now also merged. This might cause some incompatibility issues. +- **aliases are now merged.** When using the method to merge different Ability files, the aliases are now also merged. This might cause some incompatibility issues. diff --git a/docs/model_adapter.md b/docs/model_adapter.md index 45d4ade15..5ad63e173 100644 --- a/docs/model_adapter.md +++ b/docs/model_adapter.md @@ -1,52 +1,41 @@ # Model Adapter -CanCanCan includes a model adapter system that allows developers to add their own adapters for -handling behaviour depending on the model used. +CanCanCan includes a model adapter system that allows developers to add their own adapters for handling behaviour depending on the model used. CanCanCan provides maintained adapters for the following model types: -* ActiveRecord (native in `cancancan` gem) - * ActiveRecord 4 - * ActiveRecord 5 -* [Mongoid](https://github.com/CanCanCommunity/cancancan-mongoid) +- ActiveRecord (native in `cancancan` gem) + - ActiveRecord 4 + - ActiveRecord 5 +- [Mongoid](https://github.com/CanCanCommunity/cancancan-mongoid) ## Creating a Model Adapter -Due to it's flexible and extendable system of adapters, it is easy to implement a custom adapter -if the currently provided adapters do not suffice. +Due to its flexible and extendable system of adapters, it is easy to implement a custom adapter if the currently provided adapters do not suffice. -To facilitate an easy implementation of a new adapter CanCanCan provides you with an -[Abstract Adapter](https://github.com/CanCanCommunity/cancancan/blob/develop/lib/cancan/model_adapters/abstract_adapter.rb) -you can extend and build upon. This design allows for dynamic adapter handling and a decoupled -handling of information. +To facilitate an easy implementation of a new adapter CanCanCan provides you with an [Abstract Adapter](https://github.com/CanCanCommunity/cancancan/blob/develop/lib/cancan/model_adapters/abstract_adapter.rb) you can extend and build upon. This design allows for dynamic adapter handling and a decoupled handling of information. -## The Abstract Adapter +### The Abstract Adapter -The abstract adapter has multiple methods that one has to overwrite in order to -match the behaviour that is expected. It is used by the system to delegate the handling of fetching entries -base on defined rules and conditions. +The abstract adapter has multiple methods that one has to overwrite in order to match the behaviour that is expected. It is used by the system to delegate the handling of fetching entries base on defined rules and conditions. #### for_class -The ```for_class?``` method is a static method on the abstract adapter that has to be overwritten -in your adapter. +The `for_class?` method is a static method on the abstract adapter that has to be overwritten in your adapter. This method is used to determine whether a model should be passed to the adapter or not. -If your ```for_class?``` implementation returns true, the adapter will be provided -with the model to build and match the rules defined. +If your `for_class?` implementation returns true, the adapter will be provided with the model to build and match the rules defined. Otherwise the adapter will be skipped and the other subclasses of the abstract adapter will be checked. #### database_records -Used to implement the loading of entries from the database, by a developer defined handling of the -given rules for a model. +Used to implement the loading of entries from the database, by a developer-defined handling of the given rules for a model. ### Dependencies -Because cancancan wants to provide an easy method of writing and testing your own adapters -it uses appraisals to test the code against different versions of dependencies. +Because cancancan wants to provide an easy method of writing and testing your own adapters it uses appraisals to test the code against different versions of dependencies. [Appraisals](https://github.com/thoughtbot/appraisal) @@ -55,6 +44,7 @@ Thus you can add your own entry for your gems and dependencies. An example could look like: cancancan/Appraisals + ```ruby appraise 'cancancan_custom_adapter' do @@ -72,9 +62,11 @@ end You would have to replace the dependencies with ones that fit your custom adapter. -After creating your dependency definition run +After creating your dependency definition, run - bundle exec appraisal install +```bash +bundle exec appraisal install +``` to install dependencies for your adapter. @@ -107,34 +99,40 @@ RSpec.describe CanCan::ModelAdapters::MongoidAdapter do end ``` -In this case ```MonoidProject``` is a decendant of ```MogoidDocument```. The implementation of this class -will not be shown as it only acts as an example. + +In this case `MongoidProject` is a decendant of `MongoidDocument`. The implementation of this class will not be shown as it only acts as an example. ### Running tests You can run tests for the project by running - bundle exec appraisal rake +```bash +bundle exec appraisal rake +``` or you can run tests only for your adapter with - bundle exec appraisal adpater_name rake +```bash +bundle exec appraisal adapter_name rake +``` File specific tests can be run with: - bundle exec appraisal adpater_name rspec spec/cancan/model_adapters/adapter_name.rb +```shell +bundle exec appraisal adapter_name rspec spec/cancan/model_adapters/adapter_name.rb +``` **Because we haven't implemented any functionality yet, the tests will fail.** ### The Implementation -First add a line to lib/cancan.rb to include the adapter if a condition is met. In this case -we check if Mongoid is present. +First add a line to `lib/cancan.rb` to include the adapter if a condition is met. In this case we check if Mongoid is present. ```ruby require 'cancan/model_adapters/mongoid_adapter' if defined? Mongoid ``` -And after that create a new adapter in model_adapters: + +And after that, create a new adapter in `model_adapters`: ```ruby module CanCan @@ -145,7 +143,7 @@ module CanCan end def database_records - if @rules.size == 0 + if @rules.size == 0 @model_class.where(:_id => {'$exists' => false, '$type' => 7}) # return no records in Mongoid else @rules.inject(@model_class.all) do |records, rule| @@ -168,22 +166,16 @@ end As mentioned before, there are methods that have to be overwritten in order to pass as a valid adapter. -In this case we overwrite the ```for_class?``` method to validate that the given model is a -descendant of MogoidDocument. The adapter will only be used if ```for_class?``` evalues to true. +In this case we overwrite the `for_class?` method to validate that the given model is a descendant of MongoidDocument. The adapter will only be used if `for_class?` evalues to true. -And in ```database_records``` we define the way data is loaded from the storage device. -This message is used in ```accessible_by```. In this example we fetch all entries for a model that match -a given rule. +And in `database_records` we define the way data is loaded from the storage device. This message is used in `accessible_by`. In this example we fetch all entries for a model that match a given rule. -**If no rules for an object are defined, a query will be run that returns no results** +**If no rules for an object are defined, a query will be run that returns no results.** -If rules are present, we apply each of the rule conditions to them. The rule.base_behavior defines whether -the rule should be additive or subtractive. It will result in false for :cannot and true for :can. +If rules are present, we apply each of the rule conditions to them. The `rule.base_behavior` defines whether the rule should be additive or subtractive. It will result in false for `:cannot` and true for `:can`. -Some model types add additional features to the conditions hash. With Mongoid, for example, -you can do something like :age.gt => 13. -Because the abstract adapter has no knowledge of this, we have to overwrite the provided methods -in the new adapter. +Some model types add additional features to the conditions hash. With Mongoid, for example, you can do something like `:age.gt => 13`. +Because the abstract adapter has no knowledge of this, we have to overwrite the provided methods in the new adapter. ```ruby def self.override_conditions_hash_matching?(subject, conditions) @@ -197,24 +189,22 @@ end ### Additional Examples -Eventhough CanCanCan tries to make the implementation of custom adapters easy and flexible, -it can be hard task. - -Thus you'd probably be best served with inspecting the actual implementation of the activerecord adapter to get -a better overview how a battle tested adapter is structured and implemented. +Eventhough CanCanCan tries to make the implementation of custom adapters easy and flexible, it can be hard task. +Thus you'd probably be best served with inspecting the actual implementation of the `activerecord` adapter to get a better overview how a battle tested adapter is structured and implemented. #### Implementation -* [ActiveRecord Base](../lib/cancan/model_adapters/active_record_adapter.rb) -* [ActiveRecord 4](../lib/cancan/model_adapters/active_record_4_adapter.rb) -* [ActiveRecord 5](../lib/cancan/model_adapters/active_record_5_adapter.rb) +- [ActiveRecord Base](../lib/cancan/model_adapters/active_record_adapter.rb) +- [ActiveRecord 4](../lib/cancan/model_adapters/active_record_4_adapter.rb) +- [ActiveRecord 5](../lib/cancan/model_adapters/active_record_5_adapter.rb) #### Tests / Specs -* [ActiveRecord Base](../spec/cancan/model_adapters/active_record_adapter_spec.rb) -* [ActiveRecord 4](../spec/cancan/model_adapters/active_record_4_adapter_spec.rb) -* [ActiveRecord 5](../spec/cancan/model_adapters/active_record_5_adapter_spec.rb) +- [ActiveRecord Base](../spec/cancan/model_adapters/active_record_adapter_spec.rb) +- [ActiveRecord 4](../spec/cancan/model_adapters/active_record_4_adapter_spec.rb) +- [ActiveRecord 5](../spec/cancan/model_adapters/active_record_5_adapter_spec.rb) **Mondoid, the adapter used in this entry as an example, can be found at:** -* [Mongoid](https://github.com/CanCanCommunity/cancancan-mongoid) + +- [Mongoid](https://github.com/CanCanCommunity/cancancan-mongoid) diff --git a/docs/nested_resources.md b/docs/nested_resources.md index 96b910143..114280a3e 100644 --- a/docs/nested_resources.md +++ b/docs/nested_resources.md @@ -28,23 +28,23 @@ If the name of the association doesn't match the resource name, for instance `ha end ``` -If the resource name (`:project` in this case) does not match the controller then it will be considered a parent resource. You can manually specify parent/child resources using the `parent: false` option. +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: +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 @@ -85,8 +85,7 @@ Here everything will be loaded through the `current_user.projects` association. ## Shallow nesting -The parent resource is required to be present and it will raise an exception if the parent is ever `nil`. -If you want it to be optional (such as with shallow routes), add the `shallow: true` option to the child. +The parent resource is required to be present and it will raise an exception if the parent is ever `nil`. If you want it to be optional (such as with shallow routes), add the `shallow: true` option to the child. ```ruby class TasksController < ApplicationController @@ -156,7 +155,8 @@ can? :read, @project => Task This will use the above `:project` hash conditions and ensure `@project` meets those conditions. ## Has_many through associations -How to load and authorize resources with a has_many :through association? + +How to load and authorize resources with a `has_many :through` association? Given that situation: @@ -195,7 +195,7 @@ in ability.rb can :create, User, groups_users: { group: { CONDITION_ON_GROUP } } ``` -Don't forget the **inverse_of** option, it is the trick to make it work correctly. +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 }`) diff --git a/docs/role_based_authorization.md b/docs/role_based_authorization.md index d9e6e3c89..b83f030df 100644 --- a/docs/role_based_authorization.md +++ b/docs/role_based_authorization.md @@ -23,13 +23,14 @@ rails generate migration add_role_to_users role:string rake db:migrate ``` -In your `users_controller.rb` add `:role` to the list of permitted parameters +In your `users_controller.rb` add `:role` to the list of permitted parameters. ```ruby def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation, :role) end ``` + If you're using ActiveAdmin don't forget to add `role` to the `user.rb` list of parameters as well ```ruby @@ -173,4 +174,4 @@ class Ability end ``` -Here each role is a separate method which is called. You can call one role inside another to define inheritance. This assumes you have a `User#roles` method which returns an array of all roles for that user. \ No newline at end of file +Here each role is a separate method which is called. You can call one role inside another to define inheritance. This assumes you have a `User#roles` method which returns an array of all roles for that user. diff --git a/docs/rules_compression.md b/docs/rules_compression.md index 37f307c78..077decec6 100644 --- a/docs/rules_compression.md +++ b/docs/rules_compression.md @@ -4,8 +4,7 @@ Your rules are optimized automatically at runtime. There are a set of "rules" to A rule without conditions is defined as `catch_all`. - -### A catch_all rule, eliminates all previous rules and all subsequent rules of the same type +## A catch_all rule, eliminates all previous rules and all subsequent rules of the same type ```ruby can :read, Book, author_id: user.id @@ -14,7 +13,9 @@ can :read, Book can :read, Book, id: 1 cannot :read, Book, private: true ``` + becomes + ```ruby can :read, Book cannot :read, Book, private: true @@ -26,7 +27,9 @@ cannot :read, Book, private: true cannot :read, Book can :read, Book, author_id: user.id ``` + becomes + ```ruby can :read, Book, author_id: user.id ``` @@ -36,9 +39,11 @@ can :read, Book, author_id: user.id ```ruby cannot :read, Book, private: true ``` + becomes + ```ruby # nothing ``` -These optimizations allow you to follow the strategy of ["Give Permissions, don't take them"](https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities%3A-Best-Practices#give-permissions-dont-take-them-away) and automatically ignore previous rules when they are not needed. \ No newline at end of file +These optimizations allow you to follow the strategy of ["Give Permissions, don't take them"](https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities%3A-Best-Practices#give-permissions-dont-take-them-away) and automatically ignore previous rules when they are not needed. diff --git a/docs/split_ability.md b/docs/split_ability.md index 9c4016929..88fcea32c 100644 --- a/docs/split_ability.md +++ b/docs/split_ability.md @@ -61,7 +61,7 @@ class BooksController end ``` -Using this technique you have all the power of CanCanCan ability files, that allows you define your permissions with hash of conditions. This means you can check permissions on a single instance of a model, but also retrieve automatically all the instances where you are authorized to perform a certain action. +Using this technique you have all the power of CanCanCan ability files, that allows you define your permissions with hash of conditions. This means you can check permissions on a single instance of a model, but also retrieve automatically all the instances where you are authorized to perform a certain action. You can call `can? :read, @book` but also `Book.accessible_by(current_ability, :read)` that will return all the books you can read. @@ -75,4 +75,4 @@ Abilities files can always be merged together, so if you need two of them in one def current_ability @current_ability ||= ReadAbility.new(current_user).merge(WriteAbility.new(current_user)) end -``` \ No newline at end of file +``` diff --git a/docs/sql_strategies.md b/docs/sql_strategies.md index 736b06705..a013cc4ab 100644 --- a/docs/sql_strategies.md +++ b/docs/sql_strategies.md @@ -41,7 +41,7 @@ can :read, Article, mentions: { user: { name: u.name } } ## :left_join -Note that in the default strategy, we use the `DISTINCT` clasue which might cause performance issues. +Note that in the default strategy, we use the `DISTINCT` clause which might cause performance issues. ```sql SELECT DISTINCT "articles".* diff --git a/docs/testing.md b/docs/testing.md index 7c2d1155d..af1516a9d 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -66,19 +66,18 @@ Scenario: Update Article Here the `rescue_from` block will take effect only in this scenario. - ## Request Testing -If you want to test authorization functionality at the request level one option is to log-in the user who has the appropriate permissions. +If you want to test authorization functionality at the request level, one option is to log-in the user who has the appropriate permissions. ```ruby user = User.create!(admin: true) article = Article.create! login user, as: :user # in devise get article_path(article) -expect(response).to have_http_status(:ok) +expect(response).to have_http_status(:ok) ``` -If you have very complex permissions it can lead to many branching possibilities. If these are all tested in the request layer then it can lead to slow and bloated tests. +If you have very complex permissions it can lead to many branching possibilities. If these are all tested in the request layer then it can lead to slow and bloated tests. Instead we recommend keeping request authorization tests light and testing the authorization functionality more thoroughly in the Ability model through unit tests as shown at the top. From d3ffbb1156221c3e537326761f6120d8693ad077 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Wed, 10 Nov 2021 19:27:22 +0300 Subject: [PATCH 11/53] Fix `main` branch mentions in documentation (#749) --- CONTRIBUTING.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5df12cc21..a111859dc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,7 +17,7 @@ That's it! The more information you give, the more easy it becomes for us to tra ### Adding new Features or Bugfixes CanCanCan uses a [git-flow](http://nvie.com/posts/a-successful-git-branching-model/) development model. -The latest "released" version of CanCanCan, the latest gem version, can always be found on `master`, +The latest "released" version of CanCanCan, the latest gem version, can always be found on `main`, while the next version or nightly is on `develop`. Please make sure you have test coverage for anything you add or fix! diff --git a/README.md b/README.md index f45b9b2d3..e205c4975 100644 --- a/README.md +++ b/README.md @@ -178,4 +178,4 @@ See the [CONTRIBUTING](./CONTRIBUTING.md) for more information. ## Special Thanks Thanks to our Sponsors and to all the [CanCanCan contributors](https://github.com/CanCanCommunity/cancancan/contributors). -See the [CHANGELOG](https://github.com/CanCanCommunity/cancancan/blob/master/CHANGELOG.md) for the full list. +See the [CHANGELOG](https://github.com/CanCanCommunity/cancancan/blob/main/CHANGELOG.md) for the full list. From 53b5f84638743830a04855cfd9ec534f8182f64f Mon Sep 17 00:00:00 2001 From: Will Gaggioli Date: Wed, 10 Nov 2021 10:27:43 -0600 Subject: [PATCH 12/53] Update controller_helpers.md to fix bunk link (#748) --- docs/controller_helpers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/controller_helpers.md b/docs/controller_helpers.md index f9b5f4337..74c209f5c 100644 --- a/docs/controller_helpers.md +++ b/docs/controller_helpers.md @@ -1,6 +1,6 @@ # Controller helpers -As mentioned in the chapter [Define and check abilities](./define_and_check_abilities.md), the `can?` method works at its best in Rails controllers and views. +As mentioned in the chapter [Define and check abilities](./define_check_abilities.md), the `can?` method works at its best in Rails controllers and views. This of course doesn't mean that it cannot be used everywhere. We know already that in order to check if the user is allowed to perform a certain action we need to have a `current_user` method available and we can check the permission with `can? :update, @article`. From ce25fdcaae71cd637fe112f3578f0670cc589fea Mon Sep 17 00:00:00 2001 From: Andrew Yang Date: Thu, 11 Nov 2021 00:28:08 +0800 Subject: [PATCH 13/53] Update ability.rb (#747) --- lib/generators/cancan/ability/templates/ability.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/generators/cancan/ability/templates/ability.rb b/lib/generators/cancan/ability/templates/ability.rb index 6afd442d9..08b2ba170 100644 --- a/lib/generators/cancan/ability/templates/ability.rb +++ b/lib/generators/cancan/ability/templates/ability.rb @@ -27,6 +27,6 @@ def initialize(user) # can :update, Article, published: true # # See the wiki for details: - # https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities + # https://github.com/CanCanCommunity/cancancan/blob/develop/docs/define_check_abilities.md end end From 9ab6807c66650b21593de0c7c388e12901c70cb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Reichel?= Date: Wed, 10 Nov 2021 17:28:32 +0100 Subject: [PATCH 14/53] Fix link (#744) From 81c2892a3bc8a9ba76111e130c3218e816d7505a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20Arag=C3=A3o=20Ferreira=20da=20Silva?= <48594379+gafds@users.noreply.github.com> Date: Wed, 10 Nov 2021 13:29:14 -0300 Subject: [PATCH 15/53] doc visibility (#743) ruby code example it's now formmated --- docs/define_check_abilities.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/define_check_abilities.md b/docs/define_check_abilities.md index 0f5bd4143..968270a47 100644 --- a/docs/define_check_abilities.md +++ b/docs/define_check_abilities.md @@ -133,7 +133,7 @@ For now, what you need to know, is that these four will be your most used, basic One last action is `manage`. This action means that you have full permissions on the subject and you can perform any possible action. Knowing that, we can now rewrite our ability.rb example: -``` +```ruby can :read, Article, published: true return unless user.present? @@ -228,4 +228,4 @@ some_user.can? :update, @article That's everything you know about defining and checking abilities. The DSL is very easy but yet very powerful. There's still a lot you need/should learn about defining abilities. You can [dig deeper](./hash_of_conditions.md) now, but we would suggest to stop, digest it, and proceed on a more Rails-specific topic: [Controller helpers](./controller_helpers.md) where you will learn how to secure your Rails application. -Or you could already take a look at the session about [testing](./testing.md). \ No newline at end of file +Or you could already take a look at the session about [testing](./testing.md). From 016674d585c343cc4366bc3e6ea69197939c2c2b Mon Sep 17 00:00:00 2001 From: mishina2228 <32959831+mishina2228@users.noreply.github.com> Date: Thu, 11 Nov 2021 01:32:54 +0900 Subject: [PATCH 16/53] Minor fix for documents (#740) * Fix links in role_based_authorization.md and also use https * Remove spaces * Use https for links * Update a link to Inherited Resources * Fix links in docs --- docs/README.md | 8 ++++---- docs/changing_defaults.md | 4 ++-- docs/check_abilities_mistakes.md | 2 +- docs/debugging.md | 4 ++-- docs/{Devise.md => devise.md} | 0 docs/fetching_records.md | 3 +-- docs/hash_of_conditions.md | 2 +- docs/inherited_resources.md | 4 ++-- docs/role_based_authorization.md | 8 ++++---- 9 files changed, 17 insertions(+), 18 deletions(-) rename docs/{Devise.md => devise.md} (100%) diff --git a/docs/README.md b/docs/README.md index 43583145c..9fbbeb8c5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,12 +6,12 @@ It will advance chapter by chapter and go more and more into details, advanced u We will start by introducing basic concepts and features, and then dig deeper into configurations and implementation details in later chapters. -You can skip the [Introduction](./introduction.md) where there's just some history and blablabla and go directly to [Installation](./installation) to start fast :rocket:. +You can skip the [Introduction](./introduction.md) where there's just some history and blablabla and go directly to [Installation](./installation.md) to start fast :rocket:. ## Summary -1. [Introduction](./introduction.md) -1. [Installation](./installation.md) +1. [Introduction](./introduction.md) +1. [Installation](./installation.md) 1. [Define and check abilities](./define_check_abilities.md) 1. [Controller helpers](./controller_helpers.md) 1. [Fetching records](./fetching_records.md) @@ -41,4 +41,4 @@ In these topics, you will learn some best practices, but also how to solve speci 1. [Rules compression](./rules_compression.md) 1. [Inherited Resources](./inherited_resources.md) 1. [Devise](./devise.md) -1. [FriendlyId](./friendly_id.md) \ No newline at end of file +1. [FriendlyId](./friendly_id.md) diff --git a/docs/changing_defaults.md b/docs/changing_defaults.md index 7db84fd2f..15dc95e1a 100644 --- a/docs/changing_defaults.md +++ b/docs/changing_defaults.md @@ -244,7 +244,7 @@ You can create differing authorization rules that depend on the controller names In this case, just override the `current_ability` method in `ApplicationController` to include the controller namespace, and create an `Ability` class that knows what to do with it. -``` ruby +```ruby class Admin::WidgetsController < ActionController::Base #... @@ -276,7 +276,7 @@ end Another way to achieve the same is to use a completely different Ability class in this controller: -``` ruby +```ruby class Admin::WidgetsController < ActionController::Base #... diff --git a/docs/check_abilities_mistakes.md b/docs/check_abilities_mistakes.md index 7b16f7787..9df6c7734 100644 --- a/docs/check_abilities_mistakes.md +++ b/docs/check_abilities_mistakes.md @@ -43,6 +43,6 @@ That is why passing a class to `can?` will return `true`. The code answering the question "can the user update all the articles?" would be something like: -``` ruby +```ruby Article.accessible_by(current_ability).count == Article.count ``` diff --git a/docs/debugging.md b/docs/debugging.md index 772016dc1..75ccb8f5f 100644 --- a/docs/debugging.md +++ b/docs/debugging.md @@ -52,5 +52,5 @@ end ## Issue Tracker -If you are still unable to resolve the issue, [open a question on Stackoverflow](http://stackoverflow.com/questions/ask?tags=cancancan) with tag -[cancancan](http://stackoverflow.com/questions/tagged/cancancan). \ No newline at end of file +If you are still unable to resolve the issue, [open a question on Stackoverflow](https://stackoverflow.com/questions/ask?tags=cancancan) with tag +[cancancan](https://stackoverflow.com/questions/tagged/cancancan). diff --git a/docs/Devise.md b/docs/devise.md similarity index 100% rename from docs/Devise.md rename to docs/devise.md diff --git a/docs/fetching_records.md b/docs/fetching_records.md index 886b35bda..1ca960b14 100644 --- a/docs/fetching_records.md +++ b/docs/fetching_records.md @@ -68,11 +68,10 @@ WHERE (user_id = 1) OR (not (self_managed = 'true') AND (public = 'true')) The generation of the SQL query is a very complex task and probably the most powerful feature of CanCanCan. Even if the default behaviour will suffice at the beginning, larger databases or more complex rules, might lead to very complex SQL queries. This might result in a slow fetching of records. This is why is possible to use different strategies to generate the SQL. -You will see that in one of the last chapters: [SQL strategies](./sql_strategies.ms) +You will see that in one of the last chapters: [SQL strategies](./sql_strategies.md) # Blocks We haven't spoken about block abilities yet, but the SQL generation will not be possible if you have even a single rule that is defined using just a block. You can define SQL fragments in addition to block to fix that. But we'll see that in the [Define Abilities with Blocks](./define_abilities_with_blocks.md) chapter. ``` - diff --git a/docs/hash_of_conditions.md b/docs/hash_of_conditions.md index 0c6bcf6c4..5eab5bf05 100644 --- a/docs/hash_of_conditions.md +++ b/docs/hash_of_conditions.md @@ -2,7 +2,7 @@ Let's start our journey into the abilities definition by explaining the CanCanCan Hash of conditions mechanism. -In the chapter [Define and Check Abilities](./define_and_check_abilities.md) we defined +In the chapter [Define and Check Abilities](./define_check_abilities.md) we defined ```ruby can :update, @article, user: user diff --git a/docs/inherited_resources.md b/docs/inherited_resources.md index fa11e6296..85f9fb5ed 100644 --- a/docs/inherited_resources.md +++ b/docs/inherited_resources.md @@ -3,7 +3,7 @@ **This guide is for cancancan < 2.0 only. If you want to use Inherited Resources and cancancan 2.0 please check for extensions like https://github.com/TylerRick/cancan-inherited_resources** -The `load_and_authorize_resource` call will automatically detect if you are using [Inherited Resources](http://github.com/josevalim/inherited_resources) and load the resource through that. The `load` part in CanCan is still necessary since Inherited Resources does lazy loading. This will also ensure the behavior is identical to normal loading. +The `load_and_authorize_resource` call will automatically detect if you are using [Inherited Resources](https://github.com/activeadmin/inherited_resources) and load the resource through that. The `load` part in CanCan is still necessary since Inherited Resources does lazy loading. This will also ensure the behavior is identical to normal loading. ```ruby class ProjectsController < InheritedResources::Base @@ -41,4 +41,4 @@ class TasksController < InheritedResources::Base ... load_and_authorize_resource :task, :through => :project_id end -``` \ No newline at end of file +``` diff --git a/docs/role_based_authorization.md b/docs/role_based_authorization.md index 49fc55f6e..24e00dc65 100644 --- a/docs/role_based_authorization.md +++ b/docs/role_based_authorization.md @@ -54,7 +54,7 @@ can :manage, :all if user.role == "admin" ## Many roles per user -It is possible to assign multiple roles to a user and store it into a single integer column using a [[bitmask|http://en.wikipedia.org/wiki/Mask_(computing)]]. First add a `roles_mask` integer column to your `users` table. +It is possible to assign multiple roles to a user and store it into a single integer column using a [bitmask](https://en.wikipedia.org/wiki/Mask_(computing)). First add a `roles_mask` integer column to your `users` table. ```bash rails generate migration add_roles_mask_to_users roles_mask:integer @@ -110,7 +110,7 @@ can :manage, :all if user.has_role? :admin See [[Custom Actions]] for a way to restrict which users can assign roles to other users. -This functionality has also been extracted into a little gem called [[role_model|http://rubygems.org/gems/role_model]] ([[code & howto|http://github.com/martinrehfeld/role_model]]). +This functionality has also been extracted into a little gem called [role_model](https://rubygems.org/gems/role_model) ([code & howto](https://github.com/martinrehfeld/role_model)). If you do not like this bitmask solution, see [[Separate Role Model]] for an alternative way to handle this. @@ -144,7 +144,7 @@ end Here a superadmin will be able to manage all three classes but a moderator can only manage the one. Of course you can change the role logic to fit your needs. You can add complex logic so certain roles only inherit from others. And if a given user can have multiple roles you can decide whether the lowest role takes priority or the highest one does. Or use other attributes on the user model such as a "banned", "activated", or "admin" column. -This functionality has been extracted into a gem called [[canard|http://rubygems.org/gems/canard]] ([[code & howto|http://github.com/james2m/canard]]). +This functionality has been extracted into a gem called [canard](https://rubygems.org/gems/canard) ([code & howto](https://github.com/james2m/canard)). ## Alternative Role Inheritance @@ -174,4 +174,4 @@ class Ability end ``` -Here each role is a separate method which is called. You can call one role inside another to define inheritance. This assumes you have a `User#roles` method which returns an array of all roles for that user. \ No newline at end of file +Here each role is a separate method which is called. You can call one role inside another to define inheritance. This assumes you have a `User#roles` method which returns an array of all roles for that user. From 7bdf9ec5bf2741728841c3b48edbf9e5977dfb4d Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Thu, 11 Nov 2021 15:04:53 +0100 Subject: [PATCH 17/53] Extract strategies in separate files (#754) Co-authored-by: kaspernj Co-authored-by: kaspernj --- lib/cancan.rb | 3 ++ lib/cancan/model_adapters/abstract_adapter.rb | 2 + .../model_adapters/active_record_5_adapter.rb | 14 +++---- .../model_adapters/active_record_adapter.rb | 2 + lib/cancan/model_adapters/strategies/base.rb | 40 +++++++++++++++++++ .../model_adapters/strategies/left_join.rb | 11 +++++ .../model_adapters/strategies/subquery.rb | 18 +++++++++ 7 files changed, 81 insertions(+), 9 deletions(-) create mode 100644 lib/cancan/model_adapters/strategies/base.rb create mode 100644 lib/cancan/model_adapters/strategies/left_join.rb create mode 100644 lib/cancan/model_adapters/strategies/subquery.rb diff --git a/lib/cancan.rb b/lib/cancan.rb index 80d7a24dc..76d72af77 100644 --- a/lib/cancan.rb +++ b/lib/cancan.rb @@ -21,4 +21,7 @@ require 'cancan/model_adapters/active_record_adapter' require 'cancan/model_adapters/active_record_4_adapter' require 'cancan/model_adapters/active_record_5_adapter' + require 'cancan/model_adapters/strategies/base' + require 'cancan/model_adapters/strategies/left_join' + require 'cancan/model_adapters/strategies/subquery' end diff --git a/lib/cancan/model_adapters/abstract_adapter.rb b/lib/cancan/model_adapters/abstract_adapter.rb index 4e0e51a25..8a68382bd 100644 --- a/lib/cancan/model_adapters/abstract_adapter.rb +++ b/lib/cancan/model_adapters/abstract_adapter.rb @@ -3,6 +3,8 @@ module CanCan module ModelAdapters class AbstractAdapter + attr_reader :model_class + def self.inherited(subclass) @subclasses ||= [] @subclasses.insert(0, subclass) diff --git a/lib/cancan/model_adapters/active_record_5_adapter.rb b/lib/cancan/model_adapters/active_record_5_adapter.rb index 072ed7f50..68f1142e5 100644 --- a/lib/cancan/model_adapters/active_record_5_adapter.rb +++ b/lib/cancan/model_adapters/active_record_5_adapter.rb @@ -22,16 +22,12 @@ def self.matches_condition?(subject, name, value) private def build_joins_relation(relation, *where_conditions) - case CanCan.accessible_by_strategy - when :subquery - inner = @model_class.unscoped do - @model_class.left_joins(joins).where(*where_conditions) - end - @model_class.where(@model_class.primary_key => inner) + strategy_class.new(adapter: self, relation: relation, where_conditions: where_conditions).execute! + end - when :left_join - relation.left_joins(joins).distinct - end + def strategy_class + strategy_class_name = CanCan.accessible_by_strategy.to_s.camelize + CanCan::ModelAdapters::Strategies.const_get(strategy_class_name) end def sanitize_sql(conditions) diff --git a/lib/cancan/model_adapters/active_record_adapter.rb b/lib/cancan/model_adapters/active_record_adapter.rb index 7bd969c92..741570838 100644 --- a/lib/cancan/model_adapters/active_record_adapter.rb +++ b/lib/cancan/model_adapters/active_record_adapter.rb @@ -11,6 +11,8 @@ def self.version_lower?(version) Gem::Version.new(ActiveRecord.version).release < Gem::Version.new(version) end + attr_reader :compressed_rules + def initialize(model_class, rules) super @compressed_rules = RulesCompressor.new(@rules.reverse).rules_collapsed.reverse diff --git a/lib/cancan/model_adapters/strategies/base.rb b/lib/cancan/model_adapters/strategies/base.rb new file mode 100644 index 000000000..005b3dbae --- /dev/null +++ b/lib/cancan/model_adapters/strategies/base.rb @@ -0,0 +1,40 @@ +module CanCan + module ModelAdapters + class Strategies + class Base + attr_reader :adapter, :relation, :where_conditions + + delegate( + :compressed_rules, + :extract_multiple_conditions, + :joins, + :model_class, + :quoted_primary_key, + :quoted_aliased_table_name, + :quoted_table_name, + to: :adapter + ) + delegate :connection, :quoted_primary_key, to: :model_class + delegate :quote_table_name, to: :connection + + def initialize(adapter:, relation:, where_conditions:) + @adapter = adapter + @relation = relation + @where_conditions = where_conditions + end + + def aliased_table_name + @aliased_table_name ||= "#{model_class.table_name}_alias" + end + + def quoted_aliased_table_name + @quoted_aliased_table_name ||= quote_table_name(aliased_table_name) + end + + def quoted_table_name + @quoted_table_name ||= quote_table_name(model_class.table_name) + end + end + end + end +end diff --git a/lib/cancan/model_adapters/strategies/left_join.rb b/lib/cancan/model_adapters/strategies/left_join.rb new file mode 100644 index 000000000..3c118963e --- /dev/null +++ b/lib/cancan/model_adapters/strategies/left_join.rb @@ -0,0 +1,11 @@ +module CanCan + module ModelAdapters + class Strategies + class LeftJoin < Base + def execute! + relation.left_joins(joins).distinct + end + end + end + end +end diff --git a/lib/cancan/model_adapters/strategies/subquery.rb b/lib/cancan/model_adapters/strategies/subquery.rb new file mode 100644 index 000000000..cc7019aa4 --- /dev/null +++ b/lib/cancan/model_adapters/strategies/subquery.rb @@ -0,0 +1,18 @@ +module CanCan + module ModelAdapters + class Strategies + class Subquery < Base + def execute! + build_joins_relation_subquery(where_conditions) + end + + def build_joins_relation_subquery(where_conditions) + inner = model_class.unscoped do + model_class.left_joins(joins).where(*where_conditions) + end + model_class.where(model_class.primary_key => inner) + end + end + end + end +end From 83d53a5a6453e6fc9f18c5bd678d6cf76a6da4e6 Mon Sep 17 00:00:00 2001 From: Martin Meyerhoff Date: Thu, 11 Nov 2021 15:07:23 +0100 Subject: [PATCH 18/53] Use Rails' main branch instead of master branch (#718) They've renamed their main branch to main. --- .github/workflows/test.yml | 10 +++++----- Appraisals | 10 ++++++---- ...record_master.gemfile => activerecord_main.gemfile} | 8 +++++--- 3 files changed, 16 insertions(+), 12 deletions(-) rename gemfiles/{activerecord_master.gemfile => activerecord_main.gemfile} (58%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a0d20db60..422809562 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,27 +15,27 @@ jobs: fail-fast: false matrix: 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'] + 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_main.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_4.2.0.gemfile' ruby: 'truffleruby' # TruffleRuby 21.0 targets Ruby 2.7, same as above - - gemfile: 'gemfiles/activerecord_master.gemfile' + - gemfile: 'gemfiles/activerecord_main.gemfile' ruby: '2.6' # rails 7+ requires ruby 3.0+ - - gemfile: 'gemfiles/activerecord_master.gemfile' + - gemfile: 'gemfiles/activerecord_main.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' ruby: '2.4' # rails 6+ requires ruby 2.5+ - - gemfile: 'gemfiles/activerecord_master.gemfile' + - gemfile: 'gemfiles/activerecord_main.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' + - gemfile: 'gemfiles/activerecord_main.gemfile' ruby: 'jruby' # this *should* work. it seems like there's an issue with rails 6 on jruby. env: diff --git a/Appraisals b/Appraisals index f7b89691a..af06018ba 100644 --- a/Appraisals +++ b/Appraisals @@ -101,10 +101,12 @@ appraise 'activerecord_6.1.0' do 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' +appraise 'activerecord_main' do + git 'https://github.com/rails/rails', branch: 'main' do + gem 'actionpack', require: 'action_pack' + gem 'activerecord', require: 'active_record' + gem 'activesupport', require: 'active_support/all' + end platforms :jruby do gem 'activerecord-jdbcsqlite3-adapter' diff --git a/gemfiles/activerecord_master.gemfile b/gemfiles/activerecord_main.gemfile similarity index 58% rename from gemfiles/activerecord_master.gemfile rename to gemfiles/activerecord_main.gemfile index 8328cadc1..46f55df29 100644 --- a/gemfiles/activerecord_master.gemfile +++ b/gemfiles/activerecord_main.gemfile @@ -2,9 +2,11 @@ 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" +git "https://github.com/rails/rails", branch: "main" do + gem "actionpack", require: "action_pack" + gem "activerecord", require: "active_record" + gem "activesupport", require: "active_support/all" +end platforms :jruby do gem "activerecord-jdbcsqlite3-adapter" From ee212c9bf7fba7656749728354d6e620301065c7 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Thu, 11 Nov 2021 15:09:36 +0100 Subject: [PATCH 19/53] Link to documentation README file ... instead of folder --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e205c4975..44b26a7cc 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![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) -[Developer guide](./docs) | +[Developer guide](./docs/README.md) | [RDocs](http://rdoc.info/projects/CanCanCommunity/cancancan) | [Screencast 1](http://railscasts.com/episodes/192-authorization-with-cancan) | [Screencast 2](https://www.youtube.com/watch?v=cTYu-OjUgDw) @@ -65,7 +65,7 @@ of models automatically and reduce duplicated code. Do you want to sponsor CanCanCan and show your logo here? Check our [Sponsors Page](https://github.com/sponsors/coorasse). -Head to our complete [Developer Guide](./docs) to learn how to use CanCanCan in details. +Head to our complete [Developer Guide](./docs/README.md) to learn how to use CanCanCan in details. ## Installation @@ -151,12 +151,12 @@ end ## Documentation -Head to our complete [Developer Guide](./docs) to learn how to use CanCanCan in details. +Head to our complete [Developer Guide](./docs/README.md) to learn how to use CanCanCan in details. ## Questions? If you have any question or doubt regarding CanCanCan which you cannot find the solution to in the -[documentation](./docs), please +[documentation](./docs/README.md), please [open a question on Stackoverflow](http://stackoverflow.com/questions/ask?tags=cancancan) with tag [cancancan](http://stackoverflow.com/questions/tagged/cancancan) From 80bf4192edafe9b88321a9f63a97c463c325425a Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 11 Nov 2021 17:27:01 +0300 Subject: [PATCH 20/53] Update accessible_attributes.md (#753) Previous described syntax doesn't work. --- docs/accessible_attributes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/accessible_attributes.md b/docs/accessible_attributes.md index d97273fda..cd0604f2b 100644 --- a/docs/accessible_attributes.md +++ b/docs/accessible_attributes.md @@ -5,7 +5,7 @@ CanCanCan gives you the possibility to define actions on single instances' attri Given you want users to only read a user first name and last name you can define: ```ruby -can :read, User, :first_name, :last_name +can :read, User, [:first_name, :last_name] ``` and check it with: @@ -34,4 +34,4 @@ or in Strong Parameters: params .require(:book) .permit(ability.permitted_attributes(:read, @book)) -``` \ No newline at end of file +``` From 12b8dad8c826400b7826433877a4f0f024d7adb3 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Thu, 11 Nov 2021 17:27:55 +0300 Subject: [PATCH 21/53] Add EditorConfig file (#750) More info here: https://editorconfig.org/ Also fix some whitespaces. --- .editorconfig | 10 ++++++++++ CONTRIBUTING.md | 4 ++-- README.md | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..b372c2262 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +max_line_length = 120 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a111859dc..7252e2f31 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,8 +2,8 @@ ### Reporting an Issue -1. If you have any questions about CanCanCan, search the [Wiki](https://github.com/cancancommunity/cancancan/wiki) or -use [Stack Overflow](http://stackoverflow.com/questions/tagged/cancancan). +1. If you have any questions about CanCanCan, search the [Wiki](https://github.com/cancancommunity/cancancan/wiki) or +use [Stack Overflow](http://stackoverflow.com/questions/tagged/cancancan). Do not post questions here. 1. If you find a security bug, **DO NOT** submit an issue here. Please send an e-mail to the [current maintainer](https://github.com/coorasse) instead. diff --git a/README.md b/README.md index 44b26a7cc..25434a766 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ class Ability can :read, Post, user: user return unless user.admin? # additional permissions for administrators - can :read, Post + can :read, Post end end ``` From 19d50fdfb91e4d27a96b2cc18f454ee80aa75b3b Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Tue, 30 Nov 2021 22:14:55 +0100 Subject: [PATCH 22/53] Add Honeybadger logo --- README.md | 6 ++++++ logo/honeybadger.svg | 28 ++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 logo/honeybadger.svg diff --git a/README.md b/README.md index 25434a766..9f393d23f 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,12 @@ of models automatically and reduce duplicated code.

+
+ + Honeybadger + +
+
Do you want to sponsor CanCanCan and show your logo here? Check our [Sponsors Page](https://github.com/sponsors/coorasse). diff --git a/logo/honeybadger.svg b/logo/honeybadger.svg new file mode 100644 index 000000000..7d0531674 --- /dev/null +++ b/logo/honeybadger.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 156a03f3003a7b6ec11976760ff52fda4be6bcc4 Mon Sep 17 00:00:00 2001 From: Judah Meek Date: Mon, 14 Mar 2022 12:10:26 -0500 Subject: [PATCH 23/53] Update link from wiki to docs (#769) --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d4b7312d..0d7145b6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,7 +39,7 @@ ## 3.0.0 -Please read the [guide on migrating from CanCanCan 2.x to 3.0](https://github.com/CanCanCommunity/cancancan/wiki/Migrating-from-CanCanCan-2.x-to-3.0) +Please read the [guide on migrating from CanCanCan 2.x to 3.0](https://github.com/CanCanCommunity/cancancan/blob/develop/docs/migrating.md#from-2x-to-3x) * [#560](https://github.com/CanCanCommunity/cancancan/pull/560): Add support for Rails 6.0. ([@coorasse][]) * [#489](https://github.com/CanCanCommunity/cancancan/pull/489): Drop support for actions without a subject. ([@andrew-aladev][]) From 2ca5bef7410e602455d6bcf9247e9bc2539a6dff Mon Sep 17 00:00:00 2001 From: Ri Caragol Date: Tue, 15 Mar 2022 07:33:05 -0400 Subject: [PATCH 24/53] Fix small typo on file. (#765) --- docs/sql_strategies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sql_strategies.md b/docs/sql_strategies.md index a013cc4ab..578656841 100644 --- a/docs/sql_strategies.md +++ b/docs/sql_strategies.md @@ -8,7 +8,7 @@ In the history of CanCanCan we had many issues with different versions of the ge That's why in the latest versions of CanCanCan, you are given the possibility to customize how the SQL is generated and choose from multiple options. -You can customize the SQL startegy globally with: +You can customize the SQL strategy globally with: ```ruby # config/initializers/cancancan.rb From cc84bd71bc89511bc1667b25203350df8a124667 Mon Sep 17 00:00:00 2001 From: mishina <32959831+mishina2228@users.noreply.github.com> Date: Tue, 15 Mar 2022 20:33:49 +0900 Subject: [PATCH 25/53] Separate Test and Lint (#763) --- .github/workflows/test.yml | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 422809562..06f76a879 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ env: jobs: tests: - name: Test & lint + name: Test runs-on: ubuntu-latest strategy: @@ -72,11 +72,27 @@ jobs: ruby-version: ${{ matrix.ruby }} bundler-cache: true - - name: Run linter - run: bundle exec rubocop - - name: Run tests on sqlite run: DB=sqlite bundle exec rake - name: Run tests on postgres run: DB=postgres bundle exec rake + + lint: + name: Lint + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + fetch-depth: '20' + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7 + bundler-cache: true + + - name: Run linter + run: bundle exec rubocop From b8e799fe5e2f39b377a371b9133210e6f6a18edf Mon Sep 17 00:00:00 2001 From: Achilleas Date: Tue, 15 Mar 2022 12:34:05 +0100 Subject: [PATCH 26/53] Update README.md (#759) We have a new link to our job page. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9f393d23f..942fc038c 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ of models automatically and reduce duplicated code.


- + Goboony
From 52f19fb28e1bf3c7e7c4636f2e061c732e0e14ba Mon Sep 17 00:00:00 2001 From: Chad Lillquist Date: Tue, 15 Mar 2022 05:34:27 -0600 Subject: [PATCH 27/53] update sponsor logo to reflect company name change (#758) Co-authored-by: Chad --- README.md | 4 ++-- logo/in_cloud_counsel.png | Bin 21364 -> 0 bytes logo/ontra.png | Bin 0 -> 19313 bytes 3 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 logo/in_cloud_counsel.png create mode 100644 logo/ontra.png diff --git a/README.md b/README.md index 942fc038c..0b2457e51 100644 --- a/README.md +++ b/README.md @@ -56,8 +56,8 @@ of models automatically and reduce duplicated code.


- - InCloudCounsel + + Ontra

diff --git a/logo/in_cloud_counsel.png b/logo/in_cloud_counsel.png deleted file mode 100644 index 33ad83b41c5a83f46ecf740425b33a363db8bcf0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21364 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 QCjbBd07*qoM6N<$f>XN8IRF3v diff --git a/logo/ontra.png b/logo/ontra.png new file mode 100644 index 0000000000000000000000000000000000000000..e7f243b70958b3a194d25795800c7b72c39da5b0 GIT binary patch literal 19313 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 literal 0 HcmV?d00001 From 56028c0522b077ceb7ea84e3b71776baeee2538d Mon Sep 17 00:00:00 2001 From: mishina <32959831+mishina2228@users.noreply.github.com> Date: Tue, 15 Mar 2022 20:35:35 +0900 Subject: [PATCH 28/53] Fix some typos (#762) --- CHANGELOG.md | 4 ++-- docs/define_abilities_best_practices.md | 2 +- docs/define_check_abilities.md | 4 ++-- docs/model_adapter.md | 4 ++-- docs/split_ability.md | 2 +- lib/cancan/conditions_matcher.rb | 2 +- lib/cancan/controller_resource.rb | 2 +- lib/cancan/model_adapters/conditions_extractor.rb | 8 ++++---- lib/cancan/model_adapters/conditions_normalizer.rb | 2 +- spec/cancan/controller_additions_spec.rb | 2 +- spec/cancan/controller_resource_spec.rb | 14 +++++++------- spec/cancan/exceptions_spec.rb | 2 +- spec/cancan/matchers_spec.rb | 2 +- .../accessible_by_integration_spec.rb | 2 +- .../model_adapters/active_record_5_adapter_spec.rb | 2 +- .../model_adapters/active_record_adapter_spec.rb | 4 ++-- .../model_adapters/has_and_belongs_to_many_spec.rb | 4 ++-- 17 files changed, 31 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d7145b6a..9fb985480 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -166,7 +166,7 @@ Please read the [guide on migrating from CanCanCan 2.x to 3.0](https://github.co ## 1.9.0 (July 20th, 2014) -* Fix cancancan#59 - Parameters are automatically detected and santitized for all actions, not just create and update ([@bryanrite][]). +* Fix cancancan#59 - Parameters are automatically detected and sanitized for all actions, not just create and update ([@bryanrite][]). * Fix cancancan#97, 72, 40, 39, 26 - Support Active Record 4 properly with references on nested permissions (scpike, tdg5, Crystark). @@ -199,7 +199,7 @@ Please read the [guide on migrating from CanCanCan 2.x to 3.0](https://github.co * Feature cancancan#3 - Permit "can?" check multiple subjects (cefigueiredo). -* Feature cancancan#29 - Add ability to use a String that will get instance_eval'd or a Proc that will get called as the parameter method option for strong_parameter santization (svoop). +* Feature cancancan#29 - Add ability to use a String that will get instance_eval'd or a Proc that will get called as the parameter method option for strong_parameter sanitization (svoop). * Feature cancancan#48 - Define a CanCanCan module. Even though it is not used, it is standard practice to define the module, and helpful for determining between CanCanCan and CanCan for external libraries. diff --git a/docs/define_abilities_best_practices.md b/docs/define_abilities_best_practices.md index 86d266dc5..eeed9a954 100644 --- a/docs/define_abilities_best_practices.md +++ b/docs/define_abilities_best_practices.md @@ -27,7 +27,7 @@ can :read, Article, is_published: true By using hashes instead of blocks for all actions, you won't have to worry about translating blocks used for member controller actions (`:create`, `:destroy`, `:update`) to equivalent blocks for collection actions (`:index`, `:show`)—which require hashes anyway! -### Hash conditions are OR'd in SQL, giving you maximum flexibilty. +### Hash conditions are OR'd in SQL, giving you maximum flexibility. Every time you define an ability with `can`, each `can` chains together with OR in the final SQL query for that model. diff --git a/docs/define_check_abilities.md b/docs/define_check_abilities.md index afd80e932..476b9e149 100644 --- a/docs/define_check_abilities.md +++ b/docs/define_check_abilities.md @@ -160,7 +160,7 @@ Now that we learned about actions and their aliases let's see what we can do wit The subject of an action is usually a Ruby class. Most of the times you want to define your permissions on specific classes, but this is not your only option. You can actually use any subject, and one of the most common cases is to just use a symbol. -An admin dashboard could be protected by definining: +An admin dashboard could be protected by defining: ```ruby can :read, :admin_dashboard @@ -188,7 +188,7 @@ and give all possible permissions to the administrator. Note that the code above allows the administrator to also `:read, :admin_dashboard`. `:manage` means literally **any** action, not only CRUD ones. -> You **must and should** always check for specific permisssions, but you don't need to define all of them if not needed. +> You **must and should** always check for specific permissions, but you don't need to define all of them if not needed. If at some point you have a new page reserved to the administrators, where they can translate articles, you should check for `can? :translate, @article`, but you don't need to define the ability, since the administrators can already do any action. It will be easy in the future to give the possibility for authors to translate their own articles by changing your permissions file: diff --git a/docs/model_adapter.md b/docs/model_adapter.md index 5ad63e173..0ebd36afc 100644 --- a/docs/model_adapter.md +++ b/docs/model_adapter.md @@ -100,7 +100,7 @@ RSpec.describe CanCan::ModelAdapters::MongoidAdapter do end ``` -In this case `MongoidProject` is a decendant of `MongoidDocument`. The implementation of this class will not be shown as it only acts as an example. +In this case `MongoidProject` is a descendant of `MongoidDocument`. The implementation of this class will not be shown as it only acts as an example. ### Running tests @@ -205,6 +205,6 @@ Thus you'd probably be best served with inspecting the actual implementation of - [ActiveRecord 4](../spec/cancan/model_adapters/active_record_4_adapter_spec.rb) - [ActiveRecord 5](../spec/cancan/model_adapters/active_record_5_adapter_spec.rb) -**Mondoid, the adapter used in this entry as an example, can be found at:** +**Mongoid, the adapter used in this entry as an example, can be found at:** - [Mongoid](https://github.com/CanCanCommunity/cancancan-mongoid) diff --git a/docs/split_ability.md b/docs/split_ability.md index 88fcea32c..5fb0b7b39 100644 --- a/docs/split_ability.md +++ b/docs/split_ability.md @@ -19,7 +19,7 @@ class Ability end ``` -This is, of course, not too complicated, and in an real world application we would not split this file, but for didactic reasons we want to split this file “per-model”. +This is, of course, not too complicated, and in a real world application we would not split this file, but for didactic reasons we want to split this file “per-model”. We suggest to have an app/abilities folder and create a separate file for each model (exactly as you would do with Pundit). diff --git a/lib/cancan/conditions_matcher.rb b/lib/cancan/conditions_matcher.rb index e07bfc13b..888ff9af4 100644 --- a/lib/cancan/conditions_matcher.rb +++ b/lib/cancan/conditions_matcher.rb @@ -38,7 +38,7 @@ def nested_subject_matches_conditions?(subject_hash) end # Checks if the given subject matches the given conditions hash. - # This behavior can be overriden by a model adapter by defining two class methods: + # This behavior can be overridden by a model adapter by defining two class methods: # override_matching_for_conditions?(subject, conditions) and # matches_conditions_hash?(subject, conditions) def matches_conditions_hash?(subject, conditions = @conditions) diff --git a/lib/cancan/controller_resource.rb b/lib/cancan/controller_resource.rb index d9f753a0e..c99fd2dcc 100644 --- a/lib/cancan/controller_resource.rb +++ b/lib/cancan/controller_resource.rb @@ -54,7 +54,7 @@ def skip?(behavior) protected - # Returns the class used for this resource. This can be overriden by the :class option. + # Returns the class used for this resource. This can be overridden by the :class option. # If +false+ is passed in it will use the resource name as a symbol in which case it should # only be used for authorization, not loading since there's no class to load through. def resource_class diff --git a/lib/cancan/model_adapters/conditions_extractor.rb b/lib/cancan/model_adapters/conditions_extractor.rb index 47d66b5d9..05b418c3a 100644 --- a/lib/cancan/model_adapters/conditions_extractor.rb +++ b/lib/cancan/model_adapters/conditions_extractor.rb @@ -3,7 +3,7 @@ # this class is responsible of converting the hash of conditions # in "where conditions" to generate the sql query # it consists of a names_cache that helps calculating the next name given to the association -# it tries to reflect the bahavior of ActiveRecord when generating aliases for tables. +# it tries to reflect the behavior of ActiveRecord when generating aliases for tables. module CanCan module ModelAdapters class ConditionsExtractor @@ -50,18 +50,18 @@ def calculate_nested(model_class, result_hash, relation_name, value, path_to_key def generate_table_alias(model_class, relation_name, path_to_key) table_alias = model_class.reflect_on_association(relation_name).table_name.to_sym - if alredy_used?(table_alias, relation_name, path_to_key) + if already_used?(table_alias, relation_name, path_to_key) table_alias = "#{relation_name.to_s.pluralize}_#{model_class.table_name}".to_sym index = 1 - while alredy_used?(table_alias, relation_name, path_to_key) + while already_used?(table_alias, relation_name, path_to_key) table_alias = "#{table_alias}_#{index += 1}".to_sym end end add_to_cache(table_alias, relation_name, path_to_key) end - def alredy_used?(table_alias, relation_name, path_to_key) + def already_used?(table_alias, relation_name, path_to_key) @names_cache[table_alias].try(:exclude?, "#{path_to_key}_#{relation_name}") end diff --git a/lib/cancan/model_adapters/conditions_normalizer.rb b/lib/cancan/model_adapters/conditions_normalizer.rb index e015d444b..2b12a80e2 100644 --- a/lib/cancan/model_adapters/conditions_normalizer.rb +++ b/lib/cancan/model_adapters/conditions_normalizer.rb @@ -1,6 +1,6 @@ # this class is responsible of normalizing the hash of conditions # by exploding has_many through associations -# when a condition is defined with an has_many thorugh association this is exploded in all its parts +# when a condition is defined with an has_many through association this is exploded in all its parts # TODO: it could identify STI and normalize it module CanCan module ModelAdapters diff --git a/spec/cancan/controller_additions_spec.rb b/spec/cancan/controller_additions_spec.rb index 98b2e6643..013cbbd55 100644 --- a/spec/cancan/controller_additions_spec.rb +++ b/spec/cancan/controller_additions_spec.rb @@ -139,7 +139,7 @@ expect(@controller_class.cancan_skipper[:load][:article]).to eq({}) end - it 'skip_load_and_authore_resource adds itself to the cancan skipper with given model name and options' do + it 'skip_load_and_authorize_resource adds itself to the cancan skipper with given model name and options' do @controller_class.skip_load_and_authorize_resource(:project, only: %i[index show]) expect(@controller_class.cancan_skipper[:load][:project]).to eq(only: %i[index show]) expect(@controller_class.cancan_skipper[:authorize][:project]).to eq(only: %i[index show]) diff --git a/spec/cancan/controller_resource_spec.rb b/spec/cancan/controller_resource_spec.rb index b6e511bbb..6d8d7a483 100644 --- a/spec/cancan/controller_resource_spec.rb +++ b/spec/cancan/controller_resource_spec.rb @@ -144,7 +144,7 @@ class HiddenModel < ::Model; end params.merge!(controller: 'model', model: { name: 'test' }) end - it 'accepts and uses the specified symbol for santitizing input' do + it 'accepts and uses the specified symbol for sanitizing input' do allow(controller).to receive(:resource_params).and_return(resource: 'params') allow(controller).to receive(:model_params).and_return(model: 'params') allow(controller).to receive(:create_params).and_return(create: 'params') @@ -163,7 +163,7 @@ class HiddenModel < ::Model; end expect(resource.send('resource_params')).to eq(custom: 'params') end - it 'prefers to use the create_params method for santitizing input' do + it 'prefers to use the create_params method for sanitizing input' do allow(controller).to receive(:resource_params).and_return(resource: 'params') allow(controller).to receive(:model_params).and_return(model: 'params') allow(controller).to receive(:create_params).and_return(create: 'params') @@ -172,7 +172,7 @@ class HiddenModel < ::Model; end expect(resource.send('resource_params')).to eq(create: 'params') end - it 'prefers to use the _params method for santitizing input if create is not found' do + it 'prefers to use the _params method for sanitizing input if create is not found' do allow(controller).to receive(:resource_params).and_return(resource: 'params') allow(controller).to receive(:model_params).and_return(model: 'params') allow(controller).to receive(:custom_params).and_return(custom: 'params') @@ -180,7 +180,7 @@ class HiddenModel < ::Model; end expect(resource.send('resource_params')).to eq(model: 'params') end - it 'prefers to use the resource_params method for santitizing input if create or model is not found' do + it 'prefers to use the resource_params method for sanitizing input if create or model is not found' do allow(controller).to receive(:resource_params).and_return(resource: 'params') allow(controller).to receive(:custom_params).and_return(custom: 'params') resource = CanCan::ControllerResource.new(controller) @@ -542,7 +542,7 @@ class Admin::Dashboard; end end end - it 'calls the santitizer when the parameter hash matches our object' do + it 'calls the sanitizer when the parameter hash matches our object' do params.merge!(action: 'create', model: { name: 'test' }) allow(controller).to receive(:create_params).and_return({}) @@ -551,7 +551,7 @@ class Admin::Dashboard; end expect(controller.instance_variable_get(:@model).name).to eq nil end - it 'santitizes correctly when the instance name is overriden' do + it 'sanitizes correctly when the instance name is overridden' do params.merge!(action: 'create', custom_name: { name: 'foobar' }) allow(controller).to receive(:create_params).and_return({}) @@ -560,7 +560,7 @@ class Admin::Dashboard; end expect(controller.instance_variable_get(:@custom_name).name).to eq nil end - it 'calls the santitize method on non-save actions when required' do + it 'calls the sanitize method on non-save actions when required' do params.merge!(action: 'new', model: { name: 'test' }) allow(controller).to receive(:resource_params).and_return({}) diff --git a/spec/cancan/exceptions_spec.rb b/spec/cancan/exceptions_spec.rb index aa8cac85e..b441861a4 100644 --- a/spec/cancan/exceptions_spec.rb +++ b/spec/cancan/exceptions_spec.rb @@ -14,7 +14,7 @@ expect(@exception.conditions).to eq(:some_conditions) end - it 'has a changable default message' do + it 'has a changeable default message' do expect(@exception.message).to eq('You are not authorized to access this page.') @exception.default_message = 'Unauthorized!' expect(@exception.message).to eq('Unauthorized!') diff --git a/spec/cancan/matchers_spec.rb b/spec/cancan/matchers_spec.rb index 985304ff5..5990e1a4b 100644 --- a/spec/cancan/matchers_spec.rb +++ b/spec/cancan/matchers_spec.rb @@ -49,7 +49,7 @@ is_expected.not_to be_able_to([], 123) end - it 'delegates to can? with array of abilities with only one eligable ability' do + it 'delegates to can? with array of abilities with only one eligible ability' do is_expected.to receive(:can?).with(:read, 123) { true } is_expected.to receive(:can?).with(:update, 123) { false } is_expected.not_to be_able_to(%i[read update], 123) diff --git a/spec/cancan/model_adapters/accessible_by_integration_spec.rb b/spec/cancan/model_adapters/accessible_by_integration_spec.rb index 89f766591..dc581993f 100644 --- a/spec/cancan/model_adapters/accessible_by_integration_spec.rb +++ b/spec/cancan/model_adapters/accessible_by_integration_spec.rb @@ -74,7 +74,7 @@ class Editor < ActiveRecord::Base ability.can :read, Post, editors: { user_id: @user1 } end - describe 'preloading of associatons' do + describe 'preloading of associations' do it 'preloads associations correctly' do posts = Post.accessible_by(ability).where(published: true).includes(likes: :user) expect(posts[0].association(:likes)).to be_loaded diff --git a/spec/cancan/model_adapters/active_record_5_adapter_spec.rb b/spec/cancan/model_adapters/active_record_5_adapter_spec.rb index 877860071..b72191051 100644 --- a/spec/cancan/model_adapters/active_record_5_adapter_spec.rb +++ b/spec/cancan/model_adapters/active_record_5_adapter_spec.rb @@ -109,7 +109,7 @@ class Disc < ActiveRecord::Base expect(Thing.accessible_by(ability)).to contain_exactly(medium) end - context 'when a rule is overriden' do + context 'when a rule is overridden' do before do ability.cannot :read, Thing, size: 'average' end diff --git a/spec/cancan/model_adapters/active_record_adapter_spec.rb b/spec/cancan/model_adapters/active_record_adapter_spec.rb index 2ac33756b..af89929c1 100644 --- a/spec/cancan/model_adapters/active_record_adapter_spec.rb +++ b/spec/cancan/model_adapters/active_record_adapter_spec.rb @@ -641,7 +641,7 @@ class UserRole < ActiveRecord::Base expect(ability.can?(:read, Foo)).to eq(true) end - it 'allows for access with association with accesible_by' do + it 'allows for access with association with accessible_by' do user = User.new foo = Foo.create(name: 'foo') bar = Bar.create(name: 'bar') @@ -664,7 +664,7 @@ class UserRole < ActiveRecord::Base expect(ability.can?(:read, Foo)).to eq(false) end - it 'blocks access with association for accesible_by' do + it 'blocks access with association for accessible_by' do user = User.create! foo = Foo.create(name: 'foo') role = Role.create(name: 'adviser') diff --git a/spec/cancan/model_adapters/has_and_belongs_to_many_spec.rb b/spec/cancan/model_adapters/has_and_belongs_to_many_spec.rb index 0d112e9e0..1fab6ad63 100644 --- a/spec/cancan/model_adapters/has_and_belongs_to_many_spec.rb +++ b/spec/cancan/model_adapters/has_and_belongs_to_many_spec.rb @@ -51,7 +51,7 @@ class House < ActiveRecord::Base CanCan.accessible_by_strategy = :subquery end - it 'it retreives the records correctly' do + it 'it retrieves the records correctly' do houses = House.accessible_by(ability) expect(houses).to match_array [@house2, @house1] end @@ -78,7 +78,7 @@ class House < ActiveRecord::Base CanCan.accessible_by_strategy = :left_join end - it 'it retreives the records correctly' do + it 'it retrieves the records correctly' do houses = House.accessible_by(ability) expect(houses).to match_array [@house2, @house1] end From acb008aa080f1f4038bad011f52a20673ca98667 Mon Sep 17 00:00:00 2001 From: MarceloAGuimaraes <41841441+MarceloAGuimaraes@users.noreply.github.com> Date: Tue, 15 Mar 2022 08:36:07 -0300 Subject: [PATCH 29/53] verify if activerecord is defined (#766) --- lib/cancan/rule.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cancan/rule.rb b/lib/cancan/rule.rb index 07063496a..18df3895d 100644 --- a/lib/cancan/rule.rb +++ b/lib/cancan/rule.rb @@ -70,7 +70,7 @@ def only_raw_sql? end def with_scope? - @conditions.is_a?(ActiveRecord::Relation) + defined?(ActiveRecord) && @conditions.is_a?(ActiveRecord::Relation) end def associations_hash(conditions = @conditions) From 75d01b523b3aedba0fbb9719718356e57ef9ea67 Mon Sep 17 00:00:00 2001 From: Alexander Popov Date: Tue, 15 Mar 2022 14:36:33 +0300 Subject: [PATCH 30/53] Add Ruby 3.0 for CI (#752) Avoid incompatible RoR versions. --- .github/workflows/test.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 06f76a879..216351fe4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,9 +14,19 @@ jobs: strategy: fail-fast: false matrix: - ruby: ['2.4', '2.5', '2.6', '2.7', 'jruby', 'truffleruby'] + ruby: ['2.4', '2.5', '2.6', '2.7', '3.0', '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_main.gemfile'] exclude: + - gemfile: 'gemfiles/activerecord_5.2.2.gemfile' + ruby: '3.0' # rails 5.2 can't run on ruby 3.0 + - gemfile: 'gemfiles/activerecord_5.1.0.gemfile' + ruby: '3.0' # rails 5.1 can't run on ruby 3.0 + - gemfile: 'gemfiles/activerecord_5.0.2.gemfile' + ruby: '3.0' # rails 5.0 can't run on ruby 3.0 + - gemfile: 'gemfiles/activerecord_5.0.2.gemfile' + ruby: '3.0' # rails 5.0 can't run on ruby 3.0 + - gemfile: 'gemfiles/activerecord_4.2.0.gemfile' + ruby: '3.0' # rails 4.2 can't run on ruby 3.0 - 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' From 5127ee92fc1888acedc593e86bdc4ee4ca883f32 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Tue, 15 Mar 2022 15:16:56 +0000 Subject: [PATCH 31/53] Drop support for ruby 2.4 and 2.5 (#773) * Fix ruby 3 compatibilities issues on CI * Remove ruby 2.4 and 2.5 --- .github/workflows/test.yml | 16 +- .rubocop.yml | 8 +- .rubocop_todo.yml | 216 +++++++++++++++++++++++ CHANGELOG.md | 4 + cancancan.gemspec | 2 +- spec/cancan/controller_additions_spec.rb | 20 ++- spec/cancan/controller_resource_spec.rb | 2 +- 7 files changed, 246 insertions(+), 22 deletions(-) create mode 100644 .rubocop_todo.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 216351fe4..64bafec01 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', '3.0', 'jruby', 'truffleruby'] + ruby: ['2.6', '2.7', '3.0', '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_main.gemfile'] exclude: - gemfile: 'gemfiles/activerecord_5.2.2.gemfile' @@ -31,16 +31,14 @@ jobs: 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: 'truffleruby' # TruffleRuby 21.0 targets Ruby 2.7, same as above + - gemfile: 'gemfiles/activerecord_5.0.2.gemfile' + ruby: 'truffleruby' # TruffleRuby 21.0 targets Ruby 2.7, same as above + - gemfile: 'gemfiles/activerecord_5.1.0.gemfile' + ruby: 'truffleruby' # TruffleRuby 21.0 targets Ruby 2.7, same as above + - gemfile: 'gemfiles/activerecord_5.2.2.gemfile' + ruby: 'truffleruby' # TruffleRuby 21.0 targets Ruby 2.7, same as above - gemfile: 'gemfiles/activerecord_main.gemfile' ruby: '2.6' # rails 7+ requires ruby 3.0+ - - gemfile: 'gemfiles/activerecord_main.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' - ruby: '2.4' # rails 6+ requires ruby 2.5+ - - gemfile: 'gemfiles/activerecord_main.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' diff --git a/.rubocop.yml b/.rubocop.yml index 8f5637e7a..1b0e44d89 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,3 +1,5 @@ +inherit_from: .rubocop_todo.yml + Style/Documentation: Enabled: false @@ -10,7 +12,7 @@ Style/EmptyMethod: Style/ClassAndModuleChildren: Enabled: false -Metrics/LineLength: +Layout/LineLength: Max: 120 Metrics/BlockLength: @@ -34,7 +36,9 @@ Lint/AmbiguousBlockAssociation: Enabled: false AllCops: - TargetRubyVersion: 2.2.0 + NewCops: enable + SuggestExtensions: false + TargetRubyVersion: 3.0 Exclude: - 'gemfiles/**/*' - 'vendor/**/*' diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 000000000..f6c643ac7 --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,216 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2022-03-15 14:26:54 UTC using RuboCop version 1.26.0. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 1 +# This cop supports safe auto-correction (--auto-correct). +# Configuration parameters: Include. +# Include: **/*.gemspec +Gemspec/RequireMFA: + Exclude: + - 'cancancan.gemspec' + +# Offense count: 1 +# Configuration parameters: Include. +# Include: **/*.gemspec +Gemspec/RequiredRubyVersion: + Exclude: + - 'cancancan.gemspec' + +# Offense count: 2 +# This cop supports safe auto-correction (--auto-correct). +# Configuration parameters: EmptyLineBetweenMethodDefs, EmptyLineBetweenClassDefs, EmptyLineBetweenModuleDefs, AllowAdjacentOneLineDefs, NumberOfEmptyLines. +Layout/EmptyLineBetweenDefs: + Exclude: + - 'spec/cancan/ability_spec.rb' + +# Offense count: 3 +# This cop supports safe auto-correction (--auto-correct). +# Configuration parameters: EnforcedStyle, IndentationWidth. +# SupportedStyles: aligned, indented +Layout/LineEndStringConcatenationIndentation: + Exclude: + - 'lib/cancan/ability/rules.rb' + - 'lib/cancan/rule.rb' + - 'spec/cancan/model_adapters/active_record_adapter_spec.rb' + +# Offense count: 1 +# This cop supports safe auto-correction (--auto-correct). +Lint/AmbiguousOperatorPrecedence: + Exclude: + - 'lib/cancan/controller_resource.rb' + +# Offense count: 77 +# Configuration parameters: AllowedMethods. +# AllowedMethods: enums +Lint/ConstantDefinitionInBlock: + Exclude: + - 'spec/cancan/ability_spec.rb' + - 'spec/cancan/controller_resource_spec.rb' + - 'spec/cancan/model_adapters/accessible_by_has_many_through_spec.rb' + - 'spec/cancan/model_adapters/accessible_by_integration_spec.rb' + - 'spec/cancan/model_adapters/active_record_4_adapter_spec.rb' + - 'spec/cancan/model_adapters/active_record_adapter_spec.rb' + - 'spec/cancan/model_adapters/conditions_extractor_spec.rb' + - 'spec/cancan/model_adapters/conditions_normalizer_spec.rb' + - 'spec/cancan/model_adapters/has_and_belongs_to_many_spec.rb' + - 'spec/cancan/rule_compressor_spec.rb' + - 'spec/cancan/rule_spec.rb' + +# Offense count: 1 +# Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches. +Lint/DuplicateBranch: + Exclude: + - 'spec/cancan/model_adapters/active_record_adapter_spec.rb' + +# Offense count: 3 +# Configuration parameters: AllowComments, AllowEmptyLambdas. +Lint/EmptyBlock: + Exclude: + - 'spec/cancan/model_adapters/conditions_normalizer_spec.rb' + +# Offense count: 4 +# Configuration parameters: AllowComments. +Lint/EmptyClass: + Exclude: + - 'spec/cancan/controller_resource_spec.rb' + - 'spec/cancan/rule_compressor_spec.rb' + +# Offense count: 2 +Lint/MissingSuper: + Exclude: + - 'lib/cancan/exceptions.rb' + - 'lib/cancan/model_adapters/abstract_adapter.rb' + +# Offense count: 1 +# Configuration parameters: IgnoredMethods, CountRepeatedAttributes. +Metrics/AbcSize: + Max: 18 + +# Offense count: 1 +# Configuration parameters: Max, CountKeywordArgs. +Metrics/ParameterLists: + MaxOptionalParameters: 4 + +# Offense count: 1 +# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers. +# SupportedStyles: snake_case, normalcase, non_integer +# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339 +Naming/VariableNumber: + Exclude: + - 'spec/cancan/model_adapters/conditions_extractor_spec.rb' + +# Offense count: 2 +# This cop supports safe auto-correction (--auto-correct). +Style/BisectedAttrAccessor: + Exclude: + - 'lib/cancan/rule.rb' + +# Offense count: 5 +# This cop supports safe auto-correction (--auto-correct). +# Configuration parameters: IgnoredMethods. +# IgnoredMethods: ==, equal?, eql? +Style/ClassEqualityComparison: + Exclude: + - 'lib/cancan/ability.rb' + - 'lib/cancan/conditions_matcher.rb' + - 'lib/cancan/relevant.rb' + - 'lib/cancan/unauthorized_message_resolver.rb' + +# Offense count: 1 +# This cop supports unsafe auto-correction (--auto-correct-all). +Style/CollectionCompact: + Exclude: + - 'lib/cancan/ability.rb' + +# Offense count: 4 +# Configuration parameters: EnforcedStyle, MaxUnannotatedPlaceholdersAllowed, IgnoredMethods. +# SupportedStyles: annotated, template, unannotated +Style/FormatStringToken: + Exclude: + - 'spec/cancan/ability_spec.rb' + +# Offense count: 9 +# This cop supports safe auto-correction (--auto-correct). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: always, always_true, never +Style/FrozenStringLiteralComment: + Exclude: + - 'lib/cancan/class_matcher.rb' + - 'lib/cancan/model_adapters/conditions_normalizer.rb' + - 'lib/cancan/model_adapters/sti_normalizer.rb' + - 'lib/cancan/model_adapters/strategies/base.rb' + - 'lib/cancan/model_adapters/strategies/left_join.rb' + - 'lib/cancan/model_adapters/strategies/subquery.rb' + - 'spec/cancan/model_adapters/accessible_by_has_many_through_spec.rb' + - 'spec/cancan/model_adapters/conditions_normalizer_spec.rb' + - 'spec/cancan/model_adapters/has_and_belongs_to_many_spec.rb' + +# Offense count: 1 +# This cop supports safe auto-correction (--auto-correct). +# Configuration parameters: EnforcedStyle. +# SupportedStyles: same_as_string_literals, single_quotes, double_quotes +Style/QuotedSymbols: + Exclude: + - 'lib/cancan/exceptions.rb' + +# Offense count: 3 +# This cop supports safe auto-correction (--auto-correct). +Style/RedundantBegin: + Exclude: + - 'spec/cancan/ability_spec.rb' + +# Offense count: 12 +# This cop supports safe auto-correction (--auto-correct). +Style/RedundantFileExtensionInRequire: + Exclude: + - 'lib/cancan/ability.rb' + - 'lib/cancan/controller_resource.rb' + - 'lib/cancan/controller_resource_loader.rb' + - 'lib/cancan/rule.rb' + - 'lib/cancan/rules_compressor.rb' + +# Offense count: 1 +# This cop supports safe auto-correction (--auto-correct). +Style/RedundantFreeze: + Exclude: + - 'lib/cancan/version.rb' + +# Offense count: 4 +# This cop supports safe auto-correction (--auto-correct). +Style/RedundantRegexpEscape: + Exclude: + - 'spec/changelog_spec.rb' + +# Offense count: 1 +# This cop supports safe auto-correction (--auto-correct). +# Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods. +# AllowedMethods: present?, blank?, presence, try, try! +Style/SafeNavigation: + Exclude: + - 'lib/cancan/controller_resource_builder.rb' + +# Offense count: 3 +# This cop supports unsafe auto-correction (--auto-correct-all). +Style/SlicingWithRange: + Exclude: + - 'lib/cancan/matchers.rb' + - 'lib/cancan/rules_compressor.rb' + - 'spec/cancan/rule_compressor_spec.rb' + +# Offense count: 2 +# This cop supports unsafe auto-correction (--auto-correct-all). +Style/StringChars: + Exclude: + - 'spec/matchers.rb' + +# Offense count: 1 +# This cop supports unsafe auto-correction (--auto-correct-all). +# Configuration parameters: Mode. +Style/StringConcatenation: + Exclude: + - 'lib/cancan/rule.rb' diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fb985480..84e8b4064 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +Unreleased + +* [#773](https://github.com/CanCanCommunity/cancancan/pull/773): Drop support for ruby 2.4 and 2.5. ([@coorasse][]) + ## 3.3.0 * [#675](https://github.com/CanCanCommunity/cancancan/pull/675): Support modifying the `accessible_by` querying strategy on a per-query basis. ([@ghiculescu][]) diff --git a/cancancan.gemspec b/cancancan.gemspec index 95005509d..91e0c6129 100644 --- a/cancancan.gemspec +++ b/cancancan.gemspec @@ -25,5 +25,5 @@ Gem::Specification.new do |s| s.add_development_dependency 'bundler', '~> 2.0' s.add_development_dependency 'rake', '~> 10.1', '>= 10.1.1' s.add_development_dependency 'rspec', '~> 3.2', '>= 3.2.0' - s.add_development_dependency 'rubocop', '~> 0.63.1' + s.add_development_dependency 'rubocop', '~> 1.26' end diff --git a/spec/cancan/controller_additions_spec.rb b/spec/cancan/controller_additions_spec.rb index 013cbbd55..b16c0c7f7 100644 --- a/spec/cancan/controller_additions_spec.rb +++ b/spec/cancan/controller_additions_spec.rb @@ -30,7 +30,7 @@ it 'load_and_authorize_resource setups a before filter which passes call to ControllerResource' do expect(cancan_resource_class = double).to receive(:load_and_authorize_resource) - allow(CanCan::ControllerResource).to receive(:new).with(@controller, nil, foo: :bar) { cancan_resource_class } + allow(CanCan::ControllerResource).to receive(:new).with(@controller, nil, { foo: :bar }) { cancan_resource_class } expect(@controller_class) .to receive(:before_action).with({}) { |_options, &block| block.call(@controller) } @controller_class.load_and_authorize_resource foo: :bar @@ -38,7 +38,9 @@ it 'load_and_authorize_resource properly passes first argument as the resource name' do expect(cancan_resource_class = double).to receive(:load_and_authorize_resource) - allow(CanCan::ControllerResource).to receive(:new).with(@controller, :project, foo: :bar) { cancan_resource_class } + allow(CanCan::ControllerResource).to receive(:new).with(@controller, :project, { foo: :bar }) do + cancan_resource_class + end expect(@controller_class) .to receive(:before_action).with({}) { |_options, &block| block.call(@controller) } @controller_class.load_and_authorize_resource :project, foo: :bar @@ -51,9 +53,9 @@ it 'authorize_resource setups a before filter which passes call to ControllerResource' do expect(cancan_resource_class = double).to receive(:authorize_resource) - allow(CanCan::ControllerResource).to receive(:new).with(@controller, nil, foo: :bar) { cancan_resource_class } + allow(CanCan::ControllerResource).to receive(:new).with(@controller, nil, { foo: :bar }) { cancan_resource_class } expect(@controller_class) - .to receive(:before_action).with(except: :show, if: true) do |_options, &block| + .to receive(:before_action).with({ except: :show, if: true }) do |_options, &block| block.call(@controller) end @controller_class.authorize_resource foo: :bar, except: :show, if: true @@ -61,9 +63,9 @@ it 'load_resource setups a before filter which passes call to ControllerResource' do expect(cancan_resource_class = double).to receive(:load_resource) - allow(CanCan::ControllerResource).to receive(:new).with(@controller, nil, foo: :bar) { cancan_resource_class } + allow(CanCan::ControllerResource).to receive(:new).with(@controller, nil, { foo: :bar }) { cancan_resource_class } expect(@controller_class) - .to receive(:before_action).with(only: %i[show index], unless: false) do |_options, &block| + .to receive(:before_action).with({ only: %i[show index], unless: false }) do |_options, &block| block.call(@controller) end @controller_class.load_resource foo: :bar, only: %i[show index], unless: false @@ -78,9 +80,9 @@ it 'check_authorization triggers AuthorizationNotPerformed in after filter' do expect(@controller_class) - .to receive(:after_action).with(only: [:test]) { |_options, &block| block.call(@controller) } + .to receive(:after_action).with({ only: [:test] }) { |_options, &block| block.call(@controller) } expect do - @controller_class.check_authorization(only: [:test]) + @controller_class.check_authorization({ only: [:test] }) end.to raise_error(CanCan::AuthorizationNotPerformed) end @@ -105,7 +107,7 @@ it 'check_authorization does not raise error when @_authorized is set' do @controller.instance_variable_set(:@_authorized, true) expect(@controller_class) - .to receive(:after_action).with(only: [:test]) { |_options, &block| block.call(@controller) } + .to receive(:after_action).with({ only: [:test] }) { |_options, &block| block.call(@controller) } expect do @controller_class.check_authorization(only: [:test]) end.not_to raise_error diff --git a/spec/cancan/controller_resource_spec.rb b/spec/cancan/controller_resource_spec.rb index 6d8d7a483..f66eb0461 100644 --- a/spec/cancan/controller_resource_spec.rb +++ b/spec/cancan/controller_resource_spec.rb @@ -282,7 +282,7 @@ class Dashboard; end it 'authorizes nested resource through parent association on index action' do controller.instance_variable_set(:@category, category = double) - allow(controller).to receive(:authorize!).with(:index, category => Model) { raise CanCan::AccessDenied } + allow(controller).to receive(:authorize!).with(:index, { category => Model }) { raise CanCan::AccessDenied } resource = CanCan::ControllerResource.new(controller, through: :category) expect { resource.authorize_resource }.to raise_error(CanCan::AccessDenied) end From 154c36587fb63ff14751f4c3f3f903e5cb423ecd Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Tue, 15 Mar 2022 13:49:20 +0000 Subject: [PATCH 32/53] Add support for non-hash conditions Co-authored-by: Juleffel --- CHANGELOG.md | 2 ++ lib/cancan/conditions_matcher.rb | 12 ++++++++++- spec/cancan/ability_spec.rb | 34 ++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84e8b4064..56ba01a40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ Unreleased +* [#772](https://github.com/CanCanCommunity/cancancan/pull/772): Support non-hash conditions in ability definitions. ([@Juleffel][]) * [#773](https://github.com/CanCanCommunity/cancancan/pull/773): Drop support for ruby 2.4 and 2.5. ([@coorasse][]) ## 3.3.0 @@ -693,3 +694,4 @@ Please read the [guide on migrating from CanCanCan 2.x to 3.0](https://github.co [@Liberatys]: https://github.com/Liberatys [@ghiculescu]: https://github.com/ghiculescu [@mtoneil]: https://github.com/mtoneil +[@Juleffel]: https://github.com/Juleffel diff --git a/lib/cancan/conditions_matcher.rb b/lib/cancan/conditions_matcher.rb index 888ff9af4..0eca798b7 100644 --- a/lib/cancan/conditions_matcher.rb +++ b/lib/cancan/conditions_matcher.rb @@ -42,7 +42,7 @@ def nested_subject_matches_conditions?(subject_hash) # override_matching_for_conditions?(subject, conditions) and # matches_conditions_hash?(subject, conditions) def matches_conditions_hash?(subject, conditions = @conditions) - return true if conditions.empty? + return true if conditions.is_a?(Hash) && conditions.empty? adapter = model_adapter(subject) @@ -54,6 +54,16 @@ def matches_conditions_hash?(subject, conditions = @conditions) end def matches_all_conditions?(adapter, conditions, subject) + if conditions.is_a?(Hash) + matches_hash_conditions(adapter, conditions, subject) + elsif conditions.respond_to?(:include?) + conditions.include?(subject) + else + subject == conditions + end + end + + def matches_hash_conditions(adapter, conditions, subject) conditions.all? do |name, value| if adapter.override_condition_matching?(subject, name, value) adapter.matches_condition?(subject, name, value) diff --git a/spec/cancan/ability_spec.rb b/spec/cancan/ability_spec.rb index 7c998fdb5..8bfade06b 100644 --- a/spec/cancan/ability_spec.rb +++ b/spec/cancan/ability_spec.rb @@ -829,4 +829,38 @@ class Account expect(@ability.send(:rules).size).to eq(0) end end + + describe 'when #can? is used with a Hash (nested resources)' do + it 'is unauthorized with no rules' do + expect(@ability.can?(:read, 1 => Symbol)).to be(false) + end + + it 'is authorized when the child is authorized' do + @ability.can :read, Symbol + expect(@ability.can?(:read, 1 => Symbol)).to be(true) + end + + it 'is authorized when the condition doesn\'t concern the parent' do + @ability.can :read, Symbol, whatever: true + expect(@ability.can?(:read, 1 => Symbol)).to be(true) + end + + it 'verifies the parent against an equality condition' do + @ability.can :read, Symbol, integer: 1 + expect(@ability.can?(:read, 1 => Symbol)).to be(true) + expect(@ability.can?(:read, 2 => Symbol)).to be(false) + end + + it 'verifies the parent against an array condition' do + @ability.can :read, Symbol, integer: [0, 1] + expect(@ability.can?(:read, 1 => Symbol)).to be(true) + expect(@ability.can?(:read, 2 => Symbol)).to be(false) + end + + it 'verifies the parent against a hash condition' do + @ability.can :read, Symbol, integer: { to_i: 1 } + expect(@ability.can?(:read, 1 => Symbol)).to be(true) + expect(@ability.can?(:read, 2 => Symbol)).to be(false) + end + end end From 0563c055fb9131a5271919c3f08a238e1540347a Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Tue, 15 Mar 2022 16:36:07 +0000 Subject: [PATCH 33/53] Add support for non-hash conditions (#772) Co-authored-by: Juleffel Co-authored-by: Juleffel --- CHANGELOG.md | 2 ++ lib/cancan/conditions_matcher.rb | 12 ++++++++++- spec/cancan/ability_spec.rb | 34 ++++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84e8b4064..56ba01a40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ Unreleased +* [#772](https://github.com/CanCanCommunity/cancancan/pull/772): Support non-hash conditions in ability definitions. ([@Juleffel][]) * [#773](https://github.com/CanCanCommunity/cancancan/pull/773): Drop support for ruby 2.4 and 2.5. ([@coorasse][]) ## 3.3.0 @@ -693,3 +694,4 @@ Please read the [guide on migrating from CanCanCan 2.x to 3.0](https://github.co [@Liberatys]: https://github.com/Liberatys [@ghiculescu]: https://github.com/ghiculescu [@mtoneil]: https://github.com/mtoneil +[@Juleffel]: https://github.com/Juleffel diff --git a/lib/cancan/conditions_matcher.rb b/lib/cancan/conditions_matcher.rb index 888ff9af4..0eca798b7 100644 --- a/lib/cancan/conditions_matcher.rb +++ b/lib/cancan/conditions_matcher.rb @@ -42,7 +42,7 @@ def nested_subject_matches_conditions?(subject_hash) # override_matching_for_conditions?(subject, conditions) and # matches_conditions_hash?(subject, conditions) def matches_conditions_hash?(subject, conditions = @conditions) - return true if conditions.empty? + return true if conditions.is_a?(Hash) && conditions.empty? adapter = model_adapter(subject) @@ -54,6 +54,16 @@ def matches_conditions_hash?(subject, conditions = @conditions) end def matches_all_conditions?(adapter, conditions, subject) + if conditions.is_a?(Hash) + matches_hash_conditions(adapter, conditions, subject) + elsif conditions.respond_to?(:include?) + conditions.include?(subject) + else + subject == conditions + end + end + + def matches_hash_conditions(adapter, conditions, subject) conditions.all? do |name, value| if adapter.override_condition_matching?(subject, name, value) adapter.matches_condition?(subject, name, value) diff --git a/spec/cancan/ability_spec.rb b/spec/cancan/ability_spec.rb index 7c998fdb5..8bfade06b 100644 --- a/spec/cancan/ability_spec.rb +++ b/spec/cancan/ability_spec.rb @@ -829,4 +829,38 @@ class Account expect(@ability.send(:rules).size).to eq(0) end end + + describe 'when #can? is used with a Hash (nested resources)' do + it 'is unauthorized with no rules' do + expect(@ability.can?(:read, 1 => Symbol)).to be(false) + end + + it 'is authorized when the child is authorized' do + @ability.can :read, Symbol + expect(@ability.can?(:read, 1 => Symbol)).to be(true) + end + + it 'is authorized when the condition doesn\'t concern the parent' do + @ability.can :read, Symbol, whatever: true + expect(@ability.can?(:read, 1 => Symbol)).to be(true) + end + + it 'verifies the parent against an equality condition' do + @ability.can :read, Symbol, integer: 1 + expect(@ability.can?(:read, 1 => Symbol)).to be(true) + expect(@ability.can?(:read, 2 => Symbol)).to be(false) + end + + it 'verifies the parent against an array condition' do + @ability.can :read, Symbol, integer: [0, 1] + expect(@ability.can?(:read, 1 => Symbol)).to be(true) + expect(@ability.can?(:read, 2 => Symbol)).to be(false) + end + + it 'verifies the parent against a hash condition' do + @ability.can :read, Symbol, integer: { to_i: 1 } + expect(@ability.can?(:read, 1 => Symbol)).to be(true) + expect(@ability.can?(:read, 2 => Symbol)).to be(false) + end + end end From 41f09b924f68c430fefda2dca77463af9e7fcb91 Mon Sep 17 00:00:00 2001 From: Juleffel Date: Fri, 4 Feb 2022 14:15:07 +0100 Subject: [PATCH 34/53] Improve ability checks with Hashes --- lib/cancan/conditions_matcher.rb | 25 ++++++----- lib/cancan/model_adapters/abstract_adapter.rb | 11 +++++ .../model_adapters/active_record_adapter.rb | 16 +++++++ spec/cancan/ability_spec.rb | 18 ++++---- .../accessible_by_integration_spec.rb | 31 ++++++++++++++ .../active_record_adapter_spec.rb | 42 +++++++++++++++++++ 6 files changed, 124 insertions(+), 19 deletions(-) diff --git a/lib/cancan/conditions_matcher.rb b/lib/cancan/conditions_matcher.rb index 0eca798b7..aaad709d4 100644 --- a/lib/cancan/conditions_matcher.rb +++ b/lib/cancan/conditions_matcher.rb @@ -33,8 +33,17 @@ def matches_non_block_conditions(subject) end def nested_subject_matches_conditions?(subject_hash) - parent, _child = subject_hash.first - matches_conditions_hash?(parent, @conditions[parent.class.name.downcase.to_sym] || {}) + parent, child = subject_hash.first + + matches_base_parent_conditions = matches_conditions_hash?(parent, @conditions[parent.class.name.downcase.to_sym] || {}) + + adapter = model_adapter(parent) + + if adapter.override_nested_subject_conditions_matching?(parent, child, @conditions) + return matches_base_parent_conditions && adapter.nested_subject_matches_conditions?(parent, child, @conditions) + end + + matches_base_parent_conditions end # Checks if the given subject matches the given conditions hash. @@ -53,17 +62,13 @@ def matches_conditions_hash?(subject, conditions = @conditions) matches_all_conditions?(adapter, conditions, subject) end + # conditions can be a collection, object, or hash def matches_all_conditions?(adapter, conditions, subject) - if conditions.is_a?(Hash) - matches_hash_conditions(adapter, conditions, subject) - elsif conditions.respond_to?(:include?) - conditions.include?(subject) - else - subject == conditions + unless conditions.is_a?(Hash) + return conditions.include?(subject) if conditions.respond_to?(:include?) + return subject == conditions end - end - def matches_hash_conditions(adapter, conditions, subject) conditions.all? do |name, value| if adapter.override_condition_matching?(subject, name, value) adapter.matches_condition?(subject, name, value) diff --git a/lib/cancan/model_adapters/abstract_adapter.rb b/lib/cancan/model_adapters/abstract_adapter.rb index 8a68382bd..7e1a2ffa6 100644 --- a/lib/cancan/model_adapters/abstract_adapter.rb +++ b/lib/cancan/model_adapters/abstract_adapter.rb @@ -35,6 +35,17 @@ def self.matches_conditions_hash?(_subject, _conditions) raise NotImplemented, 'This model adapter does not support matching on a conditions hash.' end + # Used above override_conditions_hash_matching to determine if this model adapter will override the matching behavior for nested subject. + # If this returns true then nested_subject_matches_conditions? will be called. + def self.override_nested_subject_conditions_matching?(_parent, _child, _all_conditions) + false + end + + # Override if override_nested_subject_conditions_matching? returns true + def self.nested_subject_matches_conditions?(_parent, _child, _all_conditions) + raise NotImplemented, 'This model adapter does not support matching on a nested subject.' + end + # Used to determine if this model adapter will override the matching behavior for a specific condition. # If this returns true then matches_condition? will be called. See Rule#matches_conditions_hash def self.override_condition_matching?(_subject, _name, _value) diff --git a/lib/cancan/model_adapters/active_record_adapter.rb b/lib/cancan/model_adapters/active_record_adapter.rb index 741570838..c125cd055 100644 --- a/lib/cancan/model_adapters/active_record_adapter.rb +++ b/lib/cancan/model_adapters/active_record_adapter.rb @@ -20,6 +20,22 @@ def initialize(model_class, rules) ConditionsNormalizer.normalize(model_class, @compressed_rules) end + class << self + # When belongs_to parent_id is a condition for a model, we want to check the parent when testing ability for a hash {parent => model} + def override_nested_subject_conditions_matching?(parent, _child, all_conditions) + all_conditions[:"#{parent.class.name.downcase}_id"].present? + end + + # parent_id condition can be an array of integer or one integer, we check the parent against this + def nested_subject_matches_conditions?(parent, _child, all_conditions) + id_condition = all_conditions[:"#{parent.class.name.downcase}_id"] + return id_condition.include?(parent.id) if id_condition.is_a? Array + return id_condition == parent.id if id_condition.is_a? Integer + + false + end + end + # Returns conditions intended to be used inside a database query. Normally you will not call this # method directly, but instead go through ModelAdditions#accessible_by. # diff --git a/spec/cancan/ability_spec.rb b/spec/cancan/ability_spec.rb index 8bfade06b..a30644141 100644 --- a/spec/cancan/ability_spec.rb +++ b/spec/cancan/ability_spec.rb @@ -832,35 +832,35 @@ class Account describe 'when #can? is used with a Hash (nested resources)' do it 'is unauthorized with no rules' do - expect(@ability.can?(:read, 1 => Symbol)).to be(false) + expect(@ability.can?(:read, {1 => Symbol})).to be(false) end it 'is authorized when the child is authorized' do @ability.can :read, Symbol - expect(@ability.can?(:read, 1 => Symbol)).to be(true) + expect(@ability.can?(:read, {1 => Symbol})).to be(true) end it 'is authorized when the condition doesn\'t concern the parent' do @ability.can :read, Symbol, whatever: true - expect(@ability.can?(:read, 1 => Symbol)).to be(true) + expect(@ability.can?(:read, {1 => Symbol})).to be(true) end it 'verifies the parent against an equality condition' do @ability.can :read, Symbol, integer: 1 - expect(@ability.can?(:read, 1 => Symbol)).to be(true) - expect(@ability.can?(:read, 2 => Symbol)).to be(false) + expect(@ability.can?(:read, {1 => Symbol})).to be(true) + expect(@ability.can?(:read, {2 => Symbol})).to be(false) end it 'verifies the parent against an array condition' do @ability.can :read, Symbol, integer: [0, 1] - expect(@ability.can?(:read, 1 => Symbol)).to be(true) - expect(@ability.can?(:read, 2 => Symbol)).to be(false) + expect(@ability.can?(:read, {1 => Symbol})).to be(true) + expect(@ability.can?(:read, {2 => Symbol})).to be(false) end it 'verifies the parent against a hash condition' do @ability.can :read, Symbol, integer: { to_i: 1 } - expect(@ability.can?(:read, 1 => Symbol)).to be(true) - expect(@ability.can?(:read, 2 => Symbol)).to be(false) + expect(@ability.can?(:read, {1 => Symbol})).to be(true) + expect(@ability.can?(:read, {2 => Symbol})).to be(false) end end end diff --git a/spec/cancan/model_adapters/accessible_by_integration_spec.rb b/spec/cancan/model_adapters/accessible_by_integration_spec.rb index dc581993f..928eae10d 100644 --- a/spec/cancan/model_adapters/accessible_by_integration_spec.rb +++ b/spec/cancan/model_adapters/accessible_by_integration_spec.rb @@ -97,4 +97,35 @@ class Editor < ActiveRecord::Base end end end + + describe 'accessing posts through user' do + it 'works with user_id' do + ability.can :read, User + ability.can :read, Post, user_id: @user1.id + expect(ability.can?(:read, {@user1 => Post})).to be(true) + expect(ability.can?(:read, {@user2 => Post})).to be(true) + end + + it 'works with user: {id: ...}' do + ability.can :read, User + ability.can :read, Post, user: { id: @user1.id } + expect(ability.can?(:read, {@user1 => Post})).to be(true) + expect(ability.can?(:read, {@user2 => Post})).to be(true) + end + + it 'works with cannot user_id' do + ability.can :read, User + ability.cannot :read, Post, user_id: @user1.id + expect(ability.can?(:read, {@user1 => Post})).to be(false) + expect(ability.can?(:read, {@user2 => Post})).to be(true) + end + + it 'works with cannot user: {id: ...}' do + ability.can :read, User + ability.cannot :read, Post, user: { id: @user1.id } + expect(ability.can?(:read, {@user1 => Post})).to be(false) + expect(ability.can?(:read, {@user2 => Post})).to be(true) + 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 af89929c1..f6932d70e 100644 --- a/spec/cancan/model_adapters/active_record_adapter_spec.rb +++ b/spec/cancan/model_adapters/active_record_adapter_spec.rb @@ -62,6 +62,7 @@ end class Project < ActiveRecord::Base + has_many :articles has_many :comments end @@ -96,6 +97,7 @@ class Mention < ActiveRecord::Base class Comment < ActiveRecord::Base belongs_to :article + belongs_to :project end class User < ActiveRecord::Base @@ -441,6 +443,46 @@ class User < ActiveRecord::Base ability.cannot :read, Article, :secret expect(Article.accessible_by(ability)).to eq([article]) end + + describe 'when can? is used with a Hash (nested resources)' do + it 'verifies parent_id key in conditions' do + user1 = User.create!(name: 'user1') + user2 = User.create!(name: 'user2') + cat = Category.create!(name: 'cat') + proj = Project.create!(name: 'proj') + art1 = Article.create!(name: 'art1', category: cat, project: proj, user: user1) + art2 = Article.create!(name: 'art1', category: cat, project: proj, user: user2) + comm1 = Comment.create!(article: art1, project: proj) + comm2 = Comment.create!(article: art2, project: proj) + + ability = Ability.new(user1) + ability.can :read, Article + ability.can :manage, Article, user_id: user1.id + ability.can :manage, Comment, article_id: user1.article_ids + + expect(ability.can?(:manage, {art1 => Comment})).to eq(true) + expect(ability.can?(:manage, {art2 => Comment})).to eq(false) + end + + it 'verifies parent equality correctly' do + user1 = User.create!(name: 'user1') + user2 = User.create!(name: 'user2') + cat = Category.create!(name: 'cat') + proj = Project.create!(name: 'proj') + art1 = Article.create!(name: 'art1', category: cat, project: proj, user: user1) + art2 = Article.create!(name: 'art1', category: cat, project: proj, user: user2) + comm1 = Comment.create!(article: art1, project: proj) + comm2 = Comment.create!(article: art2, project: proj) + + ability = Ability.new(user1) + ability.can :read, Article + ability.can :manage, Article, user: user1 + ability.can :manage, Comment, article: user1.articles + + expect(ability.can?(:manage, {art1 => Comment})).to eq(true) + expect(ability.can?(:manage, {art2 => Comment})).to eq(false) + end + end end end From e2ee4afdaffd3a49d832872784303fc01cd84bb5 Mon Sep 17 00:00:00 2001 From: Juleffel Date: Fri, 4 Feb 2022 15:02:37 +0100 Subject: [PATCH 35/53] Remove unnecessary tests --- .../accessible_by_integration_spec.rb | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/spec/cancan/model_adapters/accessible_by_integration_spec.rb b/spec/cancan/model_adapters/accessible_by_integration_spec.rb index 928eae10d..dc581993f 100644 --- a/spec/cancan/model_adapters/accessible_by_integration_spec.rb +++ b/spec/cancan/model_adapters/accessible_by_integration_spec.rb @@ -97,35 +97,4 @@ class Editor < ActiveRecord::Base end end end - - describe 'accessing posts through user' do - it 'works with user_id' do - ability.can :read, User - ability.can :read, Post, user_id: @user1.id - expect(ability.can?(:read, {@user1 => Post})).to be(true) - expect(ability.can?(:read, {@user2 => Post})).to be(true) - end - - it 'works with user: {id: ...}' do - ability.can :read, User - ability.can :read, Post, user: { id: @user1.id } - expect(ability.can?(:read, {@user1 => Post})).to be(true) - expect(ability.can?(:read, {@user2 => Post})).to be(true) - end - - it 'works with cannot user_id' do - ability.can :read, User - ability.cannot :read, Post, user_id: @user1.id - expect(ability.can?(:read, {@user1 => Post})).to be(false) - expect(ability.can?(:read, {@user2 => Post})).to be(true) - end - - it 'works with cannot user: {id: ...}' do - ability.can :read, User - ability.cannot :read, Post, user: { id: @user1.id } - expect(ability.can?(:read, {@user1 => Post})).to be(false) - expect(ability.can?(:read, {@user2 => Post})).to be(true) - end - end - end From a6da6e5f47857e96275136b0776065d28f164823 Mon Sep 17 00:00:00 2001 From: Juleffel Date: Tue, 15 Mar 2022 18:10:20 +0100 Subject: [PATCH 36/53] re-merging because I took the wrong version of code --- lib/cancan/conditions_matcher.rb | 11 ++++++++--- spec/cancan/ability_spec.rb | 18 +++++++++--------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/lib/cancan/conditions_matcher.rb b/lib/cancan/conditions_matcher.rb index aaad709d4..df8cd5261 100644 --- a/lib/cancan/conditions_matcher.rb +++ b/lib/cancan/conditions_matcher.rb @@ -64,11 +64,16 @@ def matches_conditions_hash?(subject, conditions = @conditions) # conditions can be a collection, object, or hash def matches_all_conditions?(adapter, conditions, subject) - unless conditions.is_a?(Hash) - return conditions.include?(subject) if conditions.respond_to?(:include?) - return subject == conditions + if conditions.is_a?(Hash) + matches_hash_conditions(adapter, conditions, subject) + elsif conditions.respond_to?(:include?) + conditions.include?(subject) + else + subject == conditions end + end + def matches_hash_conditions(adapter, conditions, subject) conditions.all? do |name, value| if adapter.override_condition_matching?(subject, name, value) adapter.matches_condition?(subject, name, value) diff --git a/spec/cancan/ability_spec.rb b/spec/cancan/ability_spec.rb index a30644141..8bfade06b 100644 --- a/spec/cancan/ability_spec.rb +++ b/spec/cancan/ability_spec.rb @@ -832,35 +832,35 @@ class Account describe 'when #can? is used with a Hash (nested resources)' do it 'is unauthorized with no rules' do - expect(@ability.can?(:read, {1 => Symbol})).to be(false) + expect(@ability.can?(:read, 1 => Symbol)).to be(false) end it 'is authorized when the child is authorized' do @ability.can :read, Symbol - expect(@ability.can?(:read, {1 => Symbol})).to be(true) + expect(@ability.can?(:read, 1 => Symbol)).to be(true) end it 'is authorized when the condition doesn\'t concern the parent' do @ability.can :read, Symbol, whatever: true - expect(@ability.can?(:read, {1 => Symbol})).to be(true) + expect(@ability.can?(:read, 1 => Symbol)).to be(true) end it 'verifies the parent against an equality condition' do @ability.can :read, Symbol, integer: 1 - expect(@ability.can?(:read, {1 => Symbol})).to be(true) - expect(@ability.can?(:read, {2 => Symbol})).to be(false) + expect(@ability.can?(:read, 1 => Symbol)).to be(true) + expect(@ability.can?(:read, 2 => Symbol)).to be(false) end it 'verifies the parent against an array condition' do @ability.can :read, Symbol, integer: [0, 1] - expect(@ability.can?(:read, {1 => Symbol})).to be(true) - expect(@ability.can?(:read, {2 => Symbol})).to be(false) + expect(@ability.can?(:read, 1 => Symbol)).to be(true) + expect(@ability.can?(:read, 2 => Symbol)).to be(false) end it 'verifies the parent against a hash condition' do @ability.can :read, Symbol, integer: { to_i: 1 } - expect(@ability.can?(:read, {1 => Symbol})).to be(true) - expect(@ability.can?(:read, {2 => Symbol})).to be(false) + expect(@ability.can?(:read, 1 => Symbol)).to be(true) + expect(@ability.can?(:read, 2 => Symbol)).to be(false) end end end From 352265f40886f5e710615e378301e78abe9cde0b Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Mon, 21 Mar 2022 17:03:09 +0100 Subject: [PATCH 37/53] Add more tests for parent-children checks (#775) Co-authored-by: Juleffel Co-authored-by: Juleffel --- .../active_record_adapter_spec.rb | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/spec/cancan/model_adapters/active_record_adapter_spec.rb b/spec/cancan/model_adapters/active_record_adapter_spec.rb index af89929c1..636dbf1f0 100644 --- a/spec/cancan/model_adapters/active_record_adapter_spec.rb +++ b/spec/cancan/model_adapters/active_record_adapter_spec.rb @@ -441,6 +441,35 @@ class User < ActiveRecord::Base ability.cannot :read, Article, :secret expect(Article.accessible_by(ability)).to eq([article]) end + + describe 'when can? is used with a Hash (nested resources)' do + it 'verifies parent equality correctly' do + user1 = User.create!(name: 'user1') + user2 = User.create!(name: 'user2') + category = Category.create!(name: 'cat') + article1 = Article.create!(name: 'article1', category: category, user: user1) + article2 = Article.create!(name: 'article2', category: category, user: user2) + comment1 = Comment.create!(article: article1) + comment2 = Comment.create!(article: article2) + + ability1 = Ability.new(user1) + ability1.can :read, Article + ability1.can :manage, Article, user: user1 + ability1.can :manage, Comment, article: user1.articles + + expect(ability1.can?(:manage, { article1 => Comment })).to eq(true) + expect(ability1.can?(:manage, { article2 => Comment })).to eq(false) + expect(ability1.can?(:manage, { article1 => comment1 })).to eq(true) + expect(ability1.can?(:manage, { article2 => comment2 })).to eq(false) + + ability2 = Ability.new(user2) + + expect(ability2.can?(:manage, { article1 => Comment })).to eq(false) + expect(ability2.can?(:manage, { article2 => Comment })).to eq(false) + expect(ability2.can?(:manage, { article1 => comment1 })).to eq(false) + expect(ability2.can?(:manage, { article2 => comment2 })).to eq(false) + end + end end end From f241c8a68ee9475dc30cd552a714a83a2f55ccce Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Tue, 22 Mar 2022 12:14:45 +0100 Subject: [PATCH 38/53] Remove warnings from specs (#776) --- .../model_adapters/active_record_adapter_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/cancan/model_adapters/active_record_adapter_spec.rb b/spec/cancan/model_adapters/active_record_adapter_spec.rb index 636dbf1f0..6fea75543 100644 --- a/spec/cancan/model_adapters/active_record_adapter_spec.rb +++ b/spec/cancan/model_adapters/active_record_adapter_spec.rb @@ -263,7 +263,7 @@ class User < ActiveRecord::Base it 'raises an exception when trying to merge scope with other conditions' do @ability.can :read, Article, published: true @ability.can :read, Article, Article.where(secret: true) - expect(-> { Article.accessible_by(@ability) }) + expect { Article.accessible_by(@ability) } .to raise_error(CanCan::Error, 'Unable to merge an Active Record scope with other conditions. '\ 'Instead use a hash or SQL for read Article ability.') @@ -273,20 +273,20 @@ class User < ActiveRecord::Base @ability.can :read, Article, published: true @ability.can :read, Article, Article.where(secret: true) @ability.cannot :read, Article - expect(-> { Article.accessible_by(@ability) }).not_to raise_error + expect { Article.accessible_by(@ability) }.not_to raise_error end it 'recognises empty scopes and compresses them' do @ability.can :read, Article, published: true @ability.can :read, Article, Article.all - expect(-> { Article.accessible_by(@ability) }).not_to raise_error + expect { Article.accessible_by(@ability) }.not_to raise_error end it 'does not allow to fetch records when ability with just block present' do @ability.can :read, Article do false end - expect(-> { Article.accessible_by(@ability) }).to raise_error(CanCan::Error) + expect { Article.accessible_by(@ability) }.to raise_error(CanCan::Error) end it 'should support more than one deeply nested conditions' do @@ -300,7 +300,7 @@ class User < ActiveRecord::Base it 'does not allow to check ability on object against SQL conditions without block' do @ability.can :read, Article, ['secret=?', true] - expect(-> { @ability.can? :read, Article.new }).to raise_error(CanCan::Error) + expect { @ability.can? :read, Article.new }.to raise_error(CanCan::Error) end it 'has false conditions if no abilities match' do From 3d43db273b390550fa2b52fd8b39f97f3327414e Mon Sep 17 00:00:00 2001 From: Peter Goldstein Date: Mon, 10 Jan 2022 00:22:06 -0800 Subject: [PATCH 39/53] Add Ruby 3.0 and Ruby 3.1 to CI --- .github/workflows/test.yml | 9 +++++++++ gemfiles/activerecord_7.0.0.gemfile | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 gemfiles/activerecord_7.0.0.gemfile diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 64bafec01..200b7116e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,6 +16,15 @@ jobs: matrix: ruby: ['2.6', '2.7', '3.0', '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_main.gemfile'] + include: + - gemfile: 'gemfiles/activerecord_7.0.0.gemfile' + ruby: '3.1' + - gemfile: 'gemfiles/activerecord_7.0.0.gemfile' + ruby: '3.0' + - gemfile: 'gemfiles/activerecord_6.1.0.gemfile' + ruby: '3.1' + - gemfile: 'gemfiles/activerecord_6.1.0.gemfile' + ruby: '3.0' exclude: - gemfile: 'gemfiles/activerecord_5.2.2.gemfile' ruby: '3.0' # rails 5.2 can't run on ruby 3.0 diff --git a/gemfiles/activerecord_7.0.0.gemfile b/gemfiles/activerecord_7.0.0.gemfile new file mode 100644 index 000000000..c930c7cdc --- /dev/null +++ b/gemfiles/activerecord_7.0.0.gemfile @@ -0,0 +1,20 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "actionpack", "~> 7.0.0", require: "action_pack" +gem "activerecord", "~> 7.0.0", require: "active_record" +gem "activesupport", "~> 7.0.0", require: "active_support/all" + +platforms :jruby do + gem "activerecord-jdbcsqlite3-adapter" + gem "jdbc-sqlite3" + gem "jdbc-postgres" +end + +platforms :ruby, :mswin, :mingw do + gem "pg", "~> 1.2.3" + gem "sqlite3", "~> 1.4.2" +end + +gemspec path: "../" From 6c7b6d89d17ec94bcf6a6a068269064377ed3f58 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Tue, 22 Mar 2022 14:01:27 +0100 Subject: [PATCH 40/53] Swap arguments of some methods to be consistent (#777) --- lib/cancan/conditions_matcher.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/cancan/conditions_matcher.rb b/lib/cancan/conditions_matcher.rb index 0eca798b7..c5bc1e45b 100644 --- a/lib/cancan/conditions_matcher.rb +++ b/lib/cancan/conditions_matcher.rb @@ -50,20 +50,21 @@ def matches_conditions_hash?(subject, conditions = @conditions) return adapter.matches_conditions_hash?(subject, conditions) end - matches_all_conditions?(adapter, conditions, subject) + matches_all_conditions?(adapter, subject, conditions) end - def matches_all_conditions?(adapter, conditions, subject) + def matches_all_conditions?(adapter, subject, conditions) if conditions.is_a?(Hash) - matches_hash_conditions(adapter, conditions, subject) + matches_hash_conditions(adapter, subject, conditions) elsif conditions.respond_to?(:include?) conditions.include?(subject) else + puts "does #{subject} match #{conditions}?" subject == conditions end end - def matches_hash_conditions(adapter, conditions, subject) + def matches_hash_conditions(adapter, subject, conditions) conditions.all? do |name, value| if adapter.override_condition_matching?(subject, name, value) adapter.matches_condition?(subject, name, value) From 7629f56a090183554a657bc4d3f63b10421ef430 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Tue, 22 Mar 2022 14:26:40 +0100 Subject: [PATCH 41/53] Drop ActiveRecord4 from CI (#778) --- .github/workflows/test.yml | 8 +---- Appraisals | 53 ++++++++++++++--------------- gemfiles/activerecord_4.2.0.gemfile | 21 ------------ gemfiles/activerecord_5.0.2.gemfile | 2 +- gemfiles/activerecord_5.1.0.gemfile | 4 +-- gemfiles/activerecord_5.2.2.gemfile | 4 +-- gemfiles/activerecord_6.0.0.gemfile | 4 +-- gemfiles/activerecord_6.1.0.gemfile | 2 +- gemfiles/activerecord_7.0.0.gemfile | 2 +- gemfiles/activerecord_main.gemfile | 2 +- 10 files changed, 37 insertions(+), 65 deletions(-) delete mode 100644 gemfiles/activerecord_4.2.0.gemfile diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 200b7116e..08d0a5afe 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: fail-fast: false matrix: ruby: ['2.6', '2.7', '3.0', '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_main.gemfile'] + gemfile: ['gemfiles/activerecord_5.0.2.gemfile', 'gemfiles/activerecord_5.1.0.gemfile', 'gemfiles/activerecord_5.2.2.gemfile', 'gemfiles/activerecord_6.0.0.gemfile', 'gemfiles/activerecord_6.1.0.gemfile', 'gemfiles/activerecord_main.gemfile'] include: - gemfile: 'gemfiles/activerecord_7.0.0.gemfile' ruby: '3.1' @@ -34,12 +34,6 @@ jobs: ruby: '3.0' # rails 5.0 can't run on ruby 3.0 - gemfile: 'gemfiles/activerecord_5.0.2.gemfile' ruby: '3.0' # rails 5.0 can't run on ruby 3.0 - - gemfile: 'gemfiles/activerecord_4.2.0.gemfile' - ruby: '3.0' # rails 4.2 can't run on ruby 3.0 - - 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: 'truffleruby' # TruffleRuby 21.0 targets Ruby 2.7, same as above - gemfile: 'gemfiles/activerecord_5.0.2.gemfile' ruby: 'truffleruby' # TruffleRuby 21.0 targets Ruby 2.7, same as above - gemfile: 'gemfiles/activerecord_5.1.0.gemfile' diff --git a/Appraisals b/Appraisals index af06018ba..d025b2d22 100644 --- a/Appraisals +++ b/Appraisals @@ -1,21 +1,3 @@ -appraise 'activerecord_4.2.0' do - gem 'activerecord', '~> 4.2.0', require: 'active_record' - gem 'activesupport', '~> 4.2.0', require: 'active_support/all' - gem 'actionpack', '~> 4.2.0', require: 'action_pack' - gem 'nokogiri', '~> 1.6.8', require: 'nokogiri' # TODO: fix for ruby 2.0.0 - - gemfile.platforms :jruby do - gem 'activerecord-jdbcsqlite3-adapter', '~> 1.3.24' - gem 'jdbc-sqlite3' - gem 'jdbc-postgres' - end - - gemfile.platforms :ruby, :mswin, :mingw do - gem 'sqlite3', '~> 1.3.0' - gem 'pg', '~> 0.21' - end -end - appraise 'activerecord_5.0.2' do gem 'activerecord', '~> 5.0.2', require: 'active_record' gem 'activesupport', '~> 5.0.2', require: 'active_support/all' @@ -28,8 +10,8 @@ appraise 'activerecord_5.0.2' do end gemfile.platforms :ruby, :mswin, :mingw do + gem 'pg', '~> 1.3.4' gem 'sqlite3', '~> 1.3.0' - gem 'pg', '~> 0.21' end end @@ -45,8 +27,8 @@ appraise 'activerecord_5.1.0' do end gemfile.platforms :ruby, :mswin, :mingw do - gem 'sqlite3', '~> 1.3.0' - gem 'pg', '~> 0.21' + gem 'pg', '~> 1.3.4' + gem 'sqlite3', '~> 1.4.2' end end @@ -62,8 +44,8 @@ appraise 'activerecord_5.2.2' do end gemfile.platforms :ruby, :mswin, :mingw do - gem 'sqlite3', '~> 1.3.0' - gem 'pg', '~> 0.21' + gem 'pg', '~> 1.3.4' + gem 'sqlite3', '~> 1.4.2' end end @@ -79,8 +61,8 @@ appraise 'activerecord_6.0.0' do end platforms :ruby, :mswin, :mingw do - gem 'pg', '~> 1.1.4' - gem 'sqlite3', '~> 1.4.0' + gem 'pg', '~> 1.3.4' + gem 'sqlite3', '~> 1.4.2' end end @@ -96,7 +78,24 @@ appraise 'activerecord_6.1.0' do end platforms :ruby, :mswin, :mingw do - gem 'pg', '~> 1.2.3' + gem 'pg', '~> 1.3.4' + gem 'sqlite3', '~> 1.4.2' + end +end + +appraise 'activerecord_7.0.0' do + gem 'actionpack', '~> 7.0.0', require: 'action_pack' + gem 'activerecord', '~> 7.0.0', require: 'active_record' + gem 'activesupport', '~> 7.0.0', require: 'active_support/all' + + platforms :jruby do + gem 'activerecord-jdbcsqlite3-adapter' + gem 'jdbc-sqlite3' + gem 'jdbc-postgres' + end + + platforms :ruby, :mswin, :mingw do + gem 'pg', '~> 1.3.4' gem 'sqlite3', '~> 1.4.2' end end @@ -115,7 +114,7 @@ appraise 'activerecord_main' do end platforms :ruby, :mswin, :mingw do - gem 'pg', '~> 1.2.3' + gem 'pg', '~> 1.3.4' gem 'sqlite3', '~> 1.4.2' end end diff --git a/gemfiles/activerecord_4.2.0.gemfile b/gemfiles/activerecord_4.2.0.gemfile deleted file mode 100644 index f564b76c1..000000000 --- a/gemfiles/activerecord_4.2.0.gemfile +++ /dev/null @@ -1,21 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "activerecord", "~> 4.2.0", require: "active_record" -gem "activesupport", "~> 4.2.0", require: "active_support/all" -gem "actionpack", "~> 4.2.0", require: "action_pack" -gem "nokogiri", "~> 1.6.8", require: "nokogiri" - -platforms :jruby do - gem "activerecord-jdbcsqlite3-adapter", "~> 1.3.24" - gem "jdbc-sqlite3" - gem "jdbc-postgres" -end - -platforms :ruby, :mswin, :mingw do - gem "sqlite3", "~> 1.3.0" - gem "pg", "~> 0.21" -end - -gemspec path: "../" diff --git a/gemfiles/activerecord_5.0.2.gemfile b/gemfiles/activerecord_5.0.2.gemfile index bec9ffcf3..8f0cb561a 100644 --- a/gemfiles/activerecord_5.0.2.gemfile +++ b/gemfiles/activerecord_5.0.2.gemfile @@ -13,8 +13,8 @@ platforms :jruby do end platforms :ruby, :mswin, :mingw do + gem "pg", "~> 1.3.4" gem "sqlite3", "~> 1.3.0" - gem "pg", "~> 0.21" end gemspec path: "../" diff --git a/gemfiles/activerecord_5.1.0.gemfile b/gemfiles/activerecord_5.1.0.gemfile index ff8f083b9..fe5514f1a 100644 --- a/gemfiles/activerecord_5.1.0.gemfile +++ b/gemfiles/activerecord_5.1.0.gemfile @@ -13,8 +13,8 @@ platforms :jruby do end platforms :ruby, :mswin, :mingw do - gem "sqlite3", "~> 1.3.0" - gem "pg", "~> 0.21" + gem "pg", "~> 1.3.4" + gem "sqlite3", "~> 1.4.2" end gemspec path: "../" diff --git a/gemfiles/activerecord_5.2.2.gemfile b/gemfiles/activerecord_5.2.2.gemfile index 1389b1b70..63ce61b4e 100644 --- a/gemfiles/activerecord_5.2.2.gemfile +++ b/gemfiles/activerecord_5.2.2.gemfile @@ -13,8 +13,8 @@ platforms :jruby do end platforms :ruby, :mswin, :mingw do - gem "sqlite3", "~> 1.3.0" - gem "pg", "~> 0.21" + gem "pg", "~> 1.3.4" + gem "sqlite3", "~> 1.4.2" end gemspec path: "../" diff --git a/gemfiles/activerecord_6.0.0.gemfile b/gemfiles/activerecord_6.0.0.gemfile index ad0c46c1a..e9f11ddd0 100644 --- a/gemfiles/activerecord_6.0.0.gemfile +++ b/gemfiles/activerecord_6.0.0.gemfile @@ -13,8 +13,8 @@ platforms :jruby do end platforms :ruby, :mswin, :mingw do - gem "pg", "~> 1.1.4" - gem "sqlite3", "~> 1.4.0" + gem "pg", "~> 1.3.4" + gem "sqlite3", "~> 1.4.2" end gemspec path: "../" diff --git a/gemfiles/activerecord_6.1.0.gemfile b/gemfiles/activerecord_6.1.0.gemfile index 857cfa4e2..88580ffa1 100644 --- a/gemfiles/activerecord_6.1.0.gemfile +++ b/gemfiles/activerecord_6.1.0.gemfile @@ -13,7 +13,7 @@ platforms :jruby do end platforms :ruby, :mswin, :mingw do - gem "pg", "~> 1.2.3" + gem "pg", "~> 1.3.4" gem "sqlite3", "~> 1.4.2" end diff --git a/gemfiles/activerecord_7.0.0.gemfile b/gemfiles/activerecord_7.0.0.gemfile index c930c7cdc..1f0c169dd 100644 --- a/gemfiles/activerecord_7.0.0.gemfile +++ b/gemfiles/activerecord_7.0.0.gemfile @@ -13,7 +13,7 @@ platforms :jruby do end platforms :ruby, :mswin, :mingw do - gem "pg", "~> 1.2.3" + gem "pg", "~> 1.3.4" gem "sqlite3", "~> 1.4.2" end diff --git a/gemfiles/activerecord_main.gemfile b/gemfiles/activerecord_main.gemfile index 46f55df29..ba43821e8 100644 --- a/gemfiles/activerecord_main.gemfile +++ b/gemfiles/activerecord_main.gemfile @@ -15,7 +15,7 @@ platforms :jruby do end platforms :ruby, :mswin, :mingw do - gem "pg", "~> 1.2.3" + gem "pg", "~> 1.3.4" gem "sqlite3", "~> 1.4.2" end From 00a19292b14992ab46cbec2e10d49316608b6eb5 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Mon, 21 Mar 2022 17:03:09 +0100 Subject: [PATCH 42/53] Add more tests for parent-children checks (#775) Co-authored-by: Juleffel Co-authored-by: Juleffel --- .../active_record_adapter_spec.rb | 51 +++++++++---------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/spec/cancan/model_adapters/active_record_adapter_spec.rb b/spec/cancan/model_adapters/active_record_adapter_spec.rb index f6932d70e..a641e1b00 100644 --- a/spec/cancan/model_adapters/active_record_adapter_spec.rb +++ b/spec/cancan/model_adapters/active_record_adapter_spec.rb @@ -445,39 +445,36 @@ class User < ActiveRecord::Base end describe 'when can? is used with a Hash (nested resources)' do - it 'verifies parent_id key in conditions' do + it 'verifies parent equality correctly' do user1 = User.create!(name: 'user1') user2 = User.create!(name: 'user2') - cat = Category.create!(name: 'cat') - proj = Project.create!(name: 'proj') - art1 = Article.create!(name: 'art1', category: cat, project: proj, user: user1) - art2 = Article.create!(name: 'art1', category: cat, project: proj, user: user2) - comm1 = Comment.create!(article: art1, project: proj) - comm2 = Comment.create!(article: art2, project: proj) - - ability = Ability.new(user1) - ability.can :read, Article - ability.can :manage, Article, user_id: user1.id - ability.can :manage, Comment, article_id: user1.article_ids + category = Category.create!(name: 'cat') + article1 = Article.create!(name: 'article1', category: category, user: user1) + article2 = Article.create!(name: 'article2', category: category, user: user2) + comment1 = Comment.create!(article: article1) + comment2 = Comment.create!(article: article2) - expect(ability.can?(:manage, {art1 => Comment})).to eq(true) - expect(ability.can?(:manage, {art2 => Comment})).to eq(false) - end + ability1 = Ability.new(user1) + ability1.can :read, Article + ability1.can :manage, Article, user: user1 + ability1.can :manage, Comment, article: user1.articles + + expect(ability1.can?(:manage, { article1 => Comment })).to eq(true) + expect(ability1.can?(:manage, { article2 => Comment })).to eq(false) + expect(ability1.can?(:manage, { article1 => comment1 })).to eq(true) + expect(ability1.can?(:manage, { article2 => comment2 })).to eq(false) + + ability2 = Ability.new(user2) + + expect(ability2.can?(:manage, { article1 => Comment })).to eq(false) + expect(ability2.can?(:manage, { article2 => Comment })).to eq(false) + expect(ability2.can?(:manage, { article1 => comment1 })).to eq(false) + expect(ability2.can?(:manage, { article2 => comment2 })).to eq(false) - it 'verifies parent equality correctly' do - user1 = User.create!(name: 'user1') - user2 = User.create!(name: 'user2') - cat = Category.create!(name: 'cat') - proj = Project.create!(name: 'proj') - art1 = Article.create!(name: 'art1', category: cat, project: proj, user: user1) - art2 = Article.create!(name: 'art1', category: cat, project: proj, user: user2) - comm1 = Comment.create!(article: art1, project: proj) - comm2 = Comment.create!(article: art2, project: proj) - ability = Ability.new(user1) ability.can :read, Article - ability.can :manage, Article, user: user1 - ability.can :manage, Comment, article: user1.articles + ability.can :manage, Article, user_id: user1.id + ability.can :manage, Comment, article_id: user1.article_ids expect(ability.can?(:manage, {art1 => Comment})).to eq(true) expect(ability.can?(:manage, {art2 => Comment})).to eq(false) From e769942e9221959eac4d4bf45d8d3e712b27cb02 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Tue, 22 Mar 2022 12:14:45 +0100 Subject: [PATCH 43/53] Remove warnings from specs (#776) --- .../model_adapters/active_record_adapter_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/cancan/model_adapters/active_record_adapter_spec.rb b/spec/cancan/model_adapters/active_record_adapter_spec.rb index a641e1b00..01868de14 100644 --- a/spec/cancan/model_adapters/active_record_adapter_spec.rb +++ b/spec/cancan/model_adapters/active_record_adapter_spec.rb @@ -265,7 +265,7 @@ class User < ActiveRecord::Base it 'raises an exception when trying to merge scope with other conditions' do @ability.can :read, Article, published: true @ability.can :read, Article, Article.where(secret: true) - expect(-> { Article.accessible_by(@ability) }) + expect { Article.accessible_by(@ability) } .to raise_error(CanCan::Error, 'Unable to merge an Active Record scope with other conditions. '\ 'Instead use a hash or SQL for read Article ability.') @@ -275,20 +275,20 @@ class User < ActiveRecord::Base @ability.can :read, Article, published: true @ability.can :read, Article, Article.where(secret: true) @ability.cannot :read, Article - expect(-> { Article.accessible_by(@ability) }).not_to raise_error + expect { Article.accessible_by(@ability) }.not_to raise_error end it 'recognises empty scopes and compresses them' do @ability.can :read, Article, published: true @ability.can :read, Article, Article.all - expect(-> { Article.accessible_by(@ability) }).not_to raise_error + expect { Article.accessible_by(@ability) }.not_to raise_error end it 'does not allow to fetch records when ability with just block present' do @ability.can :read, Article do false end - expect(-> { Article.accessible_by(@ability) }).to raise_error(CanCan::Error) + expect { Article.accessible_by(@ability) }.to raise_error(CanCan::Error) end it 'should support more than one deeply nested conditions' do @@ -302,7 +302,7 @@ class User < ActiveRecord::Base it 'does not allow to check ability on object against SQL conditions without block' do @ability.can :read, Article, ['secret=?', true] - expect(-> { @ability.can? :read, Article.new }).to raise_error(CanCan::Error) + expect { @ability.can? :read, Article.new }.to raise_error(CanCan::Error) end it 'has false conditions if no abilities match' do From e092f0df89e908ce91ec71608b629a818515041a Mon Sep 17 00:00:00 2001 From: Peter Goldstein Date: Mon, 10 Jan 2022 00:22:06 -0800 Subject: [PATCH 44/53] Add Ruby 3.0 and Ruby 3.1 to CI --- .github/workflows/test.yml | 9 +++++++++ gemfiles/activerecord_7.0.0.gemfile | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 gemfiles/activerecord_7.0.0.gemfile diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 64bafec01..200b7116e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,6 +16,15 @@ jobs: matrix: ruby: ['2.6', '2.7', '3.0', '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_main.gemfile'] + include: + - gemfile: 'gemfiles/activerecord_7.0.0.gemfile' + ruby: '3.1' + - gemfile: 'gemfiles/activerecord_7.0.0.gemfile' + ruby: '3.0' + - gemfile: 'gemfiles/activerecord_6.1.0.gemfile' + ruby: '3.1' + - gemfile: 'gemfiles/activerecord_6.1.0.gemfile' + ruby: '3.0' exclude: - gemfile: 'gemfiles/activerecord_5.2.2.gemfile' ruby: '3.0' # rails 5.2 can't run on ruby 3.0 diff --git a/gemfiles/activerecord_7.0.0.gemfile b/gemfiles/activerecord_7.0.0.gemfile new file mode 100644 index 000000000..c930c7cdc --- /dev/null +++ b/gemfiles/activerecord_7.0.0.gemfile @@ -0,0 +1,20 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "actionpack", "~> 7.0.0", require: "action_pack" +gem "activerecord", "~> 7.0.0", require: "active_record" +gem "activesupport", "~> 7.0.0", require: "active_support/all" + +platforms :jruby do + gem "activerecord-jdbcsqlite3-adapter" + gem "jdbc-sqlite3" + gem "jdbc-postgres" +end + +platforms :ruby, :mswin, :mingw do + gem "pg", "~> 1.2.3" + gem "sqlite3", "~> 1.4.2" +end + +gemspec path: "../" From dcb429aef63910130811c7f212772b7fa918a926 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Tue, 22 Mar 2022 14:01:27 +0100 Subject: [PATCH 45/53] Swap arguments of some methods to be consistent (#777) --- lib/cancan/conditions_matcher.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/cancan/conditions_matcher.rb b/lib/cancan/conditions_matcher.rb index df8cd5261..0f55bd692 100644 --- a/lib/cancan/conditions_matcher.rb +++ b/lib/cancan/conditions_matcher.rb @@ -59,21 +59,21 @@ def matches_conditions_hash?(subject, conditions = @conditions) return adapter.matches_conditions_hash?(subject, conditions) end - matches_all_conditions?(adapter, conditions, subject) + matches_all_conditions?(adapter, subject, conditions) end - # conditions can be a collection, object, or hash - def matches_all_conditions?(adapter, conditions, subject) + def matches_all_conditions?(adapter, subject, conditions) if conditions.is_a?(Hash) - matches_hash_conditions(adapter, conditions, subject) + matches_hash_conditions(adapter, subject, conditions) elsif conditions.respond_to?(:include?) conditions.include?(subject) else + puts "does #{subject} match #{conditions}?" subject == conditions end end - def matches_hash_conditions(adapter, conditions, subject) + def matches_hash_conditions(adapter, subject, conditions) conditions.all? do |name, value| if adapter.override_condition_matching?(subject, name, value) adapter.matches_condition?(subject, name, value) From 051899d5c10f37ff1b8445ff5c0d2352597ef8b3 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Tue, 22 Mar 2022 14:26:40 +0100 Subject: [PATCH 46/53] Drop ActiveRecord4 from CI (#778) --- .github/workflows/test.yml | 8 +---- Appraisals | 53 ++++++++++++++--------------- gemfiles/activerecord_4.2.0.gemfile | 21 ------------ gemfiles/activerecord_5.0.2.gemfile | 2 +- gemfiles/activerecord_5.1.0.gemfile | 4 +-- gemfiles/activerecord_5.2.2.gemfile | 4 +-- gemfiles/activerecord_6.0.0.gemfile | 4 +-- gemfiles/activerecord_6.1.0.gemfile | 2 +- gemfiles/activerecord_7.0.0.gemfile | 2 +- gemfiles/activerecord_main.gemfile | 2 +- 10 files changed, 37 insertions(+), 65 deletions(-) delete mode 100644 gemfiles/activerecord_4.2.0.gemfile diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 200b7116e..08d0a5afe 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,7 +15,7 @@ jobs: fail-fast: false matrix: ruby: ['2.6', '2.7', '3.0', '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_main.gemfile'] + gemfile: ['gemfiles/activerecord_5.0.2.gemfile', 'gemfiles/activerecord_5.1.0.gemfile', 'gemfiles/activerecord_5.2.2.gemfile', 'gemfiles/activerecord_6.0.0.gemfile', 'gemfiles/activerecord_6.1.0.gemfile', 'gemfiles/activerecord_main.gemfile'] include: - gemfile: 'gemfiles/activerecord_7.0.0.gemfile' ruby: '3.1' @@ -34,12 +34,6 @@ jobs: ruby: '3.0' # rails 5.0 can't run on ruby 3.0 - gemfile: 'gemfiles/activerecord_5.0.2.gemfile' ruby: '3.0' # rails 5.0 can't run on ruby 3.0 - - gemfile: 'gemfiles/activerecord_4.2.0.gemfile' - ruby: '3.0' # rails 4.2 can't run on ruby 3.0 - - 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: 'truffleruby' # TruffleRuby 21.0 targets Ruby 2.7, same as above - gemfile: 'gemfiles/activerecord_5.0.2.gemfile' ruby: 'truffleruby' # TruffleRuby 21.0 targets Ruby 2.7, same as above - gemfile: 'gemfiles/activerecord_5.1.0.gemfile' diff --git a/Appraisals b/Appraisals index af06018ba..d025b2d22 100644 --- a/Appraisals +++ b/Appraisals @@ -1,21 +1,3 @@ -appraise 'activerecord_4.2.0' do - gem 'activerecord', '~> 4.2.0', require: 'active_record' - gem 'activesupport', '~> 4.2.0', require: 'active_support/all' - gem 'actionpack', '~> 4.2.0', require: 'action_pack' - gem 'nokogiri', '~> 1.6.8', require: 'nokogiri' # TODO: fix for ruby 2.0.0 - - gemfile.platforms :jruby do - gem 'activerecord-jdbcsqlite3-adapter', '~> 1.3.24' - gem 'jdbc-sqlite3' - gem 'jdbc-postgres' - end - - gemfile.platforms :ruby, :mswin, :mingw do - gem 'sqlite3', '~> 1.3.0' - gem 'pg', '~> 0.21' - end -end - appraise 'activerecord_5.0.2' do gem 'activerecord', '~> 5.0.2', require: 'active_record' gem 'activesupport', '~> 5.0.2', require: 'active_support/all' @@ -28,8 +10,8 @@ appraise 'activerecord_5.0.2' do end gemfile.platforms :ruby, :mswin, :mingw do + gem 'pg', '~> 1.3.4' gem 'sqlite3', '~> 1.3.0' - gem 'pg', '~> 0.21' end end @@ -45,8 +27,8 @@ appraise 'activerecord_5.1.0' do end gemfile.platforms :ruby, :mswin, :mingw do - gem 'sqlite3', '~> 1.3.0' - gem 'pg', '~> 0.21' + gem 'pg', '~> 1.3.4' + gem 'sqlite3', '~> 1.4.2' end end @@ -62,8 +44,8 @@ appraise 'activerecord_5.2.2' do end gemfile.platforms :ruby, :mswin, :mingw do - gem 'sqlite3', '~> 1.3.0' - gem 'pg', '~> 0.21' + gem 'pg', '~> 1.3.4' + gem 'sqlite3', '~> 1.4.2' end end @@ -79,8 +61,8 @@ appraise 'activerecord_6.0.0' do end platforms :ruby, :mswin, :mingw do - gem 'pg', '~> 1.1.4' - gem 'sqlite3', '~> 1.4.0' + gem 'pg', '~> 1.3.4' + gem 'sqlite3', '~> 1.4.2' end end @@ -96,7 +78,24 @@ appraise 'activerecord_6.1.0' do end platforms :ruby, :mswin, :mingw do - gem 'pg', '~> 1.2.3' + gem 'pg', '~> 1.3.4' + gem 'sqlite3', '~> 1.4.2' + end +end + +appraise 'activerecord_7.0.0' do + gem 'actionpack', '~> 7.0.0', require: 'action_pack' + gem 'activerecord', '~> 7.0.0', require: 'active_record' + gem 'activesupport', '~> 7.0.0', require: 'active_support/all' + + platforms :jruby do + gem 'activerecord-jdbcsqlite3-adapter' + gem 'jdbc-sqlite3' + gem 'jdbc-postgres' + end + + platforms :ruby, :mswin, :mingw do + gem 'pg', '~> 1.3.4' gem 'sqlite3', '~> 1.4.2' end end @@ -115,7 +114,7 @@ appraise 'activerecord_main' do end platforms :ruby, :mswin, :mingw do - gem 'pg', '~> 1.2.3' + gem 'pg', '~> 1.3.4' gem 'sqlite3', '~> 1.4.2' end end diff --git a/gemfiles/activerecord_4.2.0.gemfile b/gemfiles/activerecord_4.2.0.gemfile deleted file mode 100644 index f564b76c1..000000000 --- a/gemfiles/activerecord_4.2.0.gemfile +++ /dev/null @@ -1,21 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "activerecord", "~> 4.2.0", require: "active_record" -gem "activesupport", "~> 4.2.0", require: "active_support/all" -gem "actionpack", "~> 4.2.0", require: "action_pack" -gem "nokogiri", "~> 1.6.8", require: "nokogiri" - -platforms :jruby do - gem "activerecord-jdbcsqlite3-adapter", "~> 1.3.24" - gem "jdbc-sqlite3" - gem "jdbc-postgres" -end - -platforms :ruby, :mswin, :mingw do - gem "sqlite3", "~> 1.3.0" - gem "pg", "~> 0.21" -end - -gemspec path: "../" diff --git a/gemfiles/activerecord_5.0.2.gemfile b/gemfiles/activerecord_5.0.2.gemfile index bec9ffcf3..8f0cb561a 100644 --- a/gemfiles/activerecord_5.0.2.gemfile +++ b/gemfiles/activerecord_5.0.2.gemfile @@ -13,8 +13,8 @@ platforms :jruby do end platforms :ruby, :mswin, :mingw do + gem "pg", "~> 1.3.4" gem "sqlite3", "~> 1.3.0" - gem "pg", "~> 0.21" end gemspec path: "../" diff --git a/gemfiles/activerecord_5.1.0.gemfile b/gemfiles/activerecord_5.1.0.gemfile index ff8f083b9..fe5514f1a 100644 --- a/gemfiles/activerecord_5.1.0.gemfile +++ b/gemfiles/activerecord_5.1.0.gemfile @@ -13,8 +13,8 @@ platforms :jruby do end platforms :ruby, :mswin, :mingw do - gem "sqlite3", "~> 1.3.0" - gem "pg", "~> 0.21" + gem "pg", "~> 1.3.4" + gem "sqlite3", "~> 1.4.2" end gemspec path: "../" diff --git a/gemfiles/activerecord_5.2.2.gemfile b/gemfiles/activerecord_5.2.2.gemfile index 1389b1b70..63ce61b4e 100644 --- a/gemfiles/activerecord_5.2.2.gemfile +++ b/gemfiles/activerecord_5.2.2.gemfile @@ -13,8 +13,8 @@ platforms :jruby do end platforms :ruby, :mswin, :mingw do - gem "sqlite3", "~> 1.3.0" - gem "pg", "~> 0.21" + gem "pg", "~> 1.3.4" + gem "sqlite3", "~> 1.4.2" end gemspec path: "../" diff --git a/gemfiles/activerecord_6.0.0.gemfile b/gemfiles/activerecord_6.0.0.gemfile index ad0c46c1a..e9f11ddd0 100644 --- a/gemfiles/activerecord_6.0.0.gemfile +++ b/gemfiles/activerecord_6.0.0.gemfile @@ -13,8 +13,8 @@ platforms :jruby do end platforms :ruby, :mswin, :mingw do - gem "pg", "~> 1.1.4" - gem "sqlite3", "~> 1.4.0" + gem "pg", "~> 1.3.4" + gem "sqlite3", "~> 1.4.2" end gemspec path: "../" diff --git a/gemfiles/activerecord_6.1.0.gemfile b/gemfiles/activerecord_6.1.0.gemfile index 857cfa4e2..88580ffa1 100644 --- a/gemfiles/activerecord_6.1.0.gemfile +++ b/gemfiles/activerecord_6.1.0.gemfile @@ -13,7 +13,7 @@ platforms :jruby do end platforms :ruby, :mswin, :mingw do - gem "pg", "~> 1.2.3" + gem "pg", "~> 1.3.4" gem "sqlite3", "~> 1.4.2" end diff --git a/gemfiles/activerecord_7.0.0.gemfile b/gemfiles/activerecord_7.0.0.gemfile index c930c7cdc..1f0c169dd 100644 --- a/gemfiles/activerecord_7.0.0.gemfile +++ b/gemfiles/activerecord_7.0.0.gemfile @@ -13,7 +13,7 @@ platforms :jruby do end platforms :ruby, :mswin, :mingw do - gem "pg", "~> 1.2.3" + gem "pg", "~> 1.3.4" gem "sqlite3", "~> 1.4.2" end diff --git a/gemfiles/activerecord_main.gemfile b/gemfiles/activerecord_main.gemfile index 46f55df29..ba43821e8 100644 --- a/gemfiles/activerecord_main.gemfile +++ b/gemfiles/activerecord_main.gemfile @@ -15,7 +15,7 @@ platforms :jruby do end platforms :ruby, :mswin, :mingw do - gem "pg", "~> 1.2.3" + gem "pg", "~> 1.3.4" gem "sqlite3", "~> 1.4.2" end From d0ea89a2b762e6facf1db59dc23cc8a62ceab0a9 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Tue, 22 Mar 2022 15:51:54 +0100 Subject: [PATCH 47/53] Improve support for nested resources --- .rubocop.yml | 4 + README.md | 3 + lib/cancan/conditions_matcher.rb | 11 +-- lib/cancan/model_adapters/abstract_adapter.rb | 3 +- .../model_adapters/active_record_adapter.rb | 23 +++-- rubymine_rspec.png | Bin 0 -> 228194 bytes .../active_record_adapter_spec.rb | 86 ++++++++++++------ 7 files changed, 89 insertions(+), 41 deletions(-) create mode 100644 rubymine_rspec.png diff --git a/.rubocop.yml b/.rubocop.yml index 1b0e44d89..7456684e5 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -20,6 +20,10 @@ Metrics/BlockLength: - 'lib/cancan/matchers.rb' - '**/*_spec.rb' +Metrics/ClassLength: + Exclude: + - 'lib/cancan/model_adapters/active_record_adapter.rb' + # TODO # Offense count: 2 # Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist. diff --git a/README.md b/README.md index 0b2457e51..47a5fcc6c 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,9 @@ When first developing, you need to run `bundle install` and then `bundle exec ap 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`. +If you use RubyMine, you can run RSpec tests by configuring the RSpec configuration template like this: +![rubymine_rspec.png](rubymine_rspec.png) + See the [CONTRIBUTING](./CONTRIBUTING.md) for more information. ## Special Thanks diff --git a/lib/cancan/conditions_matcher.rb b/lib/cancan/conditions_matcher.rb index 0f55bd692..b30d3e2a4 100644 --- a/lib/cancan/conditions_matcher.rb +++ b/lib/cancan/conditions_matcher.rb @@ -35,15 +35,14 @@ def matches_non_block_conditions(subject) def nested_subject_matches_conditions?(subject_hash) parent, child = subject_hash.first - matches_base_parent_conditions = matches_conditions_hash?(parent, @conditions[parent.class.name.downcase.to_sym] || {}) + matches_base_parent_conditions = matches_conditions_hash?(parent, + @conditions[parent.class.name.downcase.to_sym] || {}) adapter = model_adapter(parent) - if adapter.override_nested_subject_conditions_matching?(parent, child, @conditions) - return matches_base_parent_conditions && adapter.nested_subject_matches_conditions?(parent, child, @conditions) - end - - matches_base_parent_conditions + matches_base_parent_conditions && + (!adapter.override_nested_subject_conditions_matching?(parent, child, @conditions) || + adapter.nested_subject_matches_conditions?(parent, child, @conditions)) end # Checks if the given subject matches the given conditions hash. diff --git a/lib/cancan/model_adapters/abstract_adapter.rb b/lib/cancan/model_adapters/abstract_adapter.rb index 7e1a2ffa6..4041dcb49 100644 --- a/lib/cancan/model_adapters/abstract_adapter.rb +++ b/lib/cancan/model_adapters/abstract_adapter.rb @@ -35,7 +35,8 @@ def self.matches_conditions_hash?(_subject, _conditions) raise NotImplemented, 'This model adapter does not support matching on a conditions hash.' end - # Used above override_conditions_hash_matching to determine if this model adapter will override the matching behavior for nested subject. + # Used above override_conditions_hash_matching to determine if this model adapter will override the + # matching behavior for nested subject. # If this returns true then nested_subject_matches_conditions? will be called. def self.override_nested_subject_conditions_matching?(_parent, _child, _all_conditions) false diff --git a/lib/cancan/model_adapters/active_record_adapter.rb b/lib/cancan/model_adapters/active_record_adapter.rb index c125cd055..e3e84c06b 100644 --- a/lib/cancan/model_adapters/active_record_adapter.rb +++ b/lib/cancan/model_adapters/active_record_adapter.rb @@ -21,19 +21,28 @@ def initialize(model_class, rules) end class << self - # When belongs_to parent_id is a condition for a model, we want to check the parent when testing ability for a hash {parent => model} - def override_nested_subject_conditions_matching?(parent, _child, all_conditions) - all_conditions[:"#{parent.class.name.downcase}_id"].present? + # When belongs_to parent_id is a condition for a model, + # we want to check the parent when testing ability for a hash {parent => model} + def override_nested_subject_conditions_matching?(parent, child, all_conditions) + parent_child_conditions(parent, child, all_conditions).present? end - + # parent_id condition can be an array of integer or one integer, we check the parent against this - def nested_subject_matches_conditions?(parent, _child, all_conditions) - id_condition = all_conditions[:"#{parent.class.name.downcase}_id"] + def nested_subject_matches_conditions?(parent, child, all_conditions) + id_condition = parent_child_conditions(parent, child, all_conditions) return id_condition.include?(parent.id) if id_condition.is_a? Array return id_condition == parent.id if id_condition.is_a? Integer - + false end + + def parent_child_conditions(parent, child, all_conditions) + child_class = child.is_a?(Class) ? child : child.class + foreign_key = child_class.reflect_on_all_associations(:belongs_to).find do |association| + association.klass == parent.class + end&.foreign_key&.to_sym + foreign_key.nil? ? nil : all_conditions[foreign_key] + end end # Returns conditions intended to be used inside a database query. Normally you will not call this diff --git a/rubymine_rspec.png b/rubymine_rspec.png new file mode 100644 index 0000000000000000000000000000000000000000..03a952661b6f155ba408370f64a69037ef922f2c GIT binary patch literal 228194 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

M)%vI=S% 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 Y_q&uB%d5?cA_gX7q4E7b!jck2@ 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 Comment })).to eq(true) - expect(ability1.can?(:manage, { article2 => Comment })).to eq(false) - expect(ability1.can?(:manage, { article1 => comment1 })).to eq(true) - expect(ability1.can?(:manage, { article2 => comment2 })).to eq(false) + context 'when conditions are defined using the parent model' do + let(:ability) do + Ability.new(user1).tap do |ability| + ability.can :read, Article + ability.can :manage, Article, user: user1 + ability.can :manage, Comment, article: user1.articles + ability.can :manage, LegacyComment, article: user1.articles + end + end - ability2 = Ability.new(user2) + it 'verifies parent equality correctly' do + expect(ability.can?(:manage, { @article1 => Comment })).to eq(true) + expect(ability.can?(:manage, { @article1 => LegacyComment })).to eq(true) + expect(ability.can?(:manage, { @article1 => @comment1 })).to eq(true) + expect(ability.can?(:manage, { @article1 => @legacy_comment1 })).to eq(true) - expect(ability2.can?(:manage, { article1 => Comment })).to eq(false) - expect(ability2.can?(:manage, { article2 => Comment })).to eq(false) - expect(ability2.can?(:manage, { article1 => comment1 })).to eq(false) - expect(ability2.can?(:manage, { article2 => comment2 })).to eq(false) + expect(ability.can?(:manage, { @article2 => Comment })).to eq(false) + expect(ability.can?(:manage, { @article2 => LegacyComment })).to eq(false) + expect(ability.can?(:manage, { @article2 => @legacy_comment2 })).to eq(false) + end + end - ability = Ability.new(user1) + context 'when conditions are defined using the parent id' do + let(:ability) do + Ability.new(user1).tap do |ability| ability.can :read, Article ability.can :manage, Article, user_id: user1.id ability.can :manage, Comment, article_id: user1.article_ids - - expect(ability.can?(:manage, {art1 => Comment})).to eq(true) - expect(ability.can?(:manage, {art2 => Comment})).to eq(false) + ability.can :manage, LegacyComment, post_id: user1.article_ids end end + + it 'verifies parent equality correctly' do + expect(ability.can?(:manage, { @article1 => Comment })).to eq(true) + expect(ability.can?(:manage, { @article1 => LegacyComment })).to eq(true) + expect(ability.can?(:manage, { @article1 => @comment1 })).to eq(true) + expect(ability.can?(:manage, { @article1 => @legacy_comment1 })).to eq(true) + + expect(ability.can?(:manage, { @article2 => Comment })).to eq(false) + expect(ability.can?(:manage, { @article2 => LegacyComment })).to eq(false) + expect(ability.can?(:manage, { @article2 => @legacy_comment2 })).to eq(false) + end end end From c97a1523372e4248c31a825b9942bcd7b0e3b439 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Tue, 22 Mar 2022 16:47:45 +0100 Subject: [PATCH 48/53] Add items to changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56ba01a40..dcacece2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,9 @@ Unreleased +* [#767](https://github.com/CanCanCommunity/cancancan/pull/767): Improve ability checks with nested resources (hash checks)vim. ([@Juleffel][]) * [#772](https://github.com/CanCanCommunity/cancancan/pull/772): Support non-hash conditions in ability definitions. ([@Juleffel][]) * [#773](https://github.com/CanCanCommunity/cancancan/pull/773): Drop support for ruby 2.4 and 2.5. ([@coorasse][]) +* [#778](https://github.com/CanCanCommunity/cancancan/pull/778): Drop support for ActiveRecord 4. ([@coorasse][]) ## 3.3.0 From 40bb1171a81e696c485066fa7037328cdc3f8961 Mon Sep 17 00:00:00 2001 From: Liberatys Date: Wed, 23 Mar 2022 08:36:51 +0100 Subject: [PATCH 49/53] Implement detection for sti for including subclasses in check (#689) * Implement detection for sti for including subclasses in check * Lint * use sti_name * Add inverse check for can/cannot on child class --- lib/cancan/class_matcher.rb | 4 ++ lib/cancan/model_adapters/sti_normalizer.rb | 8 ++-- lib/cancan/sti_detector.rb | 12 ++++++ .../active_record_adapter_spec.rb | 37 +++++++++++++++++++ 4 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 lib/cancan/sti_detector.rb diff --git a/lib/cancan/class_matcher.rb b/lib/cancan/class_matcher.rb index 0bdf0fd99..65efcca39 100644 --- a/lib/cancan/class_matcher.rb +++ b/lib/cancan/class_matcher.rb @@ -1,3 +1,5 @@ +require_relative 'sti_detector' + # This class is responsible for matching classes and their subclasses as well as # upmatching classes to their ancestors. # This is used to generate sti connections @@ -12,6 +14,8 @@ def self.matches_subject_class?(subjects, subject) def self.matching_class_check(subject, sub, has_subclasses) matches = matches_class_or_is_related(subject, sub) if has_subclasses + return matches unless StiDetector.sti_class?(sub) + matches || subject.subclasses.include?(sub) else matches diff --git a/lib/cancan/model_adapters/sti_normalizer.rb b/lib/cancan/model_adapters/sti_normalizer.rb index 85b6c0145..4e656f011 100644 --- a/lib/cancan/model_adapters/sti_normalizer.rb +++ b/lib/cancan/model_adapters/sti_normalizer.rb @@ -1,3 +1,5 @@ +require_relative '../sti_detector' + # this class is responsible for detecting sti classes and creating new rules for the # relevant subclasses, using the inheritance_column as a merger module CanCan @@ -20,9 +22,7 @@ def normalize(rules) private def update_rule(subject, rule, rules_cache) - return false unless subject.respond_to?(:descends_from_active_record?) - return false if subject == :all || subject.descends_from_active_record? - return false unless subject < ActiveRecord::Base + return false unless StiDetector.sti_class?(subject) rules_cache.push(build_rule_for_subclass(rule, subject)) true @@ -31,7 +31,7 @@ def update_rule(subject, rule, rules_cache) # create a new rule for the subclasses that links on the inheritance_column def build_rule_for_subclass(rule, subject) CanCan::Rule.new(rule.base_behavior, rule.actions, subject.superclass, - rule.conditions.merge(subject.inheritance_column => subject.name), rule.block) + rule.conditions.merge(subject.inheritance_column => subject.sti_name), rule.block) end end end diff --git a/lib/cancan/sti_detector.rb b/lib/cancan/sti_detector.rb new file mode 100644 index 000000000..fd0769dd6 --- /dev/null +++ b/lib/cancan/sti_detector.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class StiDetector + def self.sti_class?(subject) + return false unless defined?(ActiveRecord::Base) + return false unless subject.respond_to?(:descends_from_active_record?) + return false if subject == :all || subject.descends_from_active_record? + return false unless subject < ActiveRecord::Base + + true + end +end diff --git a/spec/cancan/model_adapters/active_record_adapter_spec.rb b/spec/cancan/model_adapters/active_record_adapter_spec.rb index f1e7be749..90065108e 100644 --- a/spec/cancan/model_adapters/active_record_adapter_spec.rb +++ b/spec/cancan/model_adapters/active_record_adapter_spec.rb @@ -1104,6 +1104,43 @@ class JsonTransaction < ActiveRecord::Base end end + context 'with rule application to subclass for non sti class' do + before do + ActiveRecord::Schema.define do + create_table :parents, force: true + + create_table :children, force: true + end + + class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true + end + + class Parent < ActiveRecord::Base + end + + class Child < Parent + end + end + + it 'cannot rules are not effecting parent class' do + u1 = User.create!(name: 'pippo') + ability = Ability.new(u1) + ability.can :manage, Parent + ability.cannot :manage, Child + expect(ability).not_to be_able_to(:index, Child) + expect(ability).to be_able_to(:index, Parent) + end + + it 'can rules are not effecting parent class' do + u1 = User.create!(name: 'pippo') + ability = Ability.new(u1) + ability.can :manage, Child + expect(ability).to be_able_to(:index, Child) + expect(ability).not_to be_able_to(:index, Parent) + end + end + context 'when STI is in use' do before do ActiveRecord::Schema.define do From 24ed063d9001c7b3067c718fa966be7bd584e541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20St=C3=B6ckel?= Date: Wed, 23 Mar 2022 09:09:42 +0100 Subject: [PATCH 50/53] Accessible by strategy: Exists subquery (#691) * Added the new strategy: double_exist_subquery * Accessible by strategy: Each rule as exists subquery * Use aliased joined table instead of double sub query * Solved offences and specifically solved complexity offences by extracting code out into separate strategy classes * Extracted existing strategies out into separate classes as well * Fixed more offences and cleaned up * Removed unused file * Fixed spec * Fixed specs * Select column through table name * Fixed more specs * Fixed offences * Fixed SQL in spec * Unscoped might remove existing settings on the query * Limit results to 1 * Added limit in spec as well * Fixed SQL * Just use JOIN both places * Fixed several issues after merging with latest version of develop --- lib/cancan.rb | 2 + lib/cancan/config.rb | 6 +- ...ined_alias_each_rule_as_exists_subquery.rb | 91 +++++++++++++++++++ .../joined_alias_exists_subquery.rb | 29 ++++++ .../accessible_by_has_many_through_spec.rb | 2 +- .../has_and_belongs_to_many_spec.rb | 56 ++++++++++++ 6 files changed, 184 insertions(+), 2 deletions(-) create mode 100644 lib/cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery.rb create mode 100644 lib/cancan/model_adapters/strategies/joined_alias_exists_subquery.rb diff --git a/lib/cancan.rb b/lib/cancan.rb index 76d72af77..c4f92c928 100644 --- a/lib/cancan.rb +++ b/lib/cancan.rb @@ -22,6 +22,8 @@ require 'cancan/model_adapters/active_record_4_adapter' require 'cancan/model_adapters/active_record_5_adapter' require 'cancan/model_adapters/strategies/base' + require 'cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery' + require 'cancan/model_adapters/strategies/joined_alias_exists_subquery' require 'cancan/model_adapters/strategies/left_join' require 'cancan/model_adapters/strategies/subquery' end diff --git a/lib/cancan/config.rb b/lib/cancan/config.rb index a9106526b..75e68bad7 100644 --- a/lib/cancan/config.rb +++ b/lib/cancan/config.rb @@ -3,7 +3,11 @@ module CanCan def self.valid_accessible_by_strategies strategies = [:left_join] - strategies << :subquery unless does_not_support_subquery_strategy? + + unless does_not_support_subquery_strategy? + strategies.push(:joined_alias_exists_subquery, :joined_alias_each_rule_as_exists_subquery, :subquery) + end + strategies end diff --git a/lib/cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery.rb b/lib/cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery.rb new file mode 100644 index 000000000..5838c7e7c --- /dev/null +++ b/lib/cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery.rb @@ -0,0 +1,91 @@ +module CanCan + module ModelAdapters + class Strategies + class JoinedAliasEachRuleAsExistsSubquery < Base + def execute! + model_class + .joins( + "JOIN #{quoted_table_name} AS #{quoted_aliased_table_name} ON " \ + "#{quoted_aliased_table_name}.#{quoted_primary_key} = #{quoted_table_name}.#{quoted_primary_key}" + ) + .where(double_exists_sql) + end + + def double_exists_sql + double_exists_sql = '' + + compressed_rules.each_with_index do |rule, index| + double_exists_sql << ' OR ' if index > 0 + double_exists_sql << "EXISTS (#{sub_query_for_rule(rule).to_sql})" + end + + double_exists_sql + end + + def sub_query_for_rule(rule) + conditions_extractor = ConditionsExtractor.new(model_class) + rule_where_conditions = extract_multiple_conditions(conditions_extractor, [rule]) + joins_hash, left_joins_hash = extract_joins_from_rule(rule) + sub_query_for_rules_and_join_hashes(rule_where_conditions, joins_hash, left_joins_hash) + end + + def sub_query_for_rules_and_join_hashes(rule_where_conditions, joins_hash, left_joins_hash) + model_class + .select('1') + .joins(joins_hash) + .left_joins(left_joins_hash) + .where( + "#{quoted_table_name}.#{quoted_primary_key} = " \ + "#{quoted_aliased_table_name}.#{quoted_primary_key}" + ) + .where(rule_where_conditions) + .limit(1) + end + + def extract_joins_from_rule(rule) + joins = {} + left_joins = {} + + extra_joins_recursive([], rule.conditions, joins, left_joins) + [joins, left_joins] + end + + def extra_joins_recursive(current_path, conditions, joins, left_joins) + conditions.each do |key, value| + if value.is_a?(Hash) + current_path << key + extra_joins_recursive(current_path, value, joins, left_joins) + current_path.pop + else + extra_joins_recursive_merge_joins(current_path, value, joins, left_joins) + end + end + end + + def extra_joins_recursive_merge_joins(current_path, value, joins, left_joins) + hash_joins = current_path_to_hash(current_path) + + if value.nil? + left_joins.deep_merge!(hash_joins) + else + joins.deep_merge!(hash_joins) + end + end + + # Converts an array like [:child, :grand_child] into a hash like {child: {grand_child: {}} + def current_path_to_hash(current_path) + hash_joins = {} + current_hash_joins = hash_joins + + current_path.each do |path_part| + new_hash = {} + current_hash_joins[path_part] = new_hash + current_hash_joins = new_hash + end + + hash_joins + end + end + end + end +end diff --git a/lib/cancan/model_adapters/strategies/joined_alias_exists_subquery.rb b/lib/cancan/model_adapters/strategies/joined_alias_exists_subquery.rb new file mode 100644 index 000000000..eb7e80b38 --- /dev/null +++ b/lib/cancan/model_adapters/strategies/joined_alias_exists_subquery.rb @@ -0,0 +1,29 @@ +module CanCan + module ModelAdapters + class Strategies + class JoinedAliasExistsSubquery < Base + def execute! + model_class + .joins( + "JOIN #{quoted_table_name} AS #{quoted_aliased_table_name} ON " \ + "#{quoted_aliased_table_name}.#{quoted_primary_key} = #{quoted_table_name}.#{quoted_primary_key}" + ) + .where("EXISTS (#{joined_alias_exists_subquery_inner_query.to_sql})") + end + + def joined_alias_exists_subquery_inner_query + model_class + .unscoped + .select('1') + .left_joins(joins) + .where(*where_conditions) + .where( + "#{quoted_table_name}.#{quoted_primary_key} = " \ + "#{quoted_aliased_table_name}.#{quoted_primary_key}" + ) + .limit(1) + end + end + end + end +end diff --git a/spec/cancan/model_adapters/accessible_by_has_many_through_spec.rb b/spec/cancan/model_adapters/accessible_by_has_many_through_spec.rb index 3f5eb2124..28cca16b5 100644 --- a/spec/cancan/model_adapters/accessible_by_has_many_through_spec.rb +++ b/spec/cancan/model_adapters/accessible_by_has_many_through_spec.rb @@ -89,7 +89,7 @@ class Editor < ActiveRecord::Base if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0') describe 'selecting custom columns' do it 'extracts custom columns correctly' do - posts = Post.accessible_by(ability).where(published: true).select('title as mytitle') + posts = Post.accessible_by(ability).where(published: true).select('posts.title as mytitle') expect(posts[0].mytitle).to eq 'post1' end end diff --git a/spec/cancan/model_adapters/has_and_belongs_to_many_spec.rb b/spec/cancan/model_adapters/has_and_belongs_to_many_spec.rb index 1fab6ad63..c98adb16c 100644 --- a/spec/cancan/model_adapters/has_and_belongs_to_many_spec.rb +++ b/spec/cancan/model_adapters/has_and_belongs_to_many_spec.rb @@ -46,6 +46,62 @@ class House < ActiveRecord::Base end unless CanCan::ModelAdapters::ActiveRecordAdapter.version_lower?('5.0.0') + describe 'fetching of records - joined_alias_subquery strategy' do + before do + CanCan.accessible_by_strategy = :joined_alias_exists_subquery + end + + it 'it retreives the records correctly' do + houses = House.accessible_by(ability) + expect(houses).to match_array [@house2, @house1] + end + + if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0') + it 'generates the correct query' do + expect(ability.model_adapter(House, :read)) + .to generate_sql("SELECT \"houses\".* + FROM \"houses\" + JOIN \"houses\" AS \"houses_alias\" ON \"houses_alias\".\"id\" = \"houses\".\"id\" + WHERE (EXISTS (SELECT 1 + FROM \"houses\" + LEFT OUTER JOIN \"houses_people\" ON \"houses_people\".\"house_id\" = \"houses\".\"id\" + LEFT OUTER JOIN \"people\" ON \"people\".\"id\" = \"houses_people\".\"person_id\" + WHERE + \"people\".\"id\" = #{@person1.id} AND + (\"houses\".\"id\" = \"houses_alias\".\"id\") LIMIT 1)) + ") + end + end + end + + describe 'fetching of records - joined_alias_each_rule_as_exists_subquery strategy' do + before do + CanCan.accessible_by_strategy = :joined_alias_each_rule_as_exists_subquery + end + + it 'it retreives the records correctly' do + houses = House.accessible_by(ability) + expect(houses).to match_array [@house2, @house1] + end + + if CanCan::ModelAdapters::ActiveRecordAdapter.version_greater_or_equal?('5.0.0') + it 'generates the correct query' do + expect(ability.model_adapter(House, :read)) + .to generate_sql("SELECT \"houses\".* + FROM \"houses\" + JOIN \"houses\" AS \"houses_alias\" ON \"houses_alias\".\"id\" = \"houses\".\"id\" + WHERE (EXISTS (SELECT 1 + FROM \"houses\" + INNER JOIN \"houses_people\" ON \"houses_people\".\"house_id\" = \"houses\".\"id\" + INNER JOIN \"people\" ON \"people\".\"id\" = \"houses_people\".\"person_id\" + WHERE (\"houses\".\"id\" = \"houses_alias\".\"id\") AND + (\"people\".\"id\" = #{@person1.id}) + LIMIT 1)) + ") + end + end + end + describe 'fetching of records - subquery strategy' do before do CanCan.accessible_by_strategy = :subquery From 9c75a78b5b60d59d9620ae6ae609d76941228cca Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Wed, 23 Mar 2022 09:14:37 +0100 Subject: [PATCH 51/53] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dcacece2b..dfb5dd59e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ Unreleased +* [#691](https://github.com/CanCanCommunity/cancancan/pull/691): Add two new subquery strategies: `joined_alias_exists_subquery`, `joined_alias_each_rule_as_exists_subquery`. ([@kaspernj][]) * [#767](https://github.com/CanCanCommunity/cancancan/pull/767): Improve ability checks with nested resources (hash checks)vim. ([@Juleffel][]) * [#772](https://github.com/CanCanCommunity/cancancan/pull/772): Support non-hash conditions in ability definitions. ([@Juleffel][]) * [#773](https://github.com/CanCanCommunity/cancancan/pull/773): Drop support for ruby 2.4 and 2.5. ([@coorasse][]) From be14fdae4d14cff0a92c719b39889571093d82a5 Mon Sep 17 00:00:00 2001 From: Liberatys Date: Wed, 30 Mar 2022 10:17:32 +0200 Subject: [PATCH 52/53] Lint adapter query strategies (#781) --- .../strategies/joined_alias_each_rule_as_exists_subquery.rb | 4 +++- .../model_adapters/strategies/joined_alias_exists_subquery.rb | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery.rb b/lib/cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery.rb index 5838c7e7c..b27721ad0 100644 --- a/lib/cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery.rb +++ b/lib/cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: false + module CanCan module ModelAdapters class Strategies @@ -15,7 +17,7 @@ def double_exists_sql double_exists_sql = '' compressed_rules.each_with_index do |rule, index| - double_exists_sql << ' OR ' if index > 0 + double_exists_sql << ' OR ' if index.positive? double_exists_sql << "EXISTS (#{sub_query_for_rule(rule).to_sql})" end diff --git a/lib/cancan/model_adapters/strategies/joined_alias_exists_subquery.rb b/lib/cancan/model_adapters/strategies/joined_alias_exists_subquery.rb index eb7e80b38..a95592a0e 100644 --- a/lib/cancan/model_adapters/strategies/joined_alias_exists_subquery.rb +++ b/lib/cancan/model_adapters/strategies/joined_alias_exists_subquery.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module CanCan module ModelAdapters class Strategies From 9084c543820590323cfc199e70d5e5cb0a36ebb7 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Thu, 23 Jun 2022 14:03:58 +0200 Subject: [PATCH 53/53] Update CHANGELOG and Bump version --- CHANGELOG.md | 4 +++- lib/cancan/version.rb | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfb5dd59e..98fb2e7d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ -Unreleased +## Unreleased + +## 3.4.0 * [#691](https://github.com/CanCanCommunity/cancancan/pull/691): Add two new subquery strategies: `joined_alias_exists_subquery`, `joined_alias_each_rule_as_exists_subquery`. ([@kaspernj][]) * [#767](https://github.com/CanCanCommunity/cancancan/pull/767): Improve ability checks with nested resources (hash checks)vim. ([@Juleffel][]) diff --git a/lib/cancan/version.rb b/lib/cancan/version.rb index d436325d1..d5cf6e1f1 100644 --- a/lib/cancan/version.rb +++ b/lib/cancan/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module CanCan - VERSION = '3.3.0'.freeze + VERSION = '3.4.0'.freeze end