cancan 1.3.4 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG.rdoc CHANGED
@@ -1,3 +1,55 @@
1
+ 1.4.1 (November 12, 2010)
2
+
3
+ * Renaming skip_authorization to skip_authorization_check - see issue #169
4
+
5
+ * Adding :through_association option to load_resource (thanks hunterae) - see issue #171
6
+
7
+ * The :shallow option now works with the :singleton option (thanks nandalopes) - see issue #187
8
+
9
+ * Play nicely with quick_scopes gem (thanks ramontayag) - see issue #183
10
+
11
+ * Fix odd behavior when "cache_classes = false" (thanks mphalliday) - see issue #174
12
+
13
+
14
+ 1.4.0 (October 5, 2010)
15
+
16
+ * Adding Gemfile; to get specs running just +bundle+ and +rake+ - see issue #163
17
+
18
+ * Stop at 'cannot' definition when there are no conditions - see issue #161
19
+
20
+ * The :through option will now call a method with that name if instance variable doesn't exist - see issue #146
21
+
22
+ * Adding :shallow option to load_resource to bring back old behavior of fetching a child without a parent
23
+
24
+ * Raise AccessDenied error when loading a child and parent resource isn't found
25
+
26
+ * Abilities defined on a module will apply to anything that includes that module - see issue #150 and #152
27
+
28
+ * Abilities can be defined with a string of SQL in addition to a block so accessible_by works with a block - see issue #150
29
+
30
+ * Adding better support for InheritedResource - see issue #23
31
+
32
+ * Loading the collection instance variable (for index action) using accessible_by - see issue #137
33
+
34
+ * Adding action and subject variables to I18n unauthorized message - closes #142
35
+
36
+ * Adding check_authorization and skip_authorization controller class methods to ensure authorization is performed (thanks justinko) - see issue #135
37
+
38
+ * Setting initial attributes based on ability conditions in new/create actions - see issue #114
39
+
40
+ * Check parent attributes for nested association in index action - see issue #121
41
+
42
+ * Supporting nesting in can? method using hash - see issue #121
43
+
44
+ * Adding I18n support for Access Denied messages (thanks EppO) - see issue #103
45
+
46
+ * Passing no arguments to +can+ definition will pass action, class, and object to block - see issue #129
47
+
48
+ * Don't pass action to block in +can+ definition when using :+manage+ option - see issue #129
49
+
50
+ * No longer calling block in +can+ definition when checking on class - see issue #116
51
+
52
+
1
53
  1.3.4 (August 31, 2010)
2
54
 
3
55
  * Don't stop at +cannot+ with hash conditions when checking class (thanks tamoya) - see issue #131
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "http://rubygems.org"
2
+ gemspec
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 Ryan Bates
1
+ Copyright (c) 2010 Ryan Bates
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.rdoc CHANGED
@@ -2,29 +2,29 @@
2
2
 
3
3
  Wiki[http://wiki.github.com/ryanb/cancan] | RDocs[http://rdoc.info/projects/ryanb/cancan] | Screencast[http://railscasts.com/episodes/192-authorization-with-cancan]
4
4
 
5
- CanCan is an authorization solution for Ruby on Rails for restricting what a given user is allowed to access throughout the application. It does not care how your user roles are defined, it simply focusses on keeping permission logic in a single location (the +Ability+ class) so it is not duplicated across controllers, views, and database queries.
6
-
7
- By default, the +current_user+ method is required, so if you have not already, set up some authentication (such as Authlogic[http://github.com/binarylogic/authlogic] or Devise[http://github.com/plataformatec/devise]). See {Changing Defaults}[http://wiki.github.com/ryanb/cancan/changing-defaults] if you need different behavior.
5
+ 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.
8
6
 
9
7
 
10
8
  == Installation
11
9
 
12
- To install CanCan, include the gem in the environment.rb in Rails 2.3.
10
+ In <b>Rails 3</b>, add this to your Gemfile.
13
11
 
14
- config.gem "cancan"
12
+ gem "cancan"
15
13
 
16
- Or the Gemfile in Rails 3.
14
+ In <b>Rails 2</b>, add this to your environment.rb file.
17
15
 
18
- gem "cancan"
16
+ config.gem "cancan"
19
17
 
20
- Alternatively it can be installed as a plugin.
18
+ Alternatively, you can install it as a plugin.
21
19
 
22
- script/plugin install git://github.com/ryanb/cancan.git
20
+ rails plugin install git://github.com/ryanb/cancan.git
23
21
 
24
22
 
25
23
  == Getting Started
26
24
 
27
- First, define a class called +Ability+ in "models/ability.rb" or anywhere else in the load path. It should look something like this.
25
+ CanCan expects a +current_user+ method to exist. If you have not already, set up some authentication (such as Authlogic[http://github.com/binarylogic/authlogic] or Devise[http://github.com/plataformatec/devise]). See {Changing Defaults}[http://wiki.github.com/ryanb/cancan/changing-defaults] if you need different behavior.
26
+
27
+ Next create a class called +Ability+ in "models/ability.rb" or anywhere else in the load path. It should look similar to this.
28
28
 
29
29
  class Ability
30
30
  include CanCan::Ability
@@ -38,7 +38,7 @@ First, define a class called +Ability+ in "models/ability.rb" or anywhere else i
38
38
  end
39
39
  end
40
40
 
41
- This is where all permissions will go. See the "Defining Abilities" section below for more information.
41
+ The +current_user+ is passed in to this method which is where the abilities are defined. See the "Defining Abilities" section below for more information.
42
42
 
43
43
  The current user's permissions can be accessed using the "can?" and "cannot?" methods in the view and controller.
44
44
 
@@ -67,11 +67,11 @@ Setting this for every action can be tedious, therefore the +load_and_authorize_
67
67
 
68
68
  See {Authorizing Controller Actions}[http://wiki.github.com/ryanb/cancan/authorizing-controller-actions] for more information
69
69
 
70
- If the user authorization fails, a CanCan::AccessDenied exception will be raised. You can catch this and modify its behavior in the +ApplicationController+.
70
+ 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+.
71
71
 
72
72
  class ApplicationController < ActionController::Base
73
73
  rescue_from CanCan::AccessDenied do |exception|
74
- flash[:error] = exception.message
74
+ flash[:alert] = exception.message
75
75
  redirect_to root_url
76
76
  end
77
77
  end
@@ -81,7 +81,7 @@ See {Exception Handling}[http://wiki.github.com/ryanb/cancan/exception-handling]
81
81
 
82
82
  == Defining Abilities
83
83
 
84
- As shown above, the +Ability+ class is where all user permissions are defined. The user model is passed into the initialize method so the permissions can be modified based on any user attributes. CanCan makes no assumptions about how roles are handled in your application. See {Role Based Authorization}[http://wiki.github.com/ryanb/cancan/role-based-authorization] for an example.
84
+ As shown above, the +Ability+ class is where all user permissions are defined. The current user model is passed into the initialize method so the permissions can be modified based on any user attributes. CanCan makes no assumption about how roles are handled in your application. See {Role Based Authorization}[http://wiki.github.com/ryanb/cancan/role-based-authorization] for an example.
85
85
 
86
86
  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.
87
87
 
@@ -97,7 +97,7 @@ Use :+manage+ to represent any action and :+all+ to represent any class. Here ar
97
97
  can :read, :all # has permission to read any model
98
98
  can :manage, :all # has permission to do anything to any model
99
99
 
100
- You can pass a hash of conditions as the third argument to further restrict what the user is able to access. Here the user will only have permission to read active projects which he owns.
100
+ You can pass a hash of conditions as the third argument to further define what the user is able to access. Here the user will only have permission to read active projects which he owns.
101
101
 
102
102
  can :read, Project, :active => true, :user_id => user.id
103
103
 
@@ -106,10 +106,10 @@ See {Defining Abilities with Hashes}[http://wiki.github.com/ryanb/cancan/definin
106
106
  Blocks can also be used if you need more control.
107
107
 
108
108
  can :update, Project do |project|
109
- project && project.groups.include?(user.group)
109
+ project.groups.include?(user.group)
110
110
  end
111
111
 
112
- If the block returns true then the user has that :+update+ ability for that project, otherwise he will be denied access. See {Defining Abilities with Blocks}[http://wiki.github.com/ryanb/cancan/defining-abilities-with-blocks] for more information.
112
+ If the block returns true then the user has that ability for that project, otherwise he will be denied access. See {Defining Abilities with Blocks}[http://wiki.github.com/ryanb/cancan/defining-abilities-with-blocks] for more information.
113
113
 
114
114
 
115
115
  == Aliasing Actions
@@ -120,33 +120,43 @@ You will usually be working with four actions when defining and checking permiss
120
120
  alias_action :new, :to => :create
121
121
  alias_action :edit, :to => :update
122
122
 
123
- Notice the +edit+ action is aliased to +update+. 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
123
+ 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.
124
124
 
125
125
  alias_action :update, :destroy, :to => :modify
126
126
  can :modify, Comment
127
127
  can? :update, Comment # => true
128
128
 
129
- See {Custom Actions}[http://wiki.github.com/ryanb/cancan/custom-actions] for information on adding other actions.
129
+ The +alias_action+ method is an instance method and usually called in +initialize+. See {Custom Actions}[http://wiki.github.com/ryanb/cancan/custom-actions] for information on adding other actions.
130
130
 
131
131
 
132
132
  == Fetching Records
133
133
 
134
- In the controller +index+ action you may want to fetch only the records which the user has permission to read. You can do this with the +accessible_by+ scope.
134
+ It is possible to fetch records which the user has permission to read using the +accessible_by+ scope in Active Record.
135
135
 
136
136
  @articles = Article.accessible_by(current_ability)
137
137
 
138
+ Since version 1.4 this is done automatically when loading resources in the index action, so one rarely needs to do it manually.
139
+
138
140
  This will only work when abilities are defined using hash conditions, not blocks. See {Fetching Records}[http://wiki.github.com/ryanb/cancan/fetching-records] for more information.
139
141
 
140
142
 
141
143
  == Additional Docs
142
144
 
143
- * {Upgrading to 1.3}[http://wiki.github.com/ryanb/cancan/upgrading-to-13]
145
+ * {Upgrading to 1.4}[http://github.com/ryanb/cancan/wiki/Upgrading-to-1.4]
144
146
  * {Nested Resources}[http://wiki.github.com/ryanb/cancan/nested-resources]
145
147
  * {Testing Abilities}[http://wiki.github.com/ryanb/cancan/testing-abilities]
146
148
  * {Accessing Request Data}[http://wiki.github.com/ryanb/cancan/accessing-request-data]
147
149
  * {Admin Namespace}[http://wiki.github.com/ryanb/cancan/admin-namespace]
148
150
  * {See more}[http://wiki.github.com/ryanb/cancan/]
149
151
 
152
+
153
+ == Questions or Problems?
154
+
155
+ If you have any issues with CanCan which you cannot find the solution to in the documentation, please add an {issue on GitHub}[http://github.com/ryanb/cancan/issues] or fork the project and send a pull request.
156
+
157
+ To get the specs running you should call +bundle+ and then +rake+. Specs currently do not work in Ruby 1.9 due to the RR mocking framework.
158
+
159
+
150
160
  == Special Thanks
151
161
 
152
162
  CanCan was inspired by declarative_authorization[http://github.com/stffn/declarative_authorization/] and aegis[http://github.com/makandra/aegis]. Also many thanks to the CanCan contributors[http://github.com/ryanb/cancan/contributors]. See the CHANGELOG[http://github.com/ryanb/cancan/blob/master/CHANGELOG.rdoc] for the full list.
data/Rakefile CHANGED
@@ -1,13 +1,10 @@
1
1
  require 'rubygems'
2
2
  require 'rake'
3
- require 'spec/rake/spectask'
3
+ require 'rspec/core/rake_task'
4
4
 
5
- spec_files = Rake::FileList["spec/**/*_spec.rb"]
6
-
7
- desc "Run specs"
8
- Spec::Rake::SpecTask.new do |t|
9
- t.spec_files = spec_files
10
- t.spec_opts = ["-c"]
5
+ desc "Run RSpec"
6
+ RSpec::Core::RakeTask.new do |t|
7
+ t.verbose = false
11
8
  end
12
9
 
13
- task :default => :spec
10
+ task :default => :spec
@@ -16,7 +16,7 @@ module CanCan
16
16
  # end
17
17
  #
18
18
  module Ability
19
- # Use to check if the user has permission to perform a given action on an object.
19
+ # Check if the user has permission to perform a given action on an object.
20
20
  #
21
21
  # can? :destroy, @project
22
22
  #
@@ -24,6 +24,11 @@ module CanCan
24
24
  #
25
25
  # can? :create, Project
26
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
+ #
27
32
  # Any additional arguments will be passed into the "can" block definition. This
28
33
  # can be used to pass more information about the user's request for example.
29
34
  #
@@ -49,8 +54,7 @@ module CanCan
49
54
  #
50
55
  # Also see the RSpec Matchers to aid in testing.
51
56
  def can?(action, subject, *extra_args)
52
- raise Error, "Nom nom nom. I eated it." if action == :has && subject == :cheezburger
53
- match = relevant_can_definitions(action, subject).detect do |can_definition|
57
+ match = relevant_can_definitions_for_match(action, subject).detect do |can_definition|
54
58
  can_definition.matches_conditions?(action, subject, extra_args)
55
59
  end
56
60
  match ? match.base_behavior : false
@@ -70,54 +74,54 @@ module CanCan
70
74
  # can :update, Article
71
75
  #
72
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.
73
78
  #
74
79
  # can [:update, :destroy], [Article, Comment]
75
80
  #
76
- # In this case the user has the ability to update or destroy both articles and comments.
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
77
86
  #
78
- # You can pass a hash of conditions as the third argument.
87
+ # You can pass a hash of conditions as the third argument. Here the user can only see active projects which he owns.
79
88
  #
80
89
  # can :read, Project, :active => true, :user_id => user.id
81
90
  #
82
- # Here the user can only see active projects which he owns. See ActiveRecordAdditions#accessible_by
83
- # for how to use this in database queries.
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.
84
93
  #
85
- # If the conditions hash does not give you enough control over defining abilities, you can use a block to
86
- # write any Ruby code you want.
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.
87
96
  #
88
97
  # can :update, Project do |project|
89
- # project && project.groups.include?(user.group)
98
+ # project.groups.include?(user.group)
90
99
  # end
91
100
  #
92
101
  # If the block returns true then the user has that :update ability for that project, otherwise he
93
- # will be denied access. It's possible for the passed in model to be nil if one isn't specified,
94
- # so be sure to take that into consideration.
102
+ # will be denied access. The downside to using a block is that it cannot be used to generate
103
+ # conditions for database queries.
95
104
  #
96
- # The downside to using a block is that it cannot be used to generate conditions for database queries.
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.
97
107
  #
98
- # You can pass :all to reference every type of object. In this case the object type will be passed
99
- # into the block as well (just in case object is nil).
108
+ # can :read, :stats
109
+ # can? :read, :stats # => true
100
110
  #
101
- # can :read, :all do |object_class, object|
102
- # object_class != Order
103
- # end
111
+ # IMPORTANT: Neither a hash of conditions or a block will be used when checking permission on a class.
104
112
  #
105
- # Here the user has permission to read all objects except orders.
113
+ # can :update, Project, :priority => 3
114
+ # can? :update, Project # => true
106
115
  #
107
- # You can also pass :manage as the action which will match any action. In this case the action is
108
- # passed to the block.
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.
109
119
  #
110
- # can :manage, Comment do |action, comment|
111
- # action != :destroy
120
+ # can do |action, object_class, object|
121
+ # # check the database and return true/false
112
122
  # end
113
123
  #
114
- # You can pass custom objects into this "can" method, this is usually done through a symbol
115
- # and is useful if a class isn't available to define permissions on.
116
- #
117
- # can :read, :stats
118
- # can? :read, :stats # => true
119
- #
120
- def can(action, subject, conditions = nil, &block)
124
+ def can(action = nil, subject = nil, conditions = nil, &block)
121
125
  can_definitions << CanDefinition.new(true, action, subject, conditions, block)
122
126
  end
123
127
 
@@ -133,7 +137,7 @@ module CanCan
133
137
  # product.invisible?
134
138
  # end
135
139
  #
136
- def cannot(action, subject, conditions = nil, &block)
140
+ def cannot(action = nil, subject = nil, conditions = nil, &block)
137
141
  can_definitions << CanDefinition.new(false, action, subject, conditions, block)
138
142
  end
139
143
 
@@ -189,9 +193,54 @@ module CanCan
189
193
  Query.new(subject, relevant_can_definitions_for_query(action, subject))
190
194
  end
191
195
 
196
+ # See ControllerAdditions#authorize! for documentation.
197
+ def authorize!(action, subject, *args)
198
+ message = nil
199
+ if args.last.kind_of?(Hash) && args.last.has_key?(:message)
200
+ message = args.pop[:message]
201
+ end
202
+ if cannot?(action, subject, *args)
203
+ message ||= unauthorized_message(action, subject)
204
+ raise AccessDenied.new(message, action, subject)
205
+ end
206
+ end
207
+
208
+ def unauthorized_message(action, subject)
209
+ keys = unauthorized_message_keys(action, subject)
210
+ variables = {:action => action.to_s}
211
+ variables[:subject] = (subject.class == Class ? subject : subject.class).to_s.downcase
212
+ message = I18n.translate(nil, variables.merge(:scope => :unauthorized, :default => keys + [""]))
213
+ message.blank? ? nil : message
214
+ end
215
+
216
+ def attributes_for(action, subject)
217
+ attributes = {}
218
+ relevant_can_definitions(action, subject).map do |can_definition|
219
+ attributes.merge!(can_definition.attributes_from_conditions) if can_definition.base_behavior
220
+ end
221
+ attributes
222
+ end
223
+
224
+ def has_block?(action, subject)
225
+ relevant_can_definitions(action, subject).any?(&:only_block?)
226
+ end
227
+
228
+ def has_raw_sql?(action, subject)
229
+ relevant_can_definitions(action, subject).any?(&:only_raw_sql?)
230
+ end
231
+
192
232
  private
193
233
 
194
- # Accepts a hash of aliased actions and returns an array of actions which match.
234
+ def unauthorized_message_keys(action, subject)
235
+ subject = (subject.class == Class ? subject : subject.class).name.underscore unless subject.kind_of? Symbol
236
+ [subject, :all].map do |try_subject|
237
+ [aliases_for_action(action), :manage].flatten.map do |try_action|
238
+ :"#{try_action}.#{try_subject}"
239
+ end
240
+ end.flatten
241
+ end
242
+
243
+ # Accepts an array of actions and returns an array of actions which match.
195
244
  # This should be called before "matches?" and other checking methods since they
196
245
  # rely on the actions to be expanded.
197
246
  def expand_actions(actions)
@@ -200,6 +249,16 @@ module CanCan
200
249
  end.flatten
201
250
  end
202
251
 
252
+ # Given an action, it will try to find all of the actions which are aliased to it.
253
+ # This does the opposite kind of lookup as expand_actions.
254
+ def aliases_for_action(action)
255
+ results = [action]
256
+ aliased_actions.each do |aliased_action, actions|
257
+ results += aliases_for_action(aliased_action) if actions.include? action
258
+ end
259
+ results
260
+ end
261
+
203
262
  def can_definitions
204
263
  @can_definitions ||= []
205
264
  end
@@ -213,6 +272,14 @@ module CanCan
213
272
  end
214
273
  end
215
274
 
275
+ def relevant_can_definitions_for_match(action, subject)
276
+ relevant_can_definitions(action, subject).each do |can_definition|
277
+ if can_definition.only_raw_sql?
278
+ 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}"
279
+ end
280
+ end
281
+ end
282
+
216
283
  def relevant_can_definitions_for_query(action, subject)
217
284
  relevant_can_definitions(action, subject).each do |can_definition|
218
285
  if can_definition.only_block?
@@ -21,7 +21,7 @@ module CanCan
21
21
  # internally uses Ability#conditions method, see that for more information.
22
22
  def accessible_by(ability, action = :read)
23
23
  query = ability.query(action, self)
24
- if respond_to? :where
24
+ if respond_to?(:where) && respond_to?(:joins)
25
25
  where(query.conditions).joins(query.joins)
26
26
  else
27
27
  scoped(:conditions => query.conditions, :joins => query.joins)
@@ -11,6 +11,7 @@ module CanCan
11
11
  # and subject respectively (such as :read, @project). The third argument is a hash
12
12
  # of conditions and the last one is the block passed to the "can" call.
13
13
  def initialize(base_behavior, action, subject, conditions, block)
14
+ @match_all = action.nil? && subject.nil?
14
15
  @base_behavior = base_behavior
15
16
  @actions = [action].flatten
16
17
  @subjects = [subject].flatten
@@ -20,21 +21,28 @@ module CanCan
20
21
 
21
22
  # Matches both the subject and action, not necessarily the conditions
22
23
  def relevant?(action, subject)
23
- matches_action?(action) && matches_subject?(subject)
24
+ subject = subject.values.first if subject.kind_of? Hash
25
+ @match_all || (matches_action?(action) && matches_subject?(subject))
24
26
  end
25
27
 
26
28
  # Matches the block or conditions hash
27
29
  def matches_conditions?(action, subject, extra_args)
28
- if @block
29
- call_block(action, subject, extra_args)
30
- elsif @conditions.kind_of?(Hash) && subject.class != Class
30
+ if @match_all
31
+ call_block_with_all(action, subject, extra_args)
32
+ elsif @block && !subject_class?(subject)
33
+ @block.call(subject, *extra_args)
34
+ elsif @conditions.kind_of?(Hash) && subject.kind_of?(Hash)
35
+ nested_subject_matches_conditions?(subject)
36
+ elsif @conditions.kind_of?(Hash) && !subject_class?(subject)
31
37
  matches_conditions_hash?(subject)
32
38
  else
33
- @base_behavior
39
+ # Don't stop at "cannot" definitions when there are conditions.
40
+ @conditions.empty? ? true : @base_behavior
34
41
  end
35
42
  end
36
43
 
37
44
  def tableized_conditions(conditions = @conditions)
45
+ return conditions unless conditions.kind_of? Hash
38
46
  conditions.inject({}) do |result_hash, (name, value)|
39
47
  if value.kind_of? Hash
40
48
  name = name.to_s.tableize.to_sym
@@ -49,6 +57,10 @@ module CanCan
49
57
  conditions_empty? && !@block.nil?
50
58
  end
51
59
 
60
+ def only_raw_sql?
61
+ @block.nil? && !conditions_empty? && !@conditions.kind_of?(Hash)
62
+ end
63
+
52
64
  def conditions_empty?
53
65
  @conditions == {} || @conditions.nil?
54
66
  end
@@ -57,12 +69,25 @@ module CanCan
57
69
  hash = {}
58
70
  conditions.map do |name, value|
59
71
  hash[name] = associations_hash(value) if value.kind_of? Hash
60
- end
72
+ end if conditions.kind_of? Hash
61
73
  hash
62
74
  end
63
75
 
76
+ def attributes_from_conditions
77
+ attributes = {}
78
+ @conditions.each do |key, value|
79
+ attributes[key] = value unless [Array, Range, Hash].include? value.class
80
+ end if @conditions.kind_of? Hash
81
+ attributes
82
+ end
83
+
64
84
  private
65
85
 
86
+ def subject_class?(subject)
87
+ klass = (subject.kind_of?(Hash) ? subject.values.first : subject).class
88
+ klass == Class || klass == Module
89
+ end
90
+
66
91
  def matches_action?(action)
67
92
  @expanded_actions.include?(:manage) || @expanded_actions.include?(action)
68
93
  end
@@ -72,7 +97,7 @@ module CanCan
72
97
  end
73
98
 
74
99
  def matches_subject_class?(subject)
75
- @subjects.any? { |sub| sub.kind_of?(Class) && (subject.kind_of?(sub) || subject.kind_of?(Class) && subject.ancestors.include?(sub)) }
100
+ @subjects.any? { |sub| sub.kind_of?(Module) && (subject.kind_of?(sub) || subject.class.to_s == sub.to_s || subject.kind_of?(Module) && subject.ancestors.include?(sub)) }
76
101
  end
77
102
 
78
103
  def matches_conditions_hash?(subject, conditions = @conditions)
@@ -92,13 +117,17 @@ module CanCan
92
117
  end
93
118
  end
94
119
 
95
- def call_block(action, subject, extra_args)
96
- block_args = []
97
- block_args << action if @expanded_actions.include?(:manage)
98
- block_args << (subject.class == Class ? subject : subject.class) if @subjects.include?(:all)
99
- block_args << (subject.class == Class ? nil : subject)
100
- block_args += extra_args
101
- @block.call(*block_args)
120
+ def nested_subject_matches_conditions?(subject_hash)
121
+ parent, child = subject_hash.shift
122
+ matches_conditions_hash?(parent, @conditions[parent.class.name.downcase.to_sym] || {})
123
+ end
124
+
125
+ def call_block_with_all(action, subject, extra_args)
126
+ if subject.class == Class
127
+ @block.call(action, subject, nil, *extra_args)
128
+ else
129
+ @block.call(action, subject.class, subject, *extra_args)
130
+ end
102
131
  end
103
132
  end
104
133
  end