cancancan 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +15 -0
  2. data/CHANGELOG.rdoc +427 -0
  3. data/CONTRIBUTING.md +11 -0
  4. data/Gemfile +23 -0
  5. data/LICENSE +20 -0
  6. data/README.rdoc +161 -0
  7. data/Rakefile +18 -0
  8. data/init.rb +1 -0
  9. data/lib/cancan.rb +13 -0
  10. data/lib/cancan/ability.rb +324 -0
  11. data/lib/cancan/controller_additions.rb +397 -0
  12. data/lib/cancan/controller_resource.rb +286 -0
  13. data/lib/cancan/exceptions.rb +50 -0
  14. data/lib/cancan/inherited_resource.rb +20 -0
  15. data/lib/cancan/matchers.rb +14 -0
  16. data/lib/cancan/model_adapters/abstract_adapter.rb +56 -0
  17. data/lib/cancan/model_adapters/active_record_adapter.rb +180 -0
  18. data/lib/cancan/model_adapters/data_mapper_adapter.rb +34 -0
  19. data/lib/cancan/model_adapters/default_adapter.rb +7 -0
  20. data/lib/cancan/model_adapters/mongoid_adapter.rb +54 -0
  21. data/lib/cancan/model_additions.rb +31 -0
  22. data/lib/cancan/rule.rb +147 -0
  23. data/lib/cancancan.rb +1 -0
  24. data/lib/generators/cancan/ability/USAGE +4 -0
  25. data/lib/generators/cancan/ability/ability_generator.rb +11 -0
  26. data/lib/generators/cancan/ability/templates/ability.rb +32 -0
  27. data/spec/README.rdoc +28 -0
  28. data/spec/cancan/ability_spec.rb +455 -0
  29. data/spec/cancan/controller_additions_spec.rb +141 -0
  30. data/spec/cancan/controller_resource_spec.rb +553 -0
  31. data/spec/cancan/exceptions_spec.rb +58 -0
  32. data/spec/cancan/inherited_resource_spec.rb +60 -0
  33. data/spec/cancan/matchers_spec.rb +29 -0
  34. data/spec/cancan/model_adapters/active_record_adapter_spec.rb +358 -0
  35. data/spec/cancan/model_adapters/data_mapper_adapter_spec.rb +118 -0
  36. data/spec/cancan/model_adapters/default_adapter_spec.rb +7 -0
  37. data/spec/cancan/model_adapters/mongoid_adapter_spec.rb +226 -0
  38. data/spec/cancan/rule_spec.rb +52 -0
  39. data/spec/matchers.rb +13 -0
  40. data/spec/spec.opts +2 -0
  41. data/spec/spec_helper.rb +77 -0
  42. metadata +126 -0
data/Gemfile ADDED
@@ -0,0 +1,23 @@
1
+ source "https://rubygems.org"
2
+
3
+ case ENV["MODEL_ADAPTER"]
4
+ when nil, "active_record"
5
+ # Sqlite for CRuby, Rubinius, including Windows and RubyInstaller
6
+ gem "sqlite3", :platform => [:ruby, :mswin, :mingw]
7
+ # Sqlite for JRuby
8
+ gem 'activerecord-jdbcsqlite3-adapter', :platform => :jruby
9
+ gem "activerecord", '~> 3.0.9', :require => "active_record"
10
+ gem "with_model", "~> 0.2.5"
11
+ gem "meta_where"
12
+ when "data_mapper"
13
+ gem "dm-core", "~> 1.0.2"
14
+ gem "dm-sqlite-adapter", "~> 1.0.2"
15
+ gem "dm-migrations", "~> 1.0.2"
16
+ when "mongoid"
17
+ gem "bson_ext", "~> 1.1"
18
+ gem "mongoid", "~> 2.0.0.beta.20"
19
+ else
20
+ raise "Unknown model adapter: #{ENV["MODEL_ADAPTER"]}"
21
+ end
22
+
23
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Ryan Bates
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,161 @@
1
+ = CanCan
2
+ {<img src="https://fury-badge.herokuapp.com/rb/cancancan.png" alt="Gem Version" />}[http://badge.fury.io/rb/cancancan]
3
+ {<img src="https://secure.travis-ci.org/bryanrite/cancancan.png?branch=master" />}[http://travis-ci.org/bryanrite/cancancan]
4
+ {<img src="https://codeclimate.com/github/bryanrite/cancancan.png" />}[https://codeclimate.com/github/bryanrite/cancancan]
5
+
6
+ Wiki[https://github.com/bryanrite/cancancan/wiki] | RDocs[http://rdoc.info/projects/ryanb/cancan] | Screencast[http://railscasts.com/episodes/192-authorization-with-cancan]
7
+
8
+ CanCan is an authorization library for Ruby on Rails which restricts what resources a given user is allowed to access. All permissions are defined in a single location (the +Ability+ class) and not duplicated across controllers, views, and database queries.
9
+
10
+
11
+ == Mission
12
+
13
+ This repo is a continuation of the dead CanCan[https://github.com/rbates/cancan] project. Our mission is to keep CanCan alive and moving forward, with maintenance fixes and new features. Pull Requests are welcome!
14
+
15
+ I am currently focusing on the 1.x branch for the immediate future, making sure it is up to date as well as ensuring compatibility with Rails 4+. I will take a look into the 2.x branch and try to see what improvements, reorganizations and redesigns Ryan was attempting and go forward from there.
16
+
17
+ Any help is greatly appreciated, feel free to submit pull-requests or open issues.
18
+
19
+
20
+ == Installation
21
+
22
+ In <b>Rails 3</b>, add this to your Gemfile and run the +bundle+ command.
23
+
24
+ gem 'cancancan', '~> 1.7'
25
+
26
+ In <b>Rails 2</b>, add this to your environment.rb file.
27
+
28
+ config.gem "cancancan"
29
+
30
+ Alternatively, you can install it as a plugin.
31
+
32
+ rails plugin install git://github.com/bryanrite/cancancan.git
33
+
34
+
35
+ == Getting Started
36
+
37
+ CanCan expects a +current_user+ method to exist in the controller. First, set up some authentication (such as Authlogic[https://github.com/binarylogic/authlogic] or Devise[https://github.com/plataformatec/devise]). See {Changing Defaults}[https://github.com/bryanrite/cancan/wiki/changing-defaults] if you need different behavior.
38
+
39
+
40
+ === 1. Define Abilities
41
+
42
+ User permissions are defined in an +Ability+ class. CanCan 1.5 includes a Rails 3 generator for creating this class.
43
+
44
+ rails g cancan:ability
45
+
46
+ In Rails 2.3, just add a new class in <tt>app/models/ability.rb</tt> with the following contents:
47
+
48
+ class Ability
49
+ include CanCan::Ability
50
+
51
+ def initialize(user)
52
+ end
53
+ end
54
+
55
+ See {Defining Abilities}[https://github.com/bryanrite/cancan/wiki/defining-abilities] for details.
56
+
57
+
58
+ === 2. Check Abilities & Authorization
59
+
60
+ The current user's permissions can then be checked using the <tt>can?</tt> and <tt>cannot?</tt> methods in the view and controller.
61
+
62
+ <% if can? :update, @article %>
63
+ <%= link_to "Edit", edit_article_path(@article) %>
64
+ <% end %>
65
+
66
+ See {Checking Abilities}[https://github.com/bryanrite/cancancan/wiki/checking-abilities] for more information
67
+
68
+ The <tt>authorize!</tt> method in the controller will raise an exception if the user is not able to perform the given action.
69
+
70
+ def show
71
+ @article = Article.find(params[:id])
72
+ authorize! :read, @article
73
+ end
74
+
75
+ 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 filter to load the resource into an instance variable and authorize it for every action.
76
+
77
+ class ArticlesController < ApplicationController
78
+ load_and_authorize_resource
79
+
80
+ def show
81
+ # @article is already loaded and authorized
82
+ end
83
+ end
84
+
85
+ See {Authorizing Controller Actions}[https://github.com/bryanrite/cancancan/wiki/authorizing-controller-actions] for more information.
86
+
87
+
88
+ ==== Strong Parameters
89
+
90
+ When using <tt>strong_parameters</tt> or Rails 4+, you have to sanitize inputs before saving the record, in actions such as <tt>:create</tt> and <tt>:update</tt>.
91
+
92
+ By default, CanCan will try to sanitize the input on <tt>:create</tt> and <tt>:update</tt> routes by seeing if your controller will respond to the following methods (in order):
93
+
94
+ * <tt>create_params</tt> or <tt>update_params</tt> (depending on the action you are performing)
95
+ * <tt><model_name>_params</tt> such as <tt>article_params</tt> (this is the default convention in rails for naming your param method)
96
+ * <tt>resource_params</tt> (a generically named method you could specify in each controller)
97
+
98
+ Additionally, <tt>load_and_authorize_resource</tt> can now take a <tt>param_method</tt> option to specify a custom method in the controller to run to sanitize input.
99
+
100
+ class ArticlesController < ApplicationController
101
+ load_and_authorize_resource param_method: :my_sanitizer
102
+
103
+ def create
104
+ if @article.save
105
+ # hurray
106
+ else
107
+ render :new
108
+ end
109
+ end
110
+
111
+ private
112
+
113
+ def my_sanitizer
114
+ params.require(:article).permit(:name)
115
+ end
116
+ end
117
+
118
+ === 3. Handle Unauthorized Access
119
+
120
+ If the user authorization fails, a <tt>CanCan::AccessDenied</tt> exception will be raised. You can catch this and modify its behavior in the +ApplicationController+.
121
+
122
+ class ApplicationController < ActionController::Base
123
+ rescue_from CanCan::AccessDenied do |exception|
124
+ redirect_to root_url, :alert => exception.message
125
+ end
126
+ end
127
+
128
+ See {Exception Handling}[https://github.com/bryanrite/cancancan/wiki/exception-handling] for more information.
129
+
130
+
131
+ === 4. Lock It Down
132
+
133
+ If you want to ensure authorization happens on every action in your application, add +check_authorization+ to your ApplicationController.
134
+
135
+ class ApplicationController < ActionController::Base
136
+ check_authorization
137
+ end
138
+
139
+ This will raise an exception if authorization is not performed in an action. If you want to skip this add +skip_authorization_check+ to a controller subclass. See {Ensure Authorization}[https://github.com/bryanrite/cancancan/wiki/Ensure-Authorization] for more information.
140
+
141
+
142
+ == Wiki Docs
143
+
144
+ * {Upgrading to 1.6}[https://github.com/bryanrite/cancancan/wiki/Upgrading-to-1.6]
145
+ * {Defining Abilities}[https://github.com/bryanrite/cancancan/wiki/Defining-Abilities]
146
+ * {Checking Abilities}[https://github.com/bryanrite/cancancan/wiki/Checking-Abilities]
147
+ * {Authorizing Controller Actions}[https://github.com/bryanrite/cancancan/wiki/Authorizing-Controller-Actions]
148
+ * {Exception Handling}[https://github.com/bryanrite/cancancan/wiki/Exception-Handling]
149
+ * {Changing Defaults}[https://github.com/bryanrite/cancancan/wiki/Changing-Defaults]
150
+ * {See more}[https://github.com/bryanrite/cancancan/wiki]
151
+
152
+ == Questions or Problems?
153
+
154
+ If you have any issues with CanCan which you cannot find the solution to in the documentation[https://github.com/bryanrite/cancancan/wiki], please add an {issue on GitHub}[https://github.com/bryanrite/cancancan/issues] or fork the project and send a pull request.
155
+
156
+ To get the specs running you should call +bundle+ and then +rake+. See the {spec/README}[https://github.com/bryanrite/cancancan/blob/master/spec/README.rdoc] for more information.
157
+
158
+
159
+ == Special Thanks
160
+
161
+ CanCan was inspired by declarative_authorization[https://github.com/stffn/declarative_authorization/] and aegis[https://github.com/makandra/aegis]. Also many thanks to the CanCan contributors[https://github.com/bryanrite/cancancan/contributors]. See the CHANGELOG[https://github.com/bryanrite/cancancan/blob/master/CHANGELOG.rdoc] for the full list.
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rspec/core/rake_task'
4
+
5
+ desc "Run RSpec"
6
+ RSpec::Core::RakeTask.new do |t|
7
+ t.verbose = false
8
+ end
9
+
10
+ desc "Run specs for all adapters"
11
+ task :spec_all do
12
+ %w[active_record data_mapper mongoid].each do |model_adapter|
13
+ puts "MODEL_ADAPTER = #{model_adapter}"
14
+ system "rake spec MODEL_ADAPTER=#{model_adapter}"
15
+ end
16
+ end
17
+
18
+ task :default => :spec
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'cancan'
@@ -0,0 +1,13 @@
1
+ require 'cancan/ability'
2
+ require 'cancan/rule'
3
+ require 'cancan/controller_resource'
4
+ require 'cancan/controller_additions'
5
+ require 'cancan/model_additions'
6
+ require 'cancan/exceptions'
7
+ require 'cancan/inherited_resource'
8
+
9
+ require 'cancan/model_adapters/abstract_adapter'
10
+ require 'cancan/model_adapters/default_adapter'
11
+ require 'cancan/model_adapters/active_record_adapter' if defined? ActiveRecord
12
+ require 'cancan/model_adapters/data_mapper_adapter' if defined? DataMapper
13
+ require 'cancan/model_adapters/mongoid_adapter' if defined?(Mongoid) && defined?(Mongoid::Document)
@@ -0,0 +1,324 @@
1
+ module CanCan
2
+
3
+ # This module is designed to be included into an Ability class. This will
4
+ # provide the "can" methods for defining and checking abilities.
5
+ #
6
+ # class Ability
7
+ # include CanCan::Ability
8
+ #
9
+ # def initialize(user)
10
+ # if user.admin?
11
+ # can :manage, :all
12
+ # else
13
+ # can :read, :all
14
+ # end
15
+ # end
16
+ # end
17
+ #
18
+ module Ability
19
+ # Check if the user has permission to perform a given action on an object.
20
+ #
21
+ # can? :destroy, @project
22
+ #
23
+ # You can also pass the class instead of an instance (if you don't have one handy).
24
+ #
25
+ # can? :create, Project
26
+ #
27
+ # Nested resources can be passed through a hash, this way conditions which are
28
+ # dependent upon the association will work when using a class.
29
+ #
30
+ # can? :create, @category => Project
31
+ #
32
+ # Any additional arguments will be passed into the "can" block definition. This
33
+ # can be used to pass more information about the user's request for example.
34
+ #
35
+ # can? :create, Project, request.remote_ip
36
+ #
37
+ # can :create, Project do |project, remote_ip|
38
+ # # ...
39
+ # end
40
+ #
41
+ # Not only can you use the can? method in the controller and view (see ControllerAdditions),
42
+ # but you can also call it directly on an ability instance.
43
+ #
44
+ # ability.can? :destroy, @project
45
+ #
46
+ # This makes testing a user's abilities very easy.
47
+ #
48
+ # def test "user can only destroy projects which he owns"
49
+ # user = User.new
50
+ # ability = Ability.new(user)
51
+ # assert ability.can?(:destroy, Project.new(:user => user))
52
+ # assert ability.cannot?(:destroy, Project.new)
53
+ # end
54
+ #
55
+ # Also see the RSpec Matchers to aid in testing.
56
+ def can?(action, subject, *extra_args)
57
+ match = relevant_rules_for_match(action, subject).detect do |rule|
58
+ rule.matches_conditions?(action, subject, extra_args)
59
+ end
60
+ match ? match.base_behavior : false
61
+ end
62
+
63
+ # Convenience method which works the same as "can?" but returns the opposite value.
64
+ #
65
+ # cannot? :destroy, @project
66
+ #
67
+ def cannot?(*args)
68
+ !can?(*args)
69
+ end
70
+
71
+ # Defines which abilities are allowed using two arguments. The first one is the action
72
+ # you're setting the permission for, the second one is the class of object you're setting it on.
73
+ #
74
+ # can :update, Article
75
+ #
76
+ # You can pass an array for either of these parameters to match any one.
77
+ # Here the user has the ability to update or destroy both articles and comments.
78
+ #
79
+ # can [:update, :destroy], [Article, Comment]
80
+ #
81
+ # You can pass :all to match any object and :manage to match any action. Here are some examples.
82
+ #
83
+ # can :manage, :all
84
+ # can :update, :all
85
+ # can :manage, Project
86
+ #
87
+ # You can pass a hash of conditions as the third argument. Here the user can only see active projects which he owns.
88
+ #
89
+ # can :read, Project, :active => true, :user_id => user.id
90
+ #
91
+ # See ActiveRecordAdditions#accessible_by for how to use this in database queries. These conditions
92
+ # are also used for initial attributes when building a record in ControllerAdditions#load_resource.
93
+ #
94
+ # If the conditions hash does not give you enough control over defining abilities, you can use a block
95
+ # along with any Ruby code you want.
96
+ #
97
+ # can :update, Project do |project|
98
+ # project.groups.include?(user.group)
99
+ # end
100
+ #
101
+ # If the block returns true then the user has that :update ability for that project, otherwise he
102
+ # will be denied access. The downside to using a block is that it cannot be used to generate
103
+ # conditions for database queries.
104
+ #
105
+ # You can pass custom objects into this "can" method, this is usually done with a symbol
106
+ # and is useful if a class isn't available to define permissions on.
107
+ #
108
+ # can :read, :stats
109
+ # can? :read, :stats # => true
110
+ #
111
+ # IMPORTANT: Neither a hash of conditions or a block will be used when checking permission on a class.
112
+ #
113
+ # can :update, Project, :priority => 3
114
+ # can? :update, Project # => true
115
+ #
116
+ # If you pass no arguments to +can+, the action, class, and object will be passed to the block and the
117
+ # block will always be executed. This allows you to override the full behavior if the permissions are
118
+ # defined in an external source such as the database.
119
+ #
120
+ # can do |action, object_class, object|
121
+ # # check the database and return true/false
122
+ # end
123
+ #
124
+ def can(action = nil, subject = nil, conditions = nil, &block)
125
+ rules << Rule.new(true, action, subject, conditions, block)
126
+ end
127
+
128
+ # Defines an ability which cannot be done. Accepts the same arguments as "can".
129
+ #
130
+ # can :read, :all
131
+ # cannot :read, Comment
132
+ #
133
+ # A block can be passed just like "can", however if the logic is complex it is recommended
134
+ # to use the "can" method.
135
+ #
136
+ # cannot :read, Product do |product|
137
+ # product.invisible?
138
+ # end
139
+ #
140
+ def cannot(action = nil, subject = nil, conditions = nil, &block)
141
+ rules << Rule.new(false, action, subject, conditions, block)
142
+ end
143
+
144
+ # Alias one or more actions into another one.
145
+ #
146
+ # alias_action :update, :destroy, :to => :modify
147
+ # can :modify, Comment
148
+ #
149
+ # Then :modify permission will apply to both :update and :destroy requests.
150
+ #
151
+ # can? :update, Comment # => true
152
+ # can? :destroy, Comment # => true
153
+ #
154
+ # This only works in one direction. Passing the aliased action into the "can?" call
155
+ # will not work because aliases are meant to generate more generic actions.
156
+ #
157
+ # alias_action :update, :destroy, :to => :modify
158
+ # can :update, Comment
159
+ # can? :modify, Comment # => false
160
+ #
161
+ # Unless that exact alias is used.
162
+ #
163
+ # can :modify, Comment
164
+ # can? :modify, Comment # => true
165
+ #
166
+ # The following aliases are added by default for conveniently mapping common controller actions.
167
+ #
168
+ # alias_action :index, :show, :to => :read
169
+ # alias_action :new, :to => :create
170
+ # alias_action :edit, :to => :update
171
+ #
172
+ # This way one can use params[:action] in the controller to determine the permission.
173
+ def alias_action(*args)
174
+ target = args.pop[:to]
175
+ validate_target(target)
176
+ aliased_actions[target] ||= []
177
+ aliased_actions[target] += args
178
+ end
179
+
180
+ # User shouldn't specify targets with names of real actions or it will cause Seg fault
181
+ def validate_target(target)
182
+ raise Error, "You can't specify target (#{target}) as alias because it is real action name" if aliased_actions.values.flatten.include? target
183
+ end
184
+
185
+ # Returns a hash of aliased actions. The key is the target and the value is an array of actions aliasing the key.
186
+ def aliased_actions
187
+ @aliased_actions ||= default_alias_actions
188
+ end
189
+
190
+ # Removes previously aliased actions including the defaults.
191
+ def clear_aliased_actions
192
+ @aliased_actions = {}
193
+ end
194
+
195
+ def model_adapter(model_class, action)
196
+ adapter_class = ModelAdapters::AbstractAdapter.adapter_class(model_class)
197
+ adapter_class.new(model_class, relevant_rules_for_query(action, model_class))
198
+ end
199
+
200
+ # See ControllerAdditions#authorize! for documentation.
201
+ def authorize!(action, subject, *args)
202
+ message = nil
203
+ if args.last.kind_of?(Hash) && args.last.has_key?(:message)
204
+ message = args.pop[:message]
205
+ end
206
+ if cannot?(action, subject, *args)
207
+ message ||= unauthorized_message(action, subject)
208
+ raise AccessDenied.new(message, action, subject)
209
+ end
210
+ subject
211
+ end
212
+
213
+ def unauthorized_message(action, subject)
214
+ keys = unauthorized_message_keys(action, subject)
215
+ variables = {:action => action.to_s}
216
+ variables[:subject] = (subject.class == Class ? subject : subject.class).to_s.underscore.humanize.downcase
217
+ message = I18n.translate(nil, variables.merge(:scope => :unauthorized, :default => keys + [""]))
218
+ message.blank? ? nil : message
219
+ end
220
+
221
+ def attributes_for(action, subject)
222
+ attributes = {}
223
+ relevant_rules(action, subject).map do |rule|
224
+ attributes.merge!(rule.attributes_from_conditions) if rule.base_behavior
225
+ end
226
+ attributes
227
+ end
228
+
229
+ def has_block?(action, subject)
230
+ relevant_rules(action, subject).any?(&:only_block?)
231
+ end
232
+
233
+ def has_raw_sql?(action, subject)
234
+ relevant_rules(action, subject).any?(&:only_raw_sql?)
235
+ end
236
+
237
+ def merge(ability)
238
+ ability.send(:rules).each do |rule|
239
+ rules << rule.dup
240
+ end
241
+ self
242
+ end
243
+
244
+ private
245
+
246
+ def unauthorized_message_keys(action, subject)
247
+ subject = (subject.class == Class ? subject : subject.class).name.underscore unless subject.kind_of? Symbol
248
+ [subject, :all].map do |try_subject|
249
+ [aliases_for_action(action), :manage].flatten.map do |try_action|
250
+ :"#{try_action}.#{try_subject}"
251
+ end
252
+ end.flatten
253
+ end
254
+
255
+ # Accepts an array of actions and returns an array of actions which match.
256
+ # This should be called before "matches?" and other checking methods since they
257
+ # rely on the actions to be expanded.
258
+ def expand_actions(actions)
259
+ expanded_actions[actions] ||= begin
260
+ expanded = []
261
+ actions.each do |action|
262
+ expanded << action
263
+ if aliases = aliased_actions[action]
264
+ expanded += expand_actions(aliases)
265
+ end
266
+ end
267
+ expanded
268
+ end
269
+ end
270
+
271
+ def expanded_actions
272
+ @expanded_actions ||= {}
273
+ end
274
+
275
+ # Given an action, it will try to find all of the actions which are aliased to it.
276
+ # This does the opposite kind of lookup as expand_actions.
277
+ def aliases_for_action(action)
278
+ results = [action]
279
+ aliased_actions.each do |aliased_action, actions|
280
+ results += aliases_for_action(aliased_action) if actions.include? action
281
+ end
282
+ results
283
+ end
284
+
285
+ def rules
286
+ @rules ||= []
287
+ end
288
+
289
+ # Returns an array of Rule instances which match the action and subject
290
+ # This does not take into consideration any hash conditions or block statements
291
+ def relevant_rules(action, subject)
292
+ relevant = rules.select do |rule|
293
+ rule.expanded_actions = expand_actions(rule.actions)
294
+ rule.relevant? action, subject
295
+ end
296
+ relevant.reverse!
297
+ relevant
298
+ end
299
+
300
+ def relevant_rules_for_match(action, subject)
301
+ relevant_rules(action, subject).each do |rule|
302
+ if rule.only_raw_sql?
303
+ raise Error, "The can? and cannot? call cannot be used with a raw sql 'can' definition. The checking code cannot be determined for #{action.inspect} #{subject.inspect}"
304
+ end
305
+ end
306
+ end
307
+
308
+ def relevant_rules_for_query(action, subject)
309
+ relevant_rules(action, subject).each do |rule|
310
+ if rule.only_block?
311
+ raise Error, "The accessible_by call cannot be used with a block 'can' definition. The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
312
+ end
313
+ end
314
+ end
315
+
316
+ def default_alias_actions
317
+ {
318
+ :read => [:index, :show],
319
+ :create => [:new],
320
+ :update => [:edit],
321
+ }
322
+ end
323
+ end
324
+ end