Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 233 lines (218 sloc) 8.344 kB
0cfb8c7 @ryanb adding basic ability module
authored
1 module CanCan
dfd84a1 @ryanb improving inline documentation
authored
2
b9227eb @ryanb adding a lot of inline documentation to code for rdocs
authored
3 # This module is designed to be included into an Ability class. This will
4 # provide the "can" methods for defining and checking abilities.
dfd84a1 @ryanb improving inline documentation
authored
5 #
b9227eb @ryanb adding a lot of inline documentation to code for rdocs
authored
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
dfd84a1 @ryanb improving inline documentation
authored
17 #
0cfb8c7 @ryanb adding basic ability module
authored
18 module Ability
dfd84a1 @ryanb improving inline documentation
authored
19 # Use to check if the user has permission to perform a given action on an object.
20 #
5bd1a85 @ryanb little fixes to inline documentation (rdocs)
authored
21 # can? :destroy, @project
dfd84a1 @ryanb improving inline documentation
authored
22 #
5bd1a85 @ryanb little fixes to inline documentation (rdocs)
authored
23 # You can also pass the class instead of an instance (if you don't have one handy).
dfd84a1 @ryanb improving inline documentation
authored
24 #
5bd1a85 @ryanb little fixes to inline documentation (rdocs)
authored
25 # can? :create, Project
dfd84a1 @ryanb improving inline documentation
authored
26 #
510cf50 @ryanb adding documentation for passing additional arguments to can?
authored
27 # Any additional arguments will be passed into the "can" block definition. This
28 # can be used to pass more information about the user's request for example.
dfd84a1 @ryanb improving inline documentation
authored
29 #
510cf50 @ryanb adding documentation for passing additional arguments to can?
authored
30 # can? :create, Project, request.remote_ip
dfd84a1 @ryanb improving inline documentation
authored
31 #
510cf50 @ryanb adding documentation for passing additional arguments to can?
authored
32 # can :create Project do |project, remote_ip|
33 # # ...
34 # end
dfd84a1 @ryanb improving inline documentation
authored
35 #
36 # Not only can you use the can? method in the controller and view (see ControllerAdditions),
b9227eb @ryanb adding a lot of inline documentation to code for rdocs
authored
37 # but you can also call it directly on an ability instance.
dfd84a1 @ryanb improving inline documentation
authored
38 #
b9227eb @ryanb adding a lot of inline documentation to code for rdocs
authored
39 # ability.can? :destroy, @project
dfd84a1 @ryanb improving inline documentation
authored
40 #
b9227eb @ryanb adding a lot of inline documentation to code for rdocs
authored
41 # This makes testing a user's abilities very easy.
dfd84a1 @ryanb improving inline documentation
authored
42 #
b9227eb @ryanb adding a lot of inline documentation to code for rdocs
authored
43 # def test "user can only destroy projects which he owns"
44 # user = User.new
45 # ability = Ability.new(user)
46 # assert ability.can?(:destroy, Project.new(:user => user))
47 # assert ability.cannot?(:destroy, Project.new)
48 # end
dfd84a1 @ryanb improving inline documentation
authored
49 #
50 # Also see the RSpec Matchers to aid in testing.
d9f3c8b @ryanb renaming noun to subject internally
authored
51 def can?(action, subject, *extra_args)
4da31c0 @ryanb can has cheezburger? (thanks Seivan)
authored
52 raise Error, "Nom nom nom. I eated it." if action == :has && subject == :cheezburger
6084814 @ryanb refactoring can definition matching behavior
authored
53 match = relevant_can_definitions(action, subject).detect do |can_definition|
54 can_definition.matches_conditions?(action, subject, extra_args)
7d7d249 @funny-falcon passing throw matching rules with not matching conditions
funny-falcon authored
55 end
6084814 @ryanb refactoring can definition matching behavior
authored
56 match ? match.base_behavior : false
0cfb8c7 @ryanb adding basic ability module
authored
57 end
dfd84a1 @ryanb improving inline documentation
authored
58
b9227eb @ryanb adding a lot of inline documentation to code for rdocs
authored
59 # Convenience method which works the same as "can?" but returns the opposite value.
dfd84a1 @ryanb improving inline documentation
authored
60 #
b9227eb @ryanb adding a lot of inline documentation to code for rdocs
authored
61 # cannot? :destroy, @project
dfd84a1 @ryanb improving inline documentation
authored
62 #
0f49b54 @ryanb adding 'cannot?' method which performs opposite check of 'can?' - clo…
authored
63 def cannot?(*args)
64 !can?(*args)
65 end
dfd84a1 @ryanb improving inline documentation
authored
66
b9227eb @ryanb adding a lot of inline documentation to code for rdocs
authored
67 # Defines which abilities are allowed using two arguments. The first one is the action
68 # you're setting the permission for, the second one is the class of object you're setting it on.
dfd84a1 @ryanb improving inline documentation
authored
69 #
b9227eb @ryanb adding a lot of inline documentation to code for rdocs
authored
70 # can :update, Article
dfd84a1 @ryanb improving inline documentation
authored
71 #
b9227eb @ryanb adding a lot of inline documentation to code for rdocs
authored
72 # You can pass an array for either of these parameters to match any one.
73 #
74 # can [:update, :destroy], [Article, Comment]
75 #
76 # In this case the user has the ability to update or destroy both articles and comments.
dfd84a1 @ryanb improving inline documentation
authored
77 #
baeef0b @ryanb adding conditions behavior to Ability#can and fetch with Ability#cond…
authored
78 # You can pass a hash of conditions as the third argument.
b9227eb @ryanb adding a lot of inline documentation to code for rdocs
authored
79 #
baeef0b @ryanb adding conditions behavior to Ability#can and fetch with Ability#cond…
authored
80 # can :read, Project, :active => true, :user_id => user.id
dfd84a1 @ryanb improving inline documentation
authored
81 #
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.
84 #
baeef0b @ryanb adding conditions behavior to Ability#can and fetch with Ability#cond…
authored
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.
b9227eb @ryanb adding a lot of inline documentation to code for rdocs
authored
87 #
baeef0b @ryanb adding conditions behavior to Ability#can and fetch with Ability#cond…
authored
88 # can :update, Project do |project|
89 # project && project.groups.include?(user.group)
b9227eb @ryanb adding a lot of inline documentation to code for rdocs
authored
90 # end
dfd84a1 @ryanb improving inline documentation
authored
91 #
baeef0b @ryanb adding conditions behavior to Ability#can and fetch with Ability#cond…
authored
92 # If the block returns true then the user has that :update ability for that project, otherwise he
b9227eb @ryanb adding a lot of inline documentation to code for rdocs
authored
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.
dfd84a1 @ryanb improving inline documentation
authored
95 #
baeef0b @ryanb adding conditions behavior to Ability#can and fetch with Ability#cond…
authored
96 # The downside to using a block is that it cannot be used to generate conditions for database queries.
dfd84a1 @ryanb improving inline documentation
authored
97 #
b9227eb @ryanb adding a lot of inline documentation to code for rdocs
authored
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).
dfd84a1 @ryanb improving inline documentation
authored
100 #
b9227eb @ryanb adding a lot of inline documentation to code for rdocs
authored
101 # can :read, :all do |object_class, object|
102 # object_class != Order
103 # end
dfd84a1 @ryanb improving inline documentation
authored
104 #
b9227eb @ryanb adding a lot of inline documentation to code for rdocs
authored
105 # Here the user has permission to read all objects except orders.
dfd84a1 @ryanb improving inline documentation
authored
106 #
107 # You can also pass :manage as the action which will match any action. In this case the action is
b9227eb @ryanb adding a lot of inline documentation to code for rdocs
authored
108 # passed to the block.
dfd84a1 @ryanb improving inline documentation
authored
109 #
b9227eb @ryanb adding a lot of inline documentation to code for rdocs
authored
110 # can :manage, Comment do |action, comment|
111 # action != :destroy
112 # end
dfd84a1 @ryanb improving inline documentation
authored
113 #
e603655 @ryanb support custom objects (usually symbols) in can definition - closes #8
authored
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.
dfd84a1 @ryanb improving inline documentation
authored
116 #
e603655 @ryanb support custom objects (usually symbols) in can definition - closes #8
authored
117 # can :read, :stats
118 # can? :read, :stats # => true
dfd84a1 @ryanb improving inline documentation
authored
119 #
d9f3c8b @ryanb renaming noun to subject internally
authored
120 def can(action, subject, conditions = nil, &block)
bbbc8a6 @ryanb refactoring much of Ability class into separate CanDefinition class
authored
121 can_definitions << CanDefinition.new(true, action, subject, conditions, block)
d4405e6 @ryanb adding cannot method to define which abilities cannot be done - close…
authored
122 end
dfd84a1 @ryanb improving inline documentation
authored
123
124 # Defines an ability which cannot be done. Accepts the same arguments as "can".
125 #
d4405e6 @ryanb adding cannot method to define which abilities cannot be done - close…
authored
126 # can :read, :all
127 # cannot :read, Comment
dfd84a1 @ryanb improving inline documentation
authored
128 #
d4405e6 @ryanb adding cannot method to define which abilities cannot be done - close…
authored
129 # A block can be passed just like "can", however if the logic is complex it is recommended
130 # to use the "can" method.
dfd84a1 @ryanb improving inline documentation
authored
131 #
d4405e6 @ryanb adding cannot method to define which abilities cannot be done - close…
authored
132 # cannot :read, Product do |product|
133 # product.invisible?
134 # end
dfd84a1 @ryanb improving inline documentation
authored
135 #
d9f3c8b @ryanb renaming noun to subject internally
authored
136 def cannot(action, subject, conditions = nil, &block)
bbbc8a6 @ryanb refactoring much of Ability class into separate CanDefinition class
authored
137 can_definitions << CanDefinition.new(false, action, subject, conditions, block)
4b6f538 @ryanb moving can definition into ability instance instead of class, this re…
authored
138 end
dfd84a1 @ryanb improving inline documentation
authored
139
5bd1a85 @ryanb little fixes to inline documentation (rdocs)
authored
140 # Alias one or more actions into another one.
dfd84a1 @ryanb improving inline documentation
authored
141 #
b9227eb @ryanb adding a lot of inline documentation to code for rdocs
authored
142 # alias_action :update, :destroy, :to => :modify
143 # can :modify, Comment
dfd84a1 @ryanb improving inline documentation
authored
144 #
5bd1a85 @ryanb little fixes to inline documentation (rdocs)
authored
145 # Then :modify permission will apply to both :update and :destroy requests.
dfd84a1 @ryanb improving inline documentation
authored
146 #
5bd1a85 @ryanb little fixes to inline documentation (rdocs)
authored
147 # can? :update, Comment # => true
148 # can? :destroy, Comment # => true
dfd84a1 @ryanb improving inline documentation
authored
149 #
5bd1a85 @ryanb little fixes to inline documentation (rdocs)
authored
150 # This only works in one direction. Passing the aliased action into the "can?" call
151 # will not work because aliases are meant to generate more generic actions.
dfd84a1 @ryanb improving inline documentation
authored
152 #
5bd1a85 @ryanb little fixes to inline documentation (rdocs)
authored
153 # alias_action :update, :destroy, :to => :modify
154 # can :update, Comment
155 # can? :modify, Comment # => false
dfd84a1 @ryanb improving inline documentation
authored
156 #
5bd1a85 @ryanb little fixes to inline documentation (rdocs)
authored
157 # Unless that exact alias is used.
dfd84a1 @ryanb improving inline documentation
authored
158 #
5bd1a85 @ryanb little fixes to inline documentation (rdocs)
authored
159 # can :modify, Comment
160 # can? :modify, Comment # => true
dfd84a1 @ryanb improving inline documentation
authored
161 #
b9227eb @ryanb adding a lot of inline documentation to code for rdocs
authored
162 # The following aliases are added by default for conveniently mapping common controller actions.
dfd84a1 @ryanb improving inline documentation
authored
163 #
b9227eb @ryanb adding a lot of inline documentation to code for rdocs
authored
164 # alias_action :index, :show, :to => :read
165 # alias_action :new, :to => :create
166 # alias_action :edit, :to => :update
dfd84a1 @ryanb improving inline documentation
authored
167 #
5bd1a85 @ryanb little fixes to inline documentation (rdocs)
authored
168 # This way one can use params[:action] in the controller to determine the permission.
4b6f538 @ryanb moving can definition into ability instance instead of class, this re…
authored
169 def alias_action(*args)
170 target = args.pop[:to]
f99d506 @ryanb Append aliased actions (don't overwrite them) - closes #20
authored
171 aliased_actions[target] ||= []
172 aliased_actions[target] += args
4b6f538 @ryanb moving can definition into ability instance instead of class, this re…
authored
173 end
dfd84a1 @ryanb improving inline documentation
authored
174
7d3b4cd @ryanb Adding clear_aliased_actions to Ability which removes previously defi…
authored
175 # Returns a hash of aliased actions. The key is the target and the value is an array of actions aliasing the key.
c40490d @ryanb refactoring ability can? method - closes #12
authored
176 def aliased_actions
177 @aliased_actions ||= default_alias_actions
178 end
dfd84a1 @ryanb improving inline documentation
authored
179
7d3b4cd @ryanb Adding clear_aliased_actions to Ability which removes previously defi…
authored
180 # Removes previously aliased actions including the defaults.
181 def clear_aliased_actions
182 @aliased_actions = {}
183 end
dfd84a1 @ryanb improving inline documentation
authored
184
a42e067 @ryanb extracting out Query class for generating sql conditions and associat…
authored
185 # Returns a CanCan::Query instance to help generate database queries based on the ability.
e098dda @ryanb refactoring query.conditions
authored
186 # If any relevant can definitions use a block then an exception will be raised because an
187 # SQL query cannot be generated from blocks of code.
964a476 @ryanb removing need to pass tableize option around for query conditions
authored
188 def query(action, subject)
e098dda @ryanb refactoring query.conditions
authored
189 Query.new(subject, relevant_can_definitions_for_query(action, subject))
e200814 @ryanb adding joins clause to accessible_by when conditions are across assoc…
authored
190 end
25637bb @ryanb removing extra white space at end of lines
authored
191
7d3b4cd @ryanb Adding clear_aliased_actions to Ability which removes previously defi…
authored
192 private
25637bb @ryanb removing extra white space at end of lines
authored
193
cad4259 @ryanb supporting deeply nested aliases - closes #98
authored
194 # Accepts a hash of aliased actions and returns an array of actions which match.
195 # This should be called before "matches?" and other checking methods since they
196 # rely on the actions to be expanded.
197 def expand_actions(actions)
198 actions.map do |action|
199 aliased_actions[action] ? [action, *expand_actions(aliased_actions[action])] : action
200 end.flatten
201 end
dfd84a1 @ryanb improving inline documentation
authored
202
bbbc8a6 @ryanb refactoring much of Ability class into separate CanDefinition class
authored
203 def can_definitions
204 @can_definitions ||= []
205 end
dfd84a1 @ryanb improving inline documentation
authored
206
6084814 @ryanb refactoring can definition matching behavior
authored
207 # Returns an array of CanDefinition instances which match the action and subject
208 # This does not take into consideration any hash conditions or block statements
209 def relevant_can_definitions(action, subject)
210 can_definitions.reverse.select do |can_definition|
211 can_definition.expanded_actions = expand_actions(can_definition.actions)
212 can_definition.relevant? action, subject
baeef0b @ryanb adding conditions behavior to Ability#can and fetch with Ability#cond…
authored
213 end
214 end
dfd84a1 @ryanb improving inline documentation
authored
215
e098dda @ryanb refactoring query.conditions
authored
216 def relevant_can_definitions_for_query(action, subject)
a42e067 @ryanb extracting out Query class for generating sql conditions and associat…
authored
217 relevant_can_definitions(action, subject).each do |can_definition|
218 if can_definition.only_block?
4fe44af @ryanb be more clear about blocks not working with accessible_by - closes #130
authored
219 raise Error, "The accessible_by call cannot be used with a block 'can' definition. The SQL cannot be determined for #{action.inspect} #{subject.inspect}"
a42e067 @ryanb extracting out Query class for generating sql conditions and associat…
authored
220 end
221 end
222 end
223
4b6f538 @ryanb moving can definition into ability instance instead of class, this re…
authored
224 def default_alias_actions
225 {
226 :read => [:index, :show],
227 :create => [:new],
228 :update => [:edit],
229 }
230 end
0cfb8c7 @ryanb adding basic ability module
authored
231 end
232 end
Something went wrong with that request. Please try again.