@@ -50,6 +71,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/README.md) to learn how to use CanCanCan in details.
+
## Installation
Add this to your Gemfile:
@@ -72,21 +95,15 @@ class Ability
def initialize(user)
can :read, Post, public: true
- if user.present? # additional permissions for logged in users (they can read their own posts)
- can :read, Post, user_id: user.id
+ return unless user.present? # additional permissions for logged in users (they can read their own posts)
+ can :read, Post, user: user
- if user.admin? # additional permissions for administrators
- can :read, Post
- end
- end
+ return unless user.admin? # additional permissions for administrators
+ can :read, Post
end
end
```
-See [Defining Abilities](./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 +114,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 +121,13 @@ is the possibility to retrieve all the objects that the user is authorized to ac
The following:
```ruby
- Post.accessible_by(current_ability)
+ @posts = Post.accessible_by(current_ability)
```
will use your rules to ensure that the user retrieves only a list of posts that can be read.
-See [Fetching records](./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 +137,6 @@ def show
end
```
-### 3.2 Loaders
-
Setting this for every action can be tedious, therefore the `load_and_authorize_resource` method is provided to
automatically authorize all actions in a RESTful style resource controller.
It will use a before action to load the resource into an instance variable and authorize it for every action.
@@ -150,129 +155,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/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) or our
-[mailing list](http://groups.google.com/group/cancancan), please
+[documentation](./docs/README.md), please
[open a question on Stackoverflow](http://stackoverflow.com/questions/ask?tags=cancancan) with tag
[cancancan](http://stackoverflow.com/questions/tagged/cancancan)
@@ -289,9 +179,12 @@ 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
-Many thanks to the [CanCanCan contributors](https://github.com/CanCanCommunity/cancancan/contributors).
-See the [CHANGELOG](https://github.com/CanCanCommunity/cancancan/blob/master/CHANGELOG.md) for the full list.
+Thanks to our Sponsors and to all the [CanCanCan contributors](https://github.com/CanCanCommunity/cancancan/contributors).
+See the [CHANGELOG](https://github.com/CanCanCommunity/cancancan/blob/main/CHANGELOG.md) for the full list.
diff --git a/cancancan.gemspec b/cancancan.gemspec
index 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/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
deleted file mode 100644
index 4ea1528fb..000000000
--- a/docs/Devise.md
+++ /dev/null
@@ -1,22 +0,0 @@
-You can bypass CanCanCan's authorization for Devise controllers:
-
-```ruby
-class ApplicationController < ActionController::Base
- protect_from_forgery
- check_authorization unless: :devise_controller?
-end
-```
-
-It may be a good idea to specify the rescue from action:
-
-```ruby
-rescue_from CanCan::Unauthorized 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)
- end
-end
-```
\ No newline at end of file
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/Migrating-from-CanCanCan-2.x-to-3.0.md b/docs/Migrating-from-CanCanCan-2.x-to-3.0.md
deleted file mode 100644
index f7886ea7b..000000000
--- a/docs/Migrating-from-CanCanCan-2.x-to-3.0.md
+++ /dev/null
@@ -1,15 +0,0 @@
-### Breaking changes
-
-* **Defining abilities without a subject is not allowed anymore.**
-For example, `can :dashboard` is not going to be accepted anymore and will raise an exception.
-All these kind of rules need to be rethought in terms of `can action, subject`. `can :read, :dashboard` for example.
-
-* **Eager loading is not automatic.** If you relied on CanCanCan to avoid N+1 queries, this will not be the case anymore.
-From now on, all necessary `includes`, `preload` or `eager_load` need to be explicitly written. We strongly suggest to have
-`bullet` gem installed to identify your possible N+1 issues.
-
-* **Use of distinct.** Uniqueness of the results is guaranteed by using the `distinct` clause in the final query.
-This may cause issues with some existing queries when using clauses like `group by` or `order` on associations.
-Adding a custom `select` may be necessary in these cases.
-
-* **aliases are now merged.** When using the method to merge different Ability files, the aliases are now also merged. This might cause some incompatibility issues.
diff --git a/docs/Model-Adapter.md b/docs/Model-Adapter.md
deleted file mode 100644
index 8b529e055..000000000
--- a/docs/Model-Adapter.md
+++ /dev/null
@@ -1,131 +0,0 @@
-# Model Adapter
-
-CanCan includes a model adapter layer which allows it to change behavior depending on the model used. The current adapters are.
-
-* ActiveRecord (native in `cancancan` gem)
-* [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.
-
-### The Specs
-
-
-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.
-
-```ruby
-case ENV["MODEL_ADAPTER"]
-# ...
-when "mongoid"
- gem "bson_ext", "~> 1.1"
- gem "mongoid", "~> 2.0.0.beta.20"
-# ...
-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`
-
-```ruby
-if ENV["MODEL_ADAPTER"] == "mongoid"
- require "spec_helper"
-
- class MongoidProject
- include Mongoid::Document
- end
-
- Mongoid.configure do |config|
- config.master = Mongo::Connection.new('127.0.0.1', 27017).db("cancan_mongoid_spec")
- end
-
- describe CanCan::ModelAdapters::MongoidAdapter do
- context "Mongoid defined" do
- before(:each) do
- @ability = Object.new
- @ability.extend(CanCan::Ability)
- end
-
- it "should return the correct records based on the defined ability" do
- @ability.can :read, MongoidProject, :title => "Sir"
- sir = MongoidProject.create(:title => 'Sir')
- lord = MongoidProject.create(:title => 'Lord')
- MongoidProject.accessible_by(@ability, :read).entries.should == [sir]
- end
- end
- end
-end
-```
-
-You will need many more specs for full coverage but add them one at a time. To run the specs execute the following commands.
-
-```bash
-MODEL_ADAPTER=mongoid bundle
-MODEL_ADAPTER=mongoid rake
-```
-
-That will fail since we have not added the implementation.
-
-### The Implementation
-
-First add a line to `lib/cancan.rb` for including the adapter only when 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`.
-
-```ruby
-module CanCan
- module ModelAdapters
- class MongoidAdapter < AbstractAdapter
- def self.for_class?(model_class)
- model_class <= Mongoid::Document
- end
-
- def database_records
- if @rules.size == 0
- @model_class.where(:_id => {'$exists' => false, '$type' => 7}) # return no records in Mongoid
- else
- @rules.inject(@model_class.all) do |records, rule|
- if rule.base_behavior
- records.or(rule.conditions)
- else
- records.excludes(rule.conditions)
- end
- end
- end
- end
- end
- end
-end
-
-module Mongoid::Document::ClassMethods
- include CanCan::ModelAdditions::ClassMethods
-end
-```
-
-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.
-
-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.
-
-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.
-
-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]]).
-
-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.
-
-```ruby
-# in MongoidAdapter
-def self.override_conditions_hash_matching?(subject, conditions)
- conditions.any? { |k,v| !k.kind_of?(Symbol) }
-end
-
-def self.matches_conditions_hash?(subject, conditions)
- subject.matches? subject.class.where(conditions).selector
-end
-```
-
-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.
-
-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.
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..9fbbeb8c5 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.md) to start fast :rocket:.
+
+## Summary
+
+1. [Introduction](./introduction.md)
+1. [Installation](./installation.md)
+1. [Define and check abilities](./define_check_abilities.md)
+1. [Controller helpers](./controller_helpers.md)
+1. [Fetching records](./fetching_records.md)
+1. [Cannot](./cannot.md)
+1. [Hash of conditions](./hash_of_conditions.md)
+1. [Combine Abilities](./combine_abilities.md)
+1. [Check abilities - avoid mistakes](./check_abilities_mistakes.md)
+1. [Handling CanCan::AccessDenied](./handling_access_denied.md)
+1. [Customize controller helpers](./changing_defaults.md)
+1. [Accessing request data](./accessing_request_data.md)
+1. [SQL strategies](./sql_strategies.md)
+1. [Accessible attributes](./accessible_attributes.md)
+1. [Testing](./testing.md)
+1. [Internationalization](./internationalization.md)
+
+## Further topics
+
+In these topics, you will learn some best practices, but also how to solve specific integration issues with other libraries or how to extend CanCanCan.
+
+1. [Migrating](./migrating.md)
+1. [Debugging Abilities](./debugging.md)
+1. [Split your ability file](./split_ability.md)
+1. [Define Abilities - best practices](./define_abilities_best_practices.md)
+1. [Abilities in database](./abilities_in_database.md)
+1. [Role-based Authorization](./role_based_authorization.md)
+1. [Model Adapter](./model_adapter.md)
+1. [Rules compression](./rules_compression.md)
+1. [Inherited Resources](./inherited_resources.md)
+1. [Devise](./devise.md)
+1. [FriendlyId](./friendly_id.md)
diff --git a/docs/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 %>
-
-<% 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 93%
rename from docs/Abilities-in-Database.md
rename to 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/accessible_attributes.md b/docs/accessible_attributes.md
new file mode 100644
index 000000000..4ee96dc4f
--- /dev/null
+++ b/docs/accessible_attributes.md
@@ -0,0 +1,37 @@
+# Accessible attributes
+
+CanCanCan gives you the possibility to define actions on single instances' attributes.
+
+Given you want users to only read a user first name and last name you can define:
+
+```ruby
+can :read, User, [:first_name, :last_name]
+```
+
+and check it with:
+
+```ruby
+can? :read, @user, :first_name
+```
+
+You can also ask for all the allowed attributes:
+
+```ruby
+current_ability.permitted_attributes(:read, @user)
+#=> [:first_name, :last_name]
+```
+
+This can be used, for example, to display a form:
+
+```ruby
+current_ability.permitted_attributes(:read, @book).each do |attr|
+ = form.input attr
+```
+
+or in Strong Parameters:
+
+```ruby
+params
+ .require(:book)
+ .permit(current_ability.permitted_attributes(:read, @book))
+```
diff --git a/docs/accessing_request_data.md b/docs/accessing_request_data.md
new file mode 100644
index 000000000..238389d01
--- /dev/null
+++ b/docs/accessing_request_data.md
@@ -0,0 +1,27 @@
+# Accessing request data
+
+What if you need to modify the permissions based on something outside of the User object? For example, let's say you want to forbid certain IP addresses from creating comments. The IP address is accessible through request.remote_ip but the Ability class does not have access to this. It's easy to modify what you pass to the Ability object by overriding the current_ability method in ApplicationController.
+
+```ruby
+class ApplicationController < ActionController::Base
+ #...
+
+ private
+
+ def current_ability
+ @current_ability ||= Ability.new(current_user, request.remote_ip)
+ end
+end
+```
+
+```ruby
+class Ability
+ include CanCan::Ability
+
+ def initialize(user, ip_address=nil)
+ can :create, Comment unless DENYLIST_IPS.include? ip_address
+ end
+end
+```
+
+This concept can apply to session and cookies as well.
diff --git a/docs/cannot.md b/docs/cannot.md
new file mode 100644
index 000000000..5d1fc4d69
--- /dev/null
+++ b/docs/cannot.md
@@ -0,0 +1,14 @@
+# Cannot
+
+Yes, sometimes you might need to **remove** permissions. Even if we said that CanCanCan assumes that by default no one has access to any resource, there are situations where you might need to remove an ability.
+
+The `cannot` method takes the same arguments as `can` and defines which actions the user is unable to perform. This is normally done after a more generic `can` call.
+
+```ruby
+can :manage, Project
+cannot :destroy, Project
+```
+
+will allow the user to do **any** action but destroy the project.
+
+Of course, there's a `cannot?` method to check abilities that is a simple alias for `!can?`.
diff --git a/docs/changing_defaults.md b/docs/changing_defaults.md
new file mode 100644
index 000000000..5420b4c94
--- /dev/null
+++ b/docs/changing_defaults.md
@@ -0,0 +1,287 @@
+# Customize the controller helpers
+
+We now dig deeper in the customizations and options we have when working with [controller helpers](./controller_helpers.md)
+
+## current_ability and current_user
+
+CanCanCan makes two assumptions about your application:
+
+- You have an `Ability` class which defines the permissions.
+- You have a `current_user` method in the controller which returns the current user model.
+
+You can override both of these by defining the `current_ability` method in your `ApplicationController`. The default method looks like this.
+
+```ruby
+def current_ability
+ @current_ability ||= Ability.new(current_user)
+end
+```
+
+The `Ability` class and `current_user` method can easily be changed to something else.
+
+```ruby
+# in ApplicationController
+def current_ability
+ @current_ability ||= AccountAbility.new(current_account)
+end
+```
+
+Sometimes you might have a gem in your project which provides its own Rails engine which also uses CanCanCan, in this case the current_ability override in the ApplicationController can also be useful.
+
+```ruby
+# in ApplicationController
+def current_ability
+ if request.fullpath =~ /\/rails_admin/
+ @current_ability ||= RailsAdmin::Ability.new(current_user)
+ else
+ @current_ability ||= Ability.new(current_user)
+ end
+end
+```
+
+If your method that returns the currently logged in user just has another name than `current_user`, it may be the easiest solution to simply alias the method in your ApplicationController like this:
+
+```ruby
+class ApplicationController < ActionController::Base
+ alias_method :current_user, :name_of_your_method # Could be :current_member or :logged_in_user
+end
+```
+
+## Strong parameters
+
+If your parameters sanitization method does not follow the naming convention, `load_and_authorize_resource` takes a `param_method` option to specify a custom method in the controller to run to sanitize input.
+
+You can associate the `param_method` option with a symbol corresponding to the name of a method that will get called:
+
+```ruby
+class ArticlesController < ApplicationController
+ load_and_authorize_resource param_method: :my_sanitizer
+
+ def create
+ @article.save
+ end
+
+ private
+
+ def my_sanitizer
+ params.require(:article).permit(:name)
+ end
+end
+```
+
+You can also use a string that will be evaluated in the context of the controller using `instance_eval` and needs to contain valid Ruby code.
+
+```ruby
+load_and_authorize_resource param_method: 'permitted_params.post'
+```
+
+Finally, it's possible to associate `param_method` with a Proc object which will be called with the controller as the only argument:
+
+```ruby
+load_and_authorize_resource param_method: -> { |c| c.params.require(:article).permit(:name) }
+```
+
+If your model name and controller name differ, you can specify a `class` option.
+
+> Note that the method will still be `articles_params` and not `post_params`, since we are in `ArticlesController`.
+
+```ruby
+class ArticlesController < ApplicationController
+ load_and_authorize_resource class: 'Post'
+
+ def create
+ @article.save
+ end
+
+ private
+
+ def article_params
+ params.require(:article).permit(:name)
+ end
+end
+```
+
+## Non RESTful controllers
+
+You can use CanCanCan with controllers that do not follow the traditional REST actions, however you should not use the `load_and_authorize_resource` method since there is no resource to load. Instead you can call `authorize!` in each action separately.
+
+For example, let's say we have a controller which does some miscellaneous administration tasks such as rolling log files. We can use the `authorize!` method here.
+
+```ruby
+class AdminController < ActionController::Base
+ def roll_logs
+ authorize! :roll, :logs
+ # roll the logs here
+ end
+end
+```
+
+And then authorize that in the `Ability` class.
+
+```ruby
+can :roll, :logs if user.admin?
+```
+
+Notice you can pass a symbol as the second argument to both `authorize!` and `can`. It doesn't have to be a model class or instance.
+
+Alternatively you can use the `authorize_resource` and specify that there's no class. This way it will pass the resource symbol instead. This is good if you still have a Resource-like controller but no model class backing it.
+
+```ruby
+class ToolsController < ApplicationController
+ authorize_resource class: false
+
+ def show
+ # automatically calls authorize!(:show, :tool)
+ end
+end
+```
+
+## skip load and authorize
+
+You can use the `skip_load_and_authorize_resource`, `skip_load_resource` or `skip_authorize_resource` methods to skip any of the applied behavior and specify specific actions like in a before filter. For example:
+
+```ruby
+class ProductsController < ActionController::Base
+ load_and_authorize_resource
+ skip_authorize_resource only: :new
+end
+```
+
+### Custom class name
+
+If the model is named differently than the controller, then you may explicitly name the model that should be loaded; however, you must specify that it is not a parent in a nested routing situation, ie:
+
+```ruby
+class ArticlesController < ApplicationController
+ load_and_authorize_resource :post, parent: false
+end
+```
+
+If the model class is namespaced differently than the controller you will need to specify the `:class` option.
+
+```ruby
+class ProductsController < ApplicationController
+ load_and_authorize_resource class: "Store::Product"
+end
+```
+
+### Custom find
+
+If you want to fetch a resource by something other than `id` it can be done so using the `find_by` option.
+
+```ruby
+load_resource find_by: => :permalink # will use find_by!(permalink: params[:id])
+authorize_resource
+```
+
+### Override loading
+
+The resource will only be loaded into an instance variable if it hasn't been already. This allows you to easily override how the loading happens in a separate `before_action`.
+
+```ruby
+class BooksController < ApplicationController
+ before_action :find_published_book, only: :show
+ load_and_authorize_resource
+
+ private
+
+ def find_published_book
+ @book = Book.released.find(params[:id])
+ end
+end
+```
+
+## check_authorization
+
+If you want to be certain authorization is not forgotten in some controller action, add `check_authorization` to your `ApplicationController`.
+
+```ruby
+class ApplicationController < ActionController::Base
+ check_authorization
+end
+```
+
+This will add an `after_action` to ensure authorization takes place in every inherited controller action. If no authorization happens it will raise a `CanCan::AuthorizationNotPerformed` exception. You can skip this check by adding `skip_authorization_check` to that controller. Both of these methods take the same arguments as `before_action` so you can exclude certain actions with `:only` and `:except`.
+
+```ruby
+class UsersController < ApplicationController
+ skip_authorization_check :only => [:new, :create]
+ # ...
+end
+```
+
+The `check_authorization` method supports `:if` and `:unless` options. Either one takes a method name as a symbol. This method will be called to determine if the authorization check will be performed. This makes it very easy to skip this check on all Devise controllers since they provide a `devise_controller?` method.
+
+```ruby
+class ApplicationController < ActionController::Base
+ check_authorization unless: :devise_controller?
+end
+```
+
+Here's another example where authorization is only ensured for the admin subdomain.
+
+```ruby
+class ApplicationController < ActionController::Base
+ check_authorization if: :admin_subdomain?
+
+ private
+
+ def admin_subdomain?
+ request.subdomain == "admin"
+ end
+end
+```
+
+> Note: The `check_authorization` only ensures that authorization is performed. If you have `authorize_resource` the authorization will still be performed no matter what is returned here.
+
+The default operation for CanCanCan is to authorize based on user and the object identified in `load_resource`. So if you have a `WidgetsController` and also an `Admin::WidgetsController`, you can use some different approaches.
+
+# Overriding authorizations for Namespaced controllers
+
+You can create differing authorization rules that depend on the controller namespace.
+
+In this case, just override the `current_ability` method in `ApplicationController` to include the controller namespace, and create an `Ability` class that knows what to do with it.
+
+```ruby
+class Admin::WidgetsController < ActionController::Base
+ #...
+
+ private
+
+ def current_ability
+ # I am sure there is a slicker way to capture the controller namespace
+ controller_name_segments = params[:controller].split('/')
+ controller_name_segments.pop
+ controller_namespace = controller_name_segments.join('/').camelize
+ @current_ability ||= Ability.new(current_user, controller_namespace)
+ end
+end
+
+
+class Ability
+ include CanCan::Ability
+
+ def initialize(user, controller_namespace)
+ case controller_namespace
+ when 'Admin'
+ can :manage, :all if user.has_role? 'admin'
+ else
+ # rules for non-admin controllers here
+ end
+ end
+end
+```
+
+Another way to achieve the same is to use a completely different Ability class in this controller:
+
+```ruby
+class Admin::WidgetsController < ActionController::Base
+ #...
+
+ private
+
+ def current_ability
+ @current_ability ||= AdminAbility.new(current_user)
+ end
+end
+```
diff --git a/docs/check_abilities_mistakes.md b/docs/check_abilities_mistakes.md
new file mode 100644
index 000000000..a9118eabb
--- /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..204341d8d
--- /dev/null
+++ b/docs/combine_abilities.md
@@ -0,0 +1,59 @@
+# Combine abilities
+
+It is possible to define multiple abilities for the same resource. Here the user will be able to read projects which are released OR available for preview.
+
+```ruby
+can :read, Project, released: true
+can :read, Project, preview: true
+```
+
+The `cannot` method takes the same arguments as `can` and defines which actions the user is unable to perform. This is normally done after a more generic `can` call.
+
+```ruby
+can :manage, Project
+cannot :destroy, Project
+```
+
+The order of these calls is important.
+
+## Abilities precedence
+
+An ability rule will override a previous one.
+
+For example, let's say we want the user to be able to do everything to projects except destroy them.
+
+This is the correct way:
+
+```ruby
+can :manage, Project
+cannot :destroy, Project
+```
+
+It is important that the `cannot :destroy` line comes after the `can :manage` line. If they were reversed, `cannot :destroy` would be overridden by `can :manage`.
+
+Adding `can` rules does not override prior rules, but instead are logically or'ed.
+
+```ruby
+can :manage, Project, user: user
+can :update, Project, locked: false
+```
+
+For the above, `can? :update, @project` will return true if project owner is the user, even if the project is locked.
+
+This is also important when dealing with roles which have inherited behavior. For example, let's say we have two roles, moderator and admin. We want the admin to inherit the moderator's behavior.
+
+```ruby
+if user.moderator?
+ can :manage, Project
+ cannot :destroy, Project
+ can :manage, Comment
+end
+
+if user.admin?
+ can :destroy, Project
+end
+```
+
+Here it is important for the admin permissions to be defined after the moderator ones, so it can override the `cannot` behavior to give the admin more permissions.
+
+Let's now check at a different way of defining abilities: [blocks](./define_abilities_with_blocks.md).
diff --git a/docs/controller_helpers.md b/docs/controller_helpers.md
new file mode 100644
index 000000000..ec00a8c6c
--- /dev/null
+++ b/docs/controller_helpers.md
@@ -0,0 +1,176 @@
+# Controller helpers
+
+As mentioned in the chapter [Define and check abilities](./define_check_abilities.md), the `can?` method works at its best in Rails controllers and views.
+This of course doesn't mean that it cannot be used everywhere.
+
+We know already that in order to check if the user is allowed to perform a certain action we need to have a `current_user` method available and we can check the permission with `can? :update, @article`.
+
+We can easily protect the `edit` and `update` actions of our controller by checking for the permission. Here is a very simple example:
+
+```ruby
+class ArticlesController < ApplicationController
+ def edit
+ @article = Article.find(params[:id])
+ if can? :edit, @article
+ render :edit
+ else
+ head :forbidden
+ end
+ end
+end
+```
+
+## authorize!
+
+CanCanCan provides us a `authorize!` helper that allows us to simplify the code above:
+
+```ruby
+def edit
+ @article = Article.find(params[:id])
+ authorize! :edit, @article
+ render :edit
+end
+```
+
+`authorize!` will raise a `CanCan::AccessDenied` if the action is not permitted.
+
+You can have a global configuration on how to react to this exception in `config/application.rb`:
+
+```ruby
+config.action_dispatch.rescue_responses.merge!('CanCan::AccessDenied' => :unauthorized)
+```
+
+The [Handling CanCan::AccessDenied Exception](./handling_access_denied.md) chapter digs deeper on how to handle the exception raised by `authorize!`.
+
+> `:unauthorized` might not be your favourite return status if you don't want to reveal to the user that the article exists. In such cases, `:not_found` would be a better http status.
+
+## authorize_resource, load_resource, load_and_authorize_resource
+
+In a RESTful controller, calling `authorize! action` for every action can be tedious. Here we will show you, step by step, how to improve the code above.
+
+Add `authorize_resource` in your controller, to call automatically `authorize! action_name, @article` for every action.
+The code above can be refactored like this:
+
+```ruby
+class ArticlesController < ApplicationController
+ before_action :load_article
+ authorize_resource
+
+ def edit; end
+
+ protected
+
+ def load_article
+ @article = Article.find(params[:id])
+ end
+end
+```
+
+the second helper method is `load_resource` that will perform the loading of the model automatically based on the name of the controller. The code above can be refactored like that:
+
+```ruby
+class ArticlesController < ApplicationController
+ load_resource
+ authorize_resource
+
+ def edit; end
+end
+```
+
+and, clearly, `load_and_authorize_resource` allows to do the following:
+
+```ruby
+class ArticlesController < ApplicationController
+ load_and_authorize_resource
+
+ def edit; end
+end
+```
+
+this means that a completely authorized `ArticlesController` would look as follow:
+
+```ruby
+class ArticlesController < ApplicationController
+ load_and_authorize_resource
+
+ def index
+ # @articles are already loaded...see details in later chapter
+ end
+
+ def show
+ # the @article to show is already loaded and authorized
+ end
+
+ def create
+ # the @article to create is already loaded, authorized, and params set from article_params
+ @article.create
+ end
+
+ def edit
+ # the @article to edit is already loaded and authorized
+ end
+
+ def update
+ # the @article to update is already loaded and authorized
+ @article.update(article_params)
+ end
+
+ def destroy
+ # the @article to destroy is already loaded and authorized
+ @article.destroy
+ end
+
+ protected
+
+ def article_params
+ params.require(:article).permit(:body)
+ end
+end
+```
+
+## Strong parameters
+
+You have to sanitize inputs before saving the record, in actions such as `:create` and `:update`.
+
+For the `:update` action, CanCanCan will load and authorize the resource but **not** change it automatically, so the typical usage would be something like:
+
+```ruby
+def update
+ if @article.update(article_params)
+ # hurray
+ else
+ render :edit
+ end
+end
+...
+
+def article_params
+ params.require(:article).permit(:body)
+end
+```
+
+For the `:create` action, CanCanCan will try to initialize a new instance with sanitized input by seeing if your controller will respond to the following methods (in order):
+
+1. `create_params`
+2. `_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..ad56594d0 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
@@ -35,8 +39,6 @@ can :update, Project, ["priority < ?", 3] do |project|
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 +53,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](https://stackoverflow.com/questions/ask?tags=cancancan) with tag
+[cancancan](https://stackoverflow.com/questions/tagged/cancancan).
diff --git a/docs/define_abilities_best_practices.md b/docs/define_abilities_best_practices.md
new file mode 100644
index 000000000..eeed9a954
--- /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 flexibility.
+
+Every time you define an ability with `can`, each `can` chains together with OR in the final SQL query for that model.
+
+So if, in addition to the `is_published` condition above, we want to allow authors to see their drafts:
+
+```ruby
+can :read, Article, author_id: @user.id, is_published: false
+```
+
+Then the final SQL would be:
+
+```sql
+SELECT `articles`.*
+FROM `articles`
+WHERE `articles`.`is_published` = 1
+OR ( `articles`.`author_id` = 97 AND `articles`.`is_published` = 0 )
+```
+
+### For complex object graphs, hash conditions accommodate `joins` easily.
+
+See [Hash of Conditions Chapter](./hash_of_conditions.md).
+
+### Give permissions, don't take them away
+
+As suggested in this [topic on Reddit](https://www.reddit.com/r/ruby/comments/6ytka8/refactoring_cancancan_abilities_brewing_bits/) you should, when possible, give increasing permissions to your users.
+
+CanCanCan increases permissions: it starts by giving no permissions to nobody and then increases those permissions depending on the user.
+
+A properly written `ability.rb` looks like that:
+
+```ruby
+class Ability
+ include CanCan::Ability
+
+ def initialize(user)
+ can :read, Post # start by defining rules for all users, also not logged ones
+ return unless user.present?
+ can :manage, Post, user_id: user.id # if the user is logged in can manage it's own posts
+ can :create, Comment # logged in users can also create comments
+ return unless user.manager? # if the user is a manager we give additional permissions
+ can :manage, Comment # like managing all comments in the website
+ return unless user.admin?
+ can :manage, :all # finally we give all remaining permissions only to the admins
+ end
+end
+```
+
+following this good practice will help you to keep your permissions clean and more readable.
+
+The risk of giving wrong permissions to the wrong users is also decreased.
diff --git a/docs/define_abilities_with_blocks.md b/docs/define_abilities_with_blocks.md
new file mode 100644
index 000000000..60b25480a
--- /dev/null
+++ b/docs/define_abilities_with_blocks.md
@@ -0,0 +1,60 @@
+# Define abilities with blocks
+
+If your conditions are too complex to define in a [hash of conditions](./hash_of_conditions.md), you can use a block to define them in Ruby.
+
+```ruby
+can :update, Project do |project|
+ project.priority < 3
+end
+```
+
+Note that if you pass a block to a `can` or `cannot`, the block only executes if an instance of a class is passed to `can?` or `cannot?` calls.
+
+If you define a `can` or `cannot` with a block and an object is not passed, the check will pass.
+
+```ruby
+can :update, Project do |project|
+ false
+end
+```
+
+```ruby
+can? :update, Project # returns true!
+```
+
+## Fetching Records
+
+A block's conditions are only executable through Ruby. If you are [Fetching Records](./fetching_records.md) using `accessible_by` it will raise an exception.
+
+To fetch records from the database you need to supply an SQL string representing the condition. The SQL will go in the `WHERE` clause:
+
+```ruby
+can :update, Project, ["priority < ?", 3] do |project|
+ project.priority < 3
+end
+```
+
+> If you are using `load_resource` and don't supply this SQL argument, the instance variable will not be set for the `index` action since they cannot be translated to a database query.
+
+## Block Conditions with ActiveRecord Scopes
+
+It's also possible to pass a scope instead of an SQL string when using a block in an ability.
+
+```ruby
+can :read, Article, Article.published do |article|
+ article.published_at <= Time.now
+end
+```
+
+This is really useful if you have complex conditions which require `joins`. A couple of caveats:
+
+- You cannot use this with multiple `can` definitions that match the same action and model since it is not possible to combine them. An exception will be raised when that is the case.
+- If you use this with `cannot`, the scope needs to be the inverse since it's passed directly through. For example, if you don't want someone to read discontinued products the scope will need to fetch non discontinued ones:
+
+```ruby
+cannot :read, Product, Product.where(discontinued: false) do |product|
+ product.discontinued?
+end
+```
+
+It is only recommended to use scopes if a situation is too complex for a hash condition.
diff --git a/docs/define_check_abilities.md b/docs/define_check_abilities.md
new file mode 100644
index 000000000..476b9e149
--- /dev/null
+++ b/docs/define_check_abilities.md
@@ -0,0 +1,232 @@
+# Define and Check abilities
+
+CanCanCan is an authorization library and therefore the first and most interesting thing to learn is how to define and check abilities. During the [installation](./installation.md) you generated an `ability.rb` file but you don't know yet how to use it.
+
+There are two basic methods in CanCanCan that you will use:
+
+```ruby
+can actions, subjects, conditions
+# without the question mark
+```
+
+is how you define who can **perform** certain `actions` on certain `subjects`.
+
+```ruby
+can? action, subject
+```
+
+will be the method that you will use to **check** if the user is authorized to perform a certain `action` on a certain `subject`.
+
+We don't want to be too abstract here so let's start with a very concrete example.
+
+> We have a blog with articles and the first thing you want to control is "who can edit an article?"
+
+```ruby
+class Article
+ belongs_to :user
+end
+```
+
+The answer to this question is that
+
+> "only the author can edit an article."
+
+We can define the permissions in the `ability.rb`:
+
+```ruby
+class Ability
+ include CanCan::Ability
+
+ def initialize(user)
+ can :update, Article, user: user
+ end
+end
+
+# from here on we will skip the ability.rb file structure
+```
+
+And we can easily check with the following call:
+
+```ruby
+@article = Article.find(params[:id])
+
+can? :update, @article # => true
+```
+
+But how does CanCanCan know who is the `user`?
+When you use the `can?` method in a Rails controller or view, CanCanCan expects that there's a `current_user` method defined. So if you are using something like [devise](https://github.com/heartcombo/devise) for your authentication, you don't need to do anything special.
+
+By default, CanCanCan assumes no permissions: no one can do any action on any object.
+
+`can :update, Article, user: user` is stating that the user can update an article, if it is its author.
+
+Regarding the `Article` there are actually more permissions to check:
+
+- who can read them?
+- what can the administrator do?
+
+A complete example looks like the following:
+
+```ruby
+can :read, Article, published: true
+
+return unless user.present?
+
+can :read, Article, user: user
+can :update, Article, user: user
+
+return unless user.admin?
+
+can :read, Article
+can :update, Article
+```
+
+The code above is stating the following:
+
+- users that are not logged in, can read published articles
+- logged in users can **also** read and update their own articles
+- administrators can read and update all the articles.
+
+> CanCanCan works, at its best, when defining increasing permissions.
+
+The code above can be simplified like this:
+
+```ruby
+can :read, Article, published: true
+
+return unless user.present?
+
+can [:read, :update], Article, user: user
+
+return unless user.admin?
+
+can [:read, :update], Article
+```
+
+Now that we know the basics of defining and checking abilities, let's check what are the possible actions.
+
+## Can Actions
+
+CanCanCan offers four aliases: `:read`, `:create`, `:update`, `:destroy` for the actions. These aren't the same as the seven Restful actions in Rails. CanCanCan automatically adds some convenient aliases for mapping the controller actions.
+
+```ruby
+read: [:index, :show]
+create: [:new, :create]
+update: [:edit, :update]
+destroy: [:destroy]
+```
+
+this means that when you define `can :read, Article`, you can also check:
+
+```ruby
+can? :show, @article
+```
+
+when you define `can :update, Article`, you can also check:
+
+```ruby
+can? :edit, @article
+```
+
+This will be very convenient when we will authorize the Rails Controller actions.
+
+For now, what you need to know, is that these four will be your most used, basic actions.
+
+One last action is `manage`. This action means that you have full permissions on the subject and you can perform any possible action. Knowing that, we can now rewrite our ability.rb example:
+
+```ruby
+can :read, Article, published: true
+
+return unless user.present?
+
+can [:read, :update], Article, user: user
+
+return unless user.admin?
+
+can :manage, Article
+```
+
+and say that the administrators are able to perform any action on the articles.
+
+```ruby
+can? :edit, @article # => true
+can? :destroy, @article # => true
+```
+
+Now that we learned about actions and their aliases let's see what we can do with the subjects
+
+## Can subjects
+
+The subject of an action is usually a Ruby class. Most of the times you want to define your permissions on specific classes, but this is not your only option.
+
+You can actually use any subject, and one of the most common cases is to just use a symbol.
+An admin dashboard could be protected by defining:
+
+```ruby
+can :read, :admin_dashboard
+```
+
+and checked with `can? :read, :admin_dashboard`.
+
+One special symbol is `:all`. All will allow an action on all possible subjects.
+
+In our example, it would not be uncommon to see the following:
+
+```ruby
+can :read, Article, published: true
+
+return unless user.present?
+
+can [:read, :update], Article, user: user
+
+return unless user.admin?
+
+can :manage, :all
+```
+
+and give all possible permissions to the administrator.
+
+Note that the code above allows the administrator to also `:read, :admin_dashboard`. `:manage` means literally **any** action, not only CRUD ones.
+
+> You **must and should** always check for specific permissions, but you don't need to define all of them if not needed.
+
+If at some point you have a new page reserved to the administrators, where they can translate articles, you should check for `can? :translate, @article`, but you don't need to define the ability, since the administrators can already do any action. It will be easy in the future to give the possibility for authors to translate their own articles by changing your permissions file:
+
+```ruby
+can :read, Article, published: true
+
+return unless user.present?
+
+can [:read, :update, :translate], Article, user: user
+
+return unless user.admin?
+
+can :manage, :all
+```
+
+## Checking other users abilities
+
+What if you want to determine the abilities of a `User` record that is not the `current_user`? Maybe we want to see if another user can update an article.
+
+```ruby
+Ability.new(some_user).can? :update, @article
+```
+
+You can also add an `ability` method in the `User` model and delegate the `can?` method:
+
+```ruby
+# app/models/user.rb
+class User
+ delegate :can?, :cannot?, to: :ability
+
+ def ability
+ @ability ||= Ability.new(self)
+ end
+end
+
+some_user.can? :update, @article
+```
+
+That's everything you know about defining and checking abilities. The DSL is very easy but yet very powerful. There's still a lot you need/should learn about defining abilities. You can [dig deeper](./hash_of_conditions.md) now, but we would suggest to stop, digest it, and proceed on a more Rails-specific topic: [Controller helpers](./controller_helpers.md) where you will learn how to secure your Rails application.
+
+Or you could already take a look at the session about [testing](./testing.md).
diff --git a/docs/devise.md b/docs/devise.md
new file mode 100644
index 000000000..30839511a
--- /dev/null
+++ b/docs/devise.md
@@ -0,0 +1,28 @@
+# Devise
+
+You should bypass CanCanCan's authorization for Devise controllers:
+
+```ruby
+class ApplicationController < ActionController::Base
+ protect_from_forgery
+
+ check_authorization unless: :devise_controller?
+end
+```
+
+It may be a good idea to specify the rescue from action:
+
+```ruby
+rescue_from CanCan::AccessDenied do |exception|
+ if current_user.nil?
+ session[:next] = request.fullpath
+ redirect_to login_url, alert: 'You have to log in to continue.'
+ else
+ respond_to do |format|
+ format.json { render nothing: true, status: :not_found }
+ format.html { redirect_to main_app.root_url, alert: exception.message }
+ format.js { render nothing: true, status: :not_found }
+ end
+ end
+end
+```
diff --git a/docs/fetching_records.md b/docs/fetching_records.md
new file mode 100644
index 000000000..56ff7f62a
--- /dev/null
+++ b/docs/fetching_records.md
@@ -0,0 +1,78 @@
+# Fetching records
+
+One of the key features of CanCanCan, compared to other authorization libraries, is the possibility to retrieve all the objects that the user is authorized to access. The following:
+
+```ruby
+Article.accessible_by(current_ability)
+```
+
+will use the rules you already defined to ensure that the users retrieve only a list of articles that they can read.
+
+This tool is very powerful and magic at the same time.
+
+Given the following ability file:
+
+```ruby
+can :read, Article, published: true
+
+return unless user.present?
+
+can :read, Article, user: user
+
+return unless user.admin?
+
+can :manage, :all
+```
+
+you will not only be able to check if the user `can? :read, @article` on a single article, but also to limit the articles fetched from the database, to only the ones that they can read.
+
+In an `index` action the following will just work:
+
+```ruby
+@articles = Article.accessible_by(current_ability)
+```
+
+`current_ability` is already made available by CanCanCan in your controller and the default action of `accessible_by` is `:index`, which is aliased by `:read`.
+
+You can change the action by passing it as the second argument. Here we find only the records the user has permission to update.
+
+```ruby
+@articles = Article.accessible_by(current_ability, :update)
+```
+
+And this is just an ActiveRecord scope so other scopes and pagination can be chained onto it.
+
+## Under the hood
+
+The call to accessible_by in the example above will generate the proper SQL to limit the records fetched.
+
+This works also with multiple `can` definitions, which allows you to define complex permission logic and have it translated properly to SQL.
+
+Given the definition:
+
+```ruby
+class Ability
+ can :read, Article, public: true
+ cannot :read, Article, self_managed: true
+ can :read, Article, user: user
+end
+```
+
+a call to `Article.accessible_by(current_ability)` generates the following SQL
+
+```sql
+SELECT *
+FROM articles
+WHERE (user_id = 1) OR (not (self_managed = 'true') AND (public = 'true'))
+```
+
+The generation of the SQL query is a very complex task and probably the most powerful feature of CanCanCan.
+
+Even if the default behaviour will suffice at the beginning, larger databases or more complex rules, might lead to very complex SQL queries.
+This might result in a slow fetching of records. This is why it is possible to use different strategies to generate the SQL.
+You will see that in one of the last chapters: [SQL strategies](./sql_strategies.md)
+
+## Blocks
+
+We haven't spoken about block abilities yet, but the SQL generation will not be possible if you have even a single rule that is defined using just a block.
+You can define SQL fragments in addition to block to fix that. But we'll see that in the [Define Abilities with Blocks](./define_abilities_with_blocks.md) chapter.
diff --git a/docs/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..8f2f92a38 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
@@ -27,4 +30,4 @@ if defined?(CanCan)
end
end
end
-```
\ No newline at end of file
+```
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..e5c6a32bb 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](./controller_helpers.md) chapter, we saw that when a resource is not authorized, a `CanCan::AccessDenied` exception is raised, and we offered a basic handling through `config/application.rb`. Let's now see what else we can do.
+
+The `CanCan::AccessDenied` exception is raised when calling `authorize!` in the controller and the user is not able to perform the given action.
+
+A message can optionally be provided.
```ruby
authorize! :read, Article, :message => "Unable to read this article."
@@ -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.
@@ -112,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
new file mode 100644
index 000000000..6ec8d4244
--- /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_check_abilities.md) we defined
+
+```ruby
+can :update, @article, user: user
+```
+
+to say that an Article can be updated only by it's author. But how does it work?
+
+The third argument of the `can` method (`{ user: user }`) is the hash of conditions for this rule.
+
+A hash of conditions can be passed to further restrict which records this permission applies to.
+
+In the example below the user will only have permission to read active projects which they own.
+
+```ruby
+can :read, Project, active: true, user_id: user.id
+```
+
+When defining a condition, the key should always be either a database column of the model, or the association name. In the example above, if the Project has defined
+
+```ruby
+belongs_to :owner, class_name: 'User', foreign_key: :user_id
+```
+
+the rule can also be written as:
+
+```ruby
+can :read, Project, active: true, owner: user
+```
+
+so by using the association `owner` instead of the database column `user_id`.
+
+You can nest conditions associations. Here the project can only be read if the category it belongs to is visible.
+
+```ruby
+can :read, Project, category: { visible: true }
+```
+
+An array or range can be passed to match multiple values. Here the user can only read projects of priority 1 through 3.
+
+```ruby
+can :read, Project, priority: 1..3
+```
+
+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 71%
rename from docs/Inherited-Resources.md
rename to docs/inherited_resources.md
index 6ac2bd6d8..b65c53a5a 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**
+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.
+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
@@ -18,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
@@ -32,6 +36,7 @@ end
```
## Mongoid
+
With mongoid it is necessary to reference `:project_id` instead of just `:project`
```ruby
@@ -39,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
new file mode 100644
index 000000000..78c06f023
--- /dev/null
+++ b/docs/installation.md
@@ -0,0 +1,35 @@
+# Installation
+
+Add this to your Gemfile:
+
+```ruby
+gem 'cancancan'
+```
+
+and run the `bundle install` command.
+
+Use the provided command to generate a template for your abilities file:
+
+```bash
+rails generate cancan:ability
+```
+
+This will generate the following file:
+
+```ruby
+# /app/models/ability.rb
+
+class Ability
+ include CanCan::Ability
+
+ def initialize(user)
+ end
+end
+```
+
+This is everything you need to start. :boom:
+
+All the permissions will be defined in this file.
+You can of course split it into multiple files if your application grows, but we'll cover that in a [later chapter](./split_ability.md).
+
+Let's now start with the basic concepts: [define and check abilities](./define_check_abilities.md).
diff --git a/docs/Translations-(i18n).md b/docs/internationalization.md
similarity index 92%
rename from docs/Translations-(i18n).md
rename to docs/internationalization.md
index 8fc75b968..578f930cb 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,8 +9,10 @@ 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:
+
+If you want to customize messages for some model or even for some ability, define translation like this:
```ruby
# models/ability.rb
@@ -15,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:
@@ -24,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:
@@ -38,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
new file mode 100644
index 000000000..74ee76121
--- /dev/null
+++ b/docs/introduction.md
@@ -0,0 +1,31 @@
+# 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 . It means a lot.
+
+Thank you,
+
+Alessandro Rodi
+
+Head to the [Installation](./installation.md) chapter.
diff --git a/docs/migrating.md b/docs/migrating.md
new file mode 100644
index 000000000..a669947ba
--- /dev/null
+++ b/docs/migrating.md
@@ -0,0 +1,19 @@
+# Migration Guide
+
+## From 2.x to 3.x
+
+### Breaking changes
+
+- **Defining abilities without a subject is not allowed anymore.**
+ For example, `can :dashboard` is not going to be accepted anymore and will raise an exception.
+ All these kind of rules need to be rethought in terms of `can action, subject`. `can :read, :dashboard` for example.
+
+- **Eager loading is not automatic.** If you relied on CanCanCan to avoid N+1 queries, this will not be the case anymore.
+ From now on, all necessary `includes`, `preload` or `eager_load` need to be explicitly written. We strongly suggest to have
+ `bullet` gem installed to identify your possible N+1 issues.
+
+- **Use of distinct.** Uniqueness of the results is guaranteed by using the `distinct` clause in the final query.
+ This may cause issues with some existing queries when using clauses like `group by` or `order` on associations.
+ Adding a custom `select` may be necessary in these cases.
+
+- **aliases are now merged.** When using the method to merge different Ability files, the aliases are now also merged. This might cause some incompatibility issues.
diff --git a/docs/model_adapter.md b/docs/model_adapter.md
new file mode 100644
index 000000000..0ebd36afc
--- /dev/null
+++ b/docs/model_adapter.md
@@ -0,0 +1,210 @@
+# Model Adapter
+
+CanCanCan includes a model adapter system that allows developers to add their own adapters for handling behaviour depending on the model used.
+
+CanCanCan provides maintained adapters for the following model types:
+
+- ActiveRecord (native in `cancancan` gem)
+ - ActiveRecord 4
+ - ActiveRecord 5
+- [Mongoid](https://github.com/CanCanCommunity/cancancan-mongoid)
+
+## Creating a Model Adapter
+
+Due to its flexible and extendable system of adapters, it is easy to implement a custom adapter if the currently provided adapters do not suffice.
+
+To facilitate an easy implementation of a new adapter CanCanCan provides you with an [Abstract Adapter](https://github.com/CanCanCommunity/cancancan/blob/develop/lib/cancan/model_adapters/abstract_adapter.rb) you can extend and build upon. This design allows for dynamic adapter handling and a decoupled handling of information.
+
+### The Abstract Adapter
+
+The abstract adapter has multiple methods that one has to overwrite in order to match the behaviour that is expected. It is used by the system to delegate the handling of fetching entries base on defined rules and conditions.
+
+#### for_class
+
+The `for_class?` method is a static method on the abstract adapter that has to be overwritten in your adapter.
+
+This method is used to determine whether a model should be passed to the adapter or not.
+
+If your `for_class?` implementation returns true, the adapter will be provided with the model to build and match the rules defined.
+
+Otherwise the adapter will be skipped and the other subclasses of the abstract adapter will be checked.
+
+#### database_records
+
+Used to implement the loading of entries from the database, by a developer-defined handling of the given rules for a model.
+
+### Dependencies
+
+Because cancancan wants to provide an easy method of writing and testing your own adapters it uses appraisals to test the code against different versions of dependencies.
+
+[Appraisals](https://github.com/thoughtbot/appraisal)
+
+Thus you can add your own entry for your gems and dependencies.
+
+An example could look like:
+
+cancancan/Appraisals
+
+```ruby
+
+appraise 'cancancan_custom_adapter' do
+ gem 'activerecord', '~> 5.0.2', require: 'active_record'
+
+ gemfile.platforms :jruby do
+ gem 'jdbc-postgres'
+ end
+
+ gemfile.platforms :ruby, :mswin, :mingw do
+ gem 'pg', '~> 0.21'
+ end
+end
+```
+
+You would have to replace the dependencies with ones that fit your custom adapter.
+
+After creating your dependency definition, run
+
+```bash
+bundle exec appraisal install
+```
+
+to install dependencies for your adapter.
+
+### The Specs
+
+To illustrate what a test for an adapter could look like, we will use [Mongoid](https://github.com/CanCanCommunity/cancancan-mongoid) as an example.
+
+In good TDD fashion we create a spec / test for the new adapter to later confirm our implementation.
+
+```ruby
+
+RSpec.describe CanCan::ModelAdapters::MongoidAdapter do
+
+ it 'is for only Mongoid classes' do
+ expect(CanCan::ModelAdapters::MongoidAdapter).not_to be_for_class(Object)
+ expect(CanCan::ModelAdapters::MongoidAdapter).to be_for_class(MongoidProject)
+ end
+
+ it 'finds record' do
+ project = MongoidProject.create
+ expect(CanCan::ModelAdapters::MongoidAdapter.find(MongoidProject, project.id)).to eq(project)
+ end
+
+ it "should return the correct records based on the defined ability" do
+ @ability.can :read, MongoidProject, :title => "Sir"
+ sir = MongoidProject.create(:title => 'Sir')
+ lord = MongoidProject.create(:title => 'Lord')
+ MongoidProject.accessible_by(@ability, :read).entries.should == [sir]
+ end
+
+end
+```
+
+In this case `MongoidProject` is a descendant of `MongoidDocument`. The implementation of this class will not be shown as it only acts as an example.
+
+### Running tests
+
+You can run tests for the project by running
+
+```bash
+bundle exec appraisal rake
+```
+
+or you can run tests only for your adapter with
+
+```bash
+bundle exec appraisal adapter_name rake
+```
+
+File specific tests can be run with:
+
+```shell
+bundle exec appraisal adapter_name rspec spec/cancan/model_adapters/adapter_name.rb
+```
+
+**Because we haven't implemented any functionality yet, the tests will fail.**
+
+### The Implementation
+
+First add a line to `lib/cancan.rb` to include the adapter if a condition is met. In this case we check if Mongoid is present.
+
+```ruby
+require 'cancan/model_adapters/mongoid_adapter' if defined? Mongoid
+```
+
+And after that, create a new adapter in `model_adapters`:
+
+```ruby
+module CanCan
+ module ModelAdapters
+ class MongoidAdapter < AbstractAdapter
+ def self.for_class?(model_class)
+ model_class <= Mongoid::Document
+ end
+
+ def database_records
+ if @rules.size == 0
+ @model_class.where(:_id => {'$exists' => false, '$type' => 7}) # return no records in Mongoid
+ else
+ @rules.inject(@model_class.all) do |records, rule|
+ if rule.base_behavior
+ records.or(rule.conditions)
+ else
+ records.excludes(rule.conditions)
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+module Mongoid::Document::ClassMethods
+ include CanCan::ModelAdditions::ClassMethods
+end
+```
+
+As mentioned before, there are methods that have to be overwritten in order to pass as a valid adapter.
+
+In this case we overwrite the `for_class?` method to validate that the given model is a descendant of MongoidDocument. The adapter will only be used if `for_class?` evalues to true.
+
+And in `database_records` we define the way data is loaded from the storage device. This message is used in `accessible_by`. In this example we fetch all entries for a model that match a given rule.
+
+**If no rules for an object are defined, a query will be run that returns no results.**
+
+If rules are present, we apply each of the rule conditions to them. The `rule.base_behavior` defines whether the rule should be additive or subtractive. It will result in false for `:cannot` and true for `:can`.
+
+Some model types add additional features to the conditions hash. With Mongoid, for example, you can do something like `:age.gt => 13`.
+Because the abstract adapter has no knowledge of this, we have to overwrite the provided methods in the new adapter.
+
+```ruby
+def self.override_conditions_hash_matching?(subject, conditions)
+ conditions.any? { |k,v| !k.kind_of?(Symbol) }
+end
+
+def self.matches_conditions_hash?(subject, conditions)
+ subject.matches? subject.class.where(conditions).selector
+end
+```
+
+### Additional Examples
+
+Eventhough CanCanCan tries to make the implementation of custom adapters easy and flexible, it can be hard task.
+
+Thus you'd probably be best served with inspecting the actual implementation of the `activerecord` adapter to get a better overview how a battle tested adapter is structured and implemented.
+
+#### Implementation
+
+- [ActiveRecord Base](../lib/cancan/model_adapters/active_record_adapter.rb)
+- [ActiveRecord 4](../lib/cancan/model_adapters/active_record_4_adapter.rb)
+- [ActiveRecord 5](../lib/cancan/model_adapters/active_record_5_adapter.rb)
+
+#### Tests / Specs
+
+- [ActiveRecord Base](../spec/cancan/model_adapters/active_record_adapter_spec.rb)
+- [ActiveRecord 4](../spec/cancan/model_adapters/active_record_4_adapter_spec.rb)
+- [ActiveRecord 5](../spec/cancan/model_adapters/active_record_5_adapter_spec.rb)
+
+**Mongoid, the adapter used in this entry as an example, can be found at:**
+
+- [Mongoid](https://github.com/CanCanCommunity/cancancan-mongoid)
diff --git a/docs/Nested-Resources.md b/docs/nested_resources.md
similarity index 89%
rename from docs/Nested-Resources.md
rename to docs/nested_resources.md
index 18d37d1ce..114280a3e 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
@@ -26,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
@@ -83,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
@@ -154,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:
@@ -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.
+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 90%
rename from docs/Role-Based-Authorization.md
rename to docs/role_based_authorization.md
index 86d6deda1..b83f030df 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.
@@ -21,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
@@ -49,10 +52,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
@@ -81,9 +83,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
@@ -108,11 +111,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.
@@ -142,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](http://rubygems.org/gems/canard) ([code & howto](http://github.com/james2m/canard)).
## Alternative Role Inheritance
@@ -172,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
similarity index 89%
rename from docs/Rules-compression.md
rename to 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
new file mode 100644
index 000000000..5fb0b7b39
--- /dev/null
+++ b/docs/split_ability.md
@@ -0,0 +1,78 @@
+# Split the ability file
+
+When the application becomes more complex and many abilities are defined, you might want to start splitting your ability file into multiple files.
+
+We will show here an example on how to split your ability file on a “per-model” basis.
+
+Imagine the following scenario:
+
+```ruby
+# app/models/ability.rb
+class Ability
+ include CanCan::Ability
+ def initialize(user)
+ can :edit, User, id: user.id
+ can :read, Book, published: true
+ can :edit, Book, user_id: user.id
+ can :manage, Comment, user_id: user.id
+ end
+end
+```
+
+This is, of course, not too complicated, and in a real world application we would not split this file, but for didactic reasons we want to split this file “per-model”.
+
+We suggest to have an app/abilities folder and create a separate file for each model (exactly as you would do with Pundit).
+
+```ruby
+# app/abilities/user_ability.rb
+class UserAbility
+ include CanCan::Ability
+ def initialize(user)
+ can :edit, User, id: user.id
+ end
+end
+
+# app/abilities/comment_ability.rb
+class CommentAbility
+ include CanCan::Ability
+ def initialize(user)
+ can :manage, Comment, user_id: user.id
+ end
+end
+
+# app/abilities/book_ability.rb
+class BookAbility
+ include CanCan::Ability
+ def initialize(user)
+ can :read, Book, published: true
+ can :edit, Book, user_id: user.id
+ end
+end
+```
+
+Now you can override the `current_ability` method in you controller. For example:
+
+```ruby
+# app/controllers/books_controller.rb
+class BooksController
+ def current_ability
+ @current_ability ||= BookAbility.new(current_user)
+ end
+end
+```
+
+Using this technique you have all the power of CanCanCan ability files, that allows you define your permissions with hash of conditions. This means you can check permissions on a single instance of a model, but also retrieve automatically all the instances where you are authorized to perform a certain action.
+
+You can call `can? :read, @book` but also `Book.accessible_by(current_ability, :read)` that will return all the books you can read.
+
+When your controller is executed, it will read only the ability file that you need, saving time and memory.
+
+## Merge ability files
+
+Abilities files can always be merged together, so if you need two of them in one Controller, you can simply:
+
+```ruby
+def current_ability
+ @current_ability ||= ReadAbility.new(current_user).merge(WriteAbility.new(current_user))
+end
+```
diff --git a/docs/sql_strategies.md b/docs/sql_strategies.md
new file mode 100644
index 000000000..578656841
--- /dev/null
+++ b/docs/sql_strategies.md
@@ -0,0 +1,67 @@
+# SQL Strategies
+
+When [fetching records](./fetching_records.md) from the database, CanCanCan generates the SQL for you.
+
+The generated SQL, although correct, might not be performant.
+
+In the history of CanCanCan we had many issues with different versions of the generated SQL and we finally reached to the conclusion that there's no single solutions that fits all the needs.
+
+That's why in the latest versions of CanCanCan, you are given the possibility to customize how the SQL is generated and choose from multiple options.
+
+You can customize the SQL strategy globally with:
+
+```ruby
+# config/initializers/cancancan.rb
+
+CanCan.accessible_by_strategy = :subquery # :left_join is the default
+```
+
+or on a single `accessible_by` call:
+
+```ruby
+Article.accessible_by(current_ability, strategy: :subquery) # :left_join is, again, the default
+```
+
+or on a group of queries:
+
+```ruby
+CanCan.with_accessible_by_strategy(:subquery) do
+ Article.accessible_by(current_ability)
+ # ...
+end
+```
+
+Here is a complete list of the available strategies, explained by examples.
+
+Given the following permissions:
+
+```ruby
+can :read, Article, mentions: { user: { name: u.name } }
+```
+
+## :left_join
+
+Note that in the default strategy, we use the `DISTINCT` clause which might cause performance issues.
+
+```sql
+SELECT DISTINCT "articles".*
+FROM "articles"
+LEFT OUTER JOIN "mentions" ON "mentions"."article_id" = "articles"."id"
+LEFT OUTER JOIN "users" ON "users"."id" = "mentions"."user_id"
+WHERE "users"."name" = 'pippo'
+```
+
+## :subquery
+
+By using the `:subquery` strategy, the `DISTINCT` clause can be removed.
+
+```sql
+SELECT "articles".*
+FROM "articles"
+WHERE "articles"."id" IN
+ (SELECT "articles"."id"
+ FROM "articles"
+ LEFT OUTER JOIN "mentions" ON "mentions"."article_id" = "articles"."id"
+ LEFT OUTER JOIN "users" ON "users"."id" = "legacy_mentions"."user_id"
+ WHERE "users"."name" = 'pippo')
+```
diff --git a/docs/Testing-Abilities.md b/docs/testing.md
similarity index 53%
rename from docs/Testing-Abilities.md
rename to docs/testing.md
index 76dc431e0..af1516a9d 100644
--- a/docs/Testing-Abilities.md
+++ b/docs/testing.md
@@ -1,99 +1,83 @@
-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/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_master.gemfile b/gemfiles/activerecord_7.0.0.gemfile
similarity index 54%
rename from gemfiles/activerecord_master.gemfile
rename to gemfiles/activerecord_7.0.0.gemfile
index 8328cadc1..1f0c169dd 100644
--- a/gemfiles/activerecord_master.gemfile
+++ b/gemfiles/activerecord_7.0.0.gemfile
@@ -2,9 +2,9 @@
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"
+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"
@@ -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
new file mode 100644
index 000000000..ba43821e8
--- /dev/null
+++ b/gemfiles/activerecord_main.gemfile
@@ -0,0 +1,22 @@
+# This file was generated by Appraisal
+
+source "https://rubygems.org"
+
+git "https://github.com/rails/rails", branch: "main" do
+ gem "actionpack", require: "action_pack"
+ gem "activerecord", require: "active_record"
+ gem "activesupport", require: "active_support/all"
+end
+
+platforms :jruby do
+ gem "activerecord-jdbcsqlite3-adapter"
+ gem "jdbc-sqlite3"
+ gem "jdbc-postgres"
+end
+
+platforms :ruby, :mswin, :mingw do
+ gem "pg", "~> 1.3.4"
+ gem "sqlite3", "~> 1.4.2"
+end
+
+gemspec path: "../"
diff --git a/lib/cancan.rb b/lib/cancan.rb
index 80d7a24dc..c4f92c928 100644
--- a/lib/cancan.rb
+++ b/lib/cancan.rb
@@ -21,4 +21,9 @@
require 'cancan/model_adapters/active_record_adapter'
require 'cancan/model_adapters/active_record_4_adapter'
require 'cancan/model_adapters/active_record_5_adapter'
+ require 'cancan/model_adapters/strategies/base'
+ require 'cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery'
+ require 'cancan/model_adapters/strategies/joined_alias_exists_subquery'
+ require 'cancan/model_adapters/strategies/left_join'
+ require 'cancan/model_adapters/strategies/subquery'
end
diff --git a/lib/cancan/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/conditions_matcher.rb b/lib/cancan/conditions_matcher.rb
index e07bfc13b..b30d3e2a4 100644
--- a/lib/cancan/conditions_matcher.rb
+++ b/lib/cancan/conditions_matcher.rb
@@ -33,16 +33,24 @@ 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)
+
+ matches_base_parent_conditions &&
+ (!adapter.override_nested_subject_conditions_matching?(parent, child, @conditions) ||
+ adapter.nested_subject_matches_conditions?(parent, child, @conditions))
end
# Checks if the given subject matches the given conditions hash.
- # This behavior can be overriden by a model adapter by defining two class methods:
+ # This behavior can be overridden by a model adapter by defining two class methods:
# override_matching_for_conditions?(subject, conditions) and
# matches_conditions_hash?(subject, conditions)
def matches_conditions_hash?(subject, conditions = @conditions)
- return true if conditions.empty?
+ return true if conditions.is_a?(Hash) && conditions.empty?
adapter = model_adapter(subject)
@@ -50,10 +58,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, subject, conditions)
+ if conditions.is_a?(Hash)
+ 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_all_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)
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/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/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
diff --git a/lib/cancan/model_adapters/abstract_adapter.rb b/lib/cancan/model_adapters/abstract_adapter.rb
index 4e0e51a25..4041dcb49 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)
@@ -33,6 +35,18 @@ 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_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..e3e84c06b 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
@@ -18,6 +20,31 @@ 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)
+ parent_child_conditions(parent, child, all_conditions).present?
+ end
+
+ # parent_id condition can be an array of integer or one integer, we check the parent against this
+ def nested_subject_matches_conditions?(parent, child, all_conditions)
+ id_condition = parent_child_conditions(parent, child, all_conditions)
+ return id_condition.include?(parent.id) if id_condition.is_a? Array
+ return id_condition == parent.id if id_condition.is_a? Integer
+
+ false
+ end
+
+ def parent_child_conditions(parent, child, all_conditions)
+ child_class = child.is_a?(Class) ? child : child.class
+ 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
# method directly, but instead go through ModelAdditions#accessible_by.
#
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/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/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/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..b27721ad0
--- /dev/null
+++ b/lib/cancan/model_adapters/strategies/joined_alias_each_rule_as_exists_subquery.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: false
+
+module CanCan
+ module ModelAdapters
+ class Strategies
+ class JoinedAliasEachRuleAsExistsSubquery < Base
+ def execute!
+ model_class
+ .joins(
+ "JOIN #{quoted_table_name} AS #{quoted_aliased_table_name} ON " \
+ "#{quoted_aliased_table_name}.#{quoted_primary_key} = #{quoted_table_name}.#{quoted_primary_key}"
+ )
+ .where(double_exists_sql)
+ end
+
+ def double_exists_sql
+ double_exists_sql = ''
+
+ compressed_rules.each_with_index do |rule, index|
+ double_exists_sql << ' OR ' if index.positive?
+ double_exists_sql << "EXISTS (#{sub_query_for_rule(rule).to_sql})"
+ end
+
+ double_exists_sql
+ end
+
+ def sub_query_for_rule(rule)
+ conditions_extractor = ConditionsExtractor.new(model_class)
+ rule_where_conditions = extract_multiple_conditions(conditions_extractor, [rule])
+ joins_hash, left_joins_hash = extract_joins_from_rule(rule)
+ sub_query_for_rules_and_join_hashes(rule_where_conditions, joins_hash, left_joins_hash)
+ end
+
+ def sub_query_for_rules_and_join_hashes(rule_where_conditions, joins_hash, left_joins_hash)
+ model_class
+ .select('1')
+ .joins(joins_hash)
+ .left_joins(left_joins_hash)
+ .where(
+ "#{quoted_table_name}.#{quoted_primary_key} = " \
+ "#{quoted_aliased_table_name}.#{quoted_primary_key}"
+ )
+ .where(rule_where_conditions)
+ .limit(1)
+ end
+
+ def extract_joins_from_rule(rule)
+ joins = {}
+ left_joins = {}
+
+ extra_joins_recursive([], rule.conditions, joins, left_joins)
+ [joins, left_joins]
+ end
+
+ def extra_joins_recursive(current_path, conditions, joins, left_joins)
+ conditions.each do |key, value|
+ if value.is_a?(Hash)
+ current_path << key
+ extra_joins_recursive(current_path, value, joins, left_joins)
+ current_path.pop
+ else
+ extra_joins_recursive_merge_joins(current_path, value, joins, left_joins)
+ end
+ end
+ end
+
+ def extra_joins_recursive_merge_joins(current_path, value, joins, left_joins)
+ hash_joins = current_path_to_hash(current_path)
+
+ if value.nil?
+ left_joins.deep_merge!(hash_joins)
+ else
+ joins.deep_merge!(hash_joins)
+ end
+ end
+
+ # Converts an array like [:child, :grand_child] into a hash like {child: {grand_child: {}}
+ def current_path_to_hash(current_path)
+ hash_joins = {}
+ current_hash_joins = hash_joins
+
+ current_path.each do |path_part|
+ new_hash = {}
+ current_hash_joins[path_part] = new_hash
+ current_hash_joins = new_hash
+ end
+
+ hash_joins
+ end
+ end
+ end
+ end
+end
diff --git a/lib/cancan/model_adapters/strategies/joined_alias_exists_subquery.rb b/lib/cancan/model_adapters/strategies/joined_alias_exists_subquery.rb
new file mode 100644
index 000000000..a95592a0e
--- /dev/null
+++ b/lib/cancan/model_adapters/strategies/joined_alias_exists_subquery.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module CanCan
+ module ModelAdapters
+ class Strategies
+ class JoinedAliasExistsSubquery < Base
+ def execute!
+ model_class
+ .joins(
+ "JOIN #{quoted_table_name} AS #{quoted_aliased_table_name} ON " \
+ "#{quoted_aliased_table_name}.#{quoted_primary_key} = #{quoted_table_name}.#{quoted_primary_key}"
+ )
+ .where("EXISTS (#{joined_alias_exists_subquery_inner_query.to_sql})")
+ end
+
+ def joined_alias_exists_subquery_inner_query
+ model_class
+ .unscoped
+ .select('1')
+ .left_joins(joins)
+ .where(*where_conditions)
+ .where(
+ "#{quoted_table_name}.#{quoted_primary_key} = " \
+ "#{quoted_aliased_table_name}.#{quoted_primary_key}"
+ )
+ .limit(1)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/cancan/model_adapters/strategies/left_join.rb b/lib/cancan/model_adapters/strategies/left_join.rb
new file mode 100644
index 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
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)
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/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
diff --git a/lib/generators/cancan/ability/templates/ability.rb b/lib/generators/cancan/ability/templates/ability.rb
index ef26a8c38..08b2ba170 100644
--- a/lib/generators/cancan/ability/templates/ability.rb
+++ b/lib/generators/cancan/ability/templates/ability.rb
@@ -4,14 +4,12 @@ class Ability
include CanCan::Ability
def initialize(user)
- # Define abilities for the passed in user here. For example:
+ # Define abilities for the user here. For example:
#
- # user ||= User.new # guest user (not logged in)
- # if user.admin?
- # can :manage, :all
- # else
- # can :read, :all
- # end
+ # return unless user.present?
+ # can :read, :all
+ # return unless user.admin?
+ # can :manage, :all
#
# The first argument to `can` is the action you are giving the user
# permission to do.
@@ -26,9 +24,9 @@ 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
+ # https://github.com/CanCanCommunity/cancancan/blob/develop/docs/define_check_abilities.md
end
end
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 @@
+
+
+
diff --git a/logo/new_relic.png b/logo/new_relic.png
new file mode 100644
index 000000000..6b9f27c11
Binary files /dev/null and b/logo/new_relic.png differ
diff --git a/logo/ontra.png b/logo/ontra.png
new file mode 100644
index 000000000..e7f243b70
Binary files /dev/null and b/logo/ontra.png differ
diff --git a/rubymine_rspec.png b/rubymine_rspec.png
new file mode 100644
index 000000000..03a952661
Binary files /dev/null and b/rubymine_rspec.png differ
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
diff --git a/spec/cancan/controller_additions_spec.rb b/spec/cancan/controller_additions_spec.rb
index 98b2e6643..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
@@ -139,7 +141,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..f66eb0461 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)
@@ -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
@@ -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_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/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..90065108e 100644
--- a/spec/cancan/model_adapters/active_record_adapter_spec.rb
+++ b/spec/cancan/model_adapters/active_record_adapter_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CanCan::ModelAdapters::ActiveRecordAdapter do
+RSpec.describe CanCan::ModelAdapters::ActiveRecordAdapter do
let(:true_v) do
ActiveRecord::Base.connection.quoted_true
end
@@ -49,6 +49,11 @@
t.timestamps null: false
end
+ create_table(:legacy_comments) do |t|
+ t.integer :post_id
+ t.timestamps null: false
+ end
+
create_table(:legacy_mentions) do |t|
t.integer :user_id
t.integer :article_id
@@ -62,6 +67,7 @@
end
class Project < ActiveRecord::Base
+ has_many :articles
has_many :comments
end
@@ -96,6 +102,12 @@ class Mention < ActiveRecord::Base
class Comment < ActiveRecord::Base
belongs_to :article
+ belongs_to :project
+ end
+
+ class LegacyComment < ActiveRecord::Base
+ belongs_to :article, foreign_key: 'post_id'
+ belongs_to :project
end
class User < ActiveRecord::Base
@@ -263,7 +275,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 +285,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 +312,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
@@ -441,6 +453,94 @@ 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
+
+ describe 'when can? is used with a Hash (nested resources)' do
+ let(:user1) { User.create!(name: 'user1') }
+ let(:user2) { User.create!(name: 'user2') }
+
+ before do
+ category = Category.create!(name: 'category')
+ @article1 = Article.create!(name: 'article1', category: category, user: user1)
+ @article2 = Article.create!(name: 'article2', category: category, user: user2)
+ @comment1 = Comment.create!(article: @article1)
+ @comment2 = Comment.create!(article: @article2)
+ @legacy_comment1 = LegacyComment.create!(article: @article1)
+ @legacy_comment2 = LegacyComment.create!(article: @article2)
+ end
+
+ context 'when conditions are defined using the parent model' do
+ let(:ability) do
+ Ability.new(user1).tap do |ability|
+ ability.can :read, Article
+ ability.can :manage, Article, user: user1
+ ability.can :manage, Comment, article: user1.articles
+ ability.can :manage, LegacyComment, article: user1.articles
+ 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
+
+ context 'when conditions are defined using the parent id' do
+ let(:ability) do
+ Ability.new(user1).tap do |ability|
+ ability.can :read, Article
+ ability.can :manage, Article, user_id: user1.id
+ ability.can :manage, Comment, article_id: user1.article_ids
+ ability.can :manage, LegacyComment, post_id: user1.article_ids
+ 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
@@ -641,7 +741,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 +764,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')
@@ -1004,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
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..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,12 +46,68 @@ 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
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 +134,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