cancan 1.4.0.beta1 → 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,4 +1,37 @@
1
- 1.4.0 (not yet released)
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
2
35
 
3
36
  * Adding check_authorization and skip_authorization controller class methods to ensure authorization is performed (thanks justinko) - see issue #135
4
37
 
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,7 +120,7 @@ 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
@@ -131,22 +131,32 @@ The +alias_action+ method is an instance method and usually called in +initializ
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
10
  task :default => :spec
@@ -54,7 +54,7 @@ module CanCan
54
54
  #
55
55
  # Also see the RSpec Matchers to aid in testing.
56
56
  def can?(action, subject, *extra_args)
57
- match = relevant_can_definitions(action, subject).detect do |can_definition|
57
+ match = relevant_can_definitions_for_match(action, subject).detect do |can_definition|
58
58
  can_definition.matches_conditions?(action, subject, extra_args)
59
59
  end
60
60
  match ? match.base_behavior : false
@@ -207,7 +207,9 @@ module CanCan
207
207
 
208
208
  def unauthorized_message(action, subject)
209
209
  keys = unauthorized_message_keys(action, subject)
210
- message = I18n.translate(nil, :scope => :unauthorized, :default => keys + [""])
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 + [""]))
211
213
  message.blank? ? nil : message
212
214
  end
213
215
 
@@ -219,6 +221,14 @@ module CanCan
219
221
  attributes
220
222
  end
221
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
+
222
232
  private
223
233
 
224
234
  def unauthorized_message_keys(action, subject)
@@ -262,6 +272,14 @@ module CanCan
262
272
  end
263
273
  end
264
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
+
265
283
  def relevant_can_definitions_for_query(action, subject)
266
284
  relevant_can_definitions(action, subject).each do |can_definition|
267
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)
@@ -36,11 +36,13 @@ module CanCan
36
36
  elsif @conditions.kind_of?(Hash) && !subject_class?(subject)
37
37
  matches_conditions_hash?(subject)
38
38
  else
39
- @base_behavior
39
+ # Don't stop at "cannot" definitions when there are conditions.
40
+ @conditions.empty? ? true : @base_behavior
40
41
  end
41
42
  end
42
43
 
43
44
  def tableized_conditions(conditions = @conditions)
45
+ return conditions unless conditions.kind_of? Hash
44
46
  conditions.inject({}) do |result_hash, (name, value)|
45
47
  if value.kind_of? Hash
46
48
  name = name.to_s.tableize.to_sym
@@ -55,6 +57,10 @@ module CanCan
55
57
  conditions_empty? && !@block.nil?
56
58
  end
57
59
 
60
+ def only_raw_sql?
61
+ @block.nil? && !conditions_empty? && !@conditions.kind_of?(Hash)
62
+ end
63
+
58
64
  def conditions_empty?
59
65
  @conditions == {} || @conditions.nil?
60
66
  end
@@ -63,7 +69,7 @@ module CanCan
63
69
  hash = {}
64
70
  conditions.map do |name, value|
65
71
  hash[name] = associations_hash(value) if value.kind_of? Hash
66
- end
72
+ end if conditions.kind_of? Hash
67
73
  hash
68
74
  end
69
75
 
@@ -71,14 +77,15 @@ module CanCan
71
77
  attributes = {}
72
78
  @conditions.each do |key, value|
73
79
  attributes[key] = value unless [Array, Range, Hash].include? value.class
74
- end
80
+ end if @conditions.kind_of? Hash
75
81
  attributes
76
82
  end
77
83
 
78
84
  private
79
85
 
80
86
  def subject_class?(subject)
81
- (subject.kind_of?(Hash) ? subject.values.first : subject).class == Class
87
+ klass = (subject.kind_of?(Hash) ? subject.values.first : subject).class
88
+ klass == Class || klass == Module
82
89
  end
83
90
 
84
91
  def matches_action?(action)
@@ -90,7 +97,7 @@ module CanCan
90
97
  end
91
98
 
92
99
  def matches_subject_class?(subject)
93
- @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)) }
94
101
  end
95
102
 
96
103
  def matches_conditions_hash?(subject, conditions = @conditions)
@@ -12,14 +12,14 @@ module CanCan
12
12
  # end
13
13
  #
14
14
  def load_and_authorize_resource(*args)
15
- ControllerResource.add_before_filter(self, :load_and_authorize_resource, *args)
15
+ cancan_resource_class.add_before_filter(self, :load_and_authorize_resource, *args)
16
16
  end
17
17
 
18
18
  # Sets up a before filter which loads the model resource into an instance variable.
19
19
  # For example, given an ArticlesController it will load the current article into the @article
20
20
  # instance variable. It does this by either calling Article.find(params[:id]) or
21
- # Article.new(params[:article]) depending upon the action. It does nothing for the "index"
22
- # action.
21
+ # Article.new(params[:article]) depending upon the action. The index action will
22
+ # automatically set @articles to Article.accessible_by(current_ability).
23
23
  #
24
24
  # If a conditions hash is used in the Ability, the +new+ and +create+ actions will set
25
25
  # the initial attributes based on these conditions. This way these actions will satisfy
@@ -69,7 +69,14 @@ module CanCan
69
69
  # Does not apply before filter to given actions.
70
70
  #
71
71
  # [:+through+]
72
- # Load this resource through another one. This should match the name of the parent instance variable.
72
+ # Load this resource through another one. This should match the name of the parent instance variable or method.
73
+ #
74
+ # [:+through_association+]
75
+ # The name of the association to fetch the child records through the parent resource. This is normally not needed
76
+ # because it defaults to the pluralized resource name.
77
+ #
78
+ # [:+shallow+]
79
+ # Pass +true+ to allow this resource to be loaded directly when parent is +nil+. Defaults to +false+.
73
80
  #
74
81
  # [:+singleton+]
75
82
  # Pass +true+ if this is a singleton resource through a +has_one+ association.
@@ -103,7 +110,7 @@ module CanCan
103
110
  # load_resource :new => :build
104
111
  #
105
112
  def load_resource(*args)
106
- ControllerResource.add_before_filter(self, :load_resource, *args)
113
+ cancan_resource_class.add_before_filter(self, :load_resource, *args)
107
114
  end
108
115
 
109
116
  # Sets up a before filter which authorizes the resource using the instance variable.
@@ -156,7 +163,7 @@ module CanCan
156
163
  # Authorize conditions on this parent resource when instance isn't available.
157
164
  #
158
165
  def authorize_resource(*args)
159
- ControllerResource.add_before_filter(self, :authorize_resource, *args)
166
+ cancan_resource_class.add_before_filter(self, :authorize_resource, *args)
160
167
  end
161
168
 
162
169
  # Add this to a controller to ensure it performs authorization through +authorized+! or +authorize_resource+ call.
@@ -169,11 +176,11 @@ module CanCan
169
176
  #
170
177
  # Any arguments are passed to the +after_filter+ it triggers.
171
178
  #
172
- # See skip_authorization to bypass this check on specific controller actions.
179
+ # See skip_authorization_check to bypass this check on specific controller actions.
173
180
  def check_authorization(*args)
174
181
  self.after_filter(*args) do |controller|
175
182
  unless controller.instance_variable_defined?(:@_authorized)
176
- raise AuthorizationNotPerformed, "This action failed the check_authorization because it does not authorize_resource. Add skip_authorization to bypass this check."
183
+ raise AuthorizationNotPerformed, "This action failed the check_authorization because it does not authorize_resource. Add skip_authorization_check to bypass this check."
177
184
  end
178
185
  end
179
186
  end
@@ -181,15 +188,27 @@ module CanCan
181
188
  # Call this in the class of a controller to skip the check_authorization behavior on the actions.
182
189
  #
183
190
  # class HomeController < ApplicationController
184
- # skip_authorization :only => :index
191
+ # skip_authorization_check :only => :index
185
192
  # end
186
193
  #
187
194
  # Any arguments are passed to the +before_filter+ it triggers.
188
- def skip_authorization(*args)
195
+ def skip_authorization_check(*args)
189
196
  self.before_filter(*args) do |controller|
190
197
  controller.instance_variable_set(:@_authorized, true)
191
198
  end
192
199
  end
200
+
201
+ def skip_authorization(*args)
202
+ raise ImplementationRemoved, "The CanCan skip_authorization method has been renamed to skip_authorization_check. Please update your code."
203
+ end
204
+
205
+ def cancan_resource_class
206
+ if ancestors.map(&:to_s).include? "InheritedResources::Actions"
207
+ InheritedResource
208
+ else
209
+ ControllerResource
210
+ end
211
+ end
193
212
  end
194
213
 
195
214
  def self.included(base)
@@ -215,7 +234,7 @@ module CanCan
215
234
  # en:
216
235
  # unauthorized:
217
236
  # manage:
218
- # all: "Not authorized to perform that action."
237
+ # all: "Not authorized to %{action} %{subject}."
219
238
  # user: "Not allowed to manage other user accounts."
220
239
  # update:
221
240
  # project: "Not allowed to update this project."
@@ -6,7 +6,7 @@ module CanCan
6
6
  options = args.extract_options!
7
7
  resource_name = args.first
8
8
  controller_class.before_filter(options.slice(:only, :except)) do |controller|
9
- ControllerResource.new(controller, resource_name, options.except(:only, :except)).send(method)
9
+ controller.class.cancan_resource_class.new(controller, resource_name, options.except(:only, :except)).send(method)
10
10
  end
11
11
  end
12
12
 
@@ -26,8 +26,10 @@ module CanCan
26
26
  end
27
27
 
28
28
  def load_resource
29
- if !resource_instance && (parent? || member_action?)
30
- @controller.instance_variable_set("@#{instance_name}", load_resource_instance)
29
+ if parent? || member_action?
30
+ self.resource_instance ||= load_resource_instance
31
+ elsif load_collection?
32
+ self.collection_instance ||= load_collection
31
33
  end
32
34
  end
33
35
 
@@ -39,7 +41,7 @@ module CanCan
39
41
  @options.has_key?(:parent) ? @options[:parent] : @name && @name != name_from_controller.to_sym
40
42
  end
41
43
 
42
- private
44
+ protected
43
45
 
44
46
  def load_resource_instance
45
47
  if !parent? && new_actions.include?(@params[:action].to_sym)
@@ -49,8 +51,21 @@ module CanCan
49
51
  end
50
52
  end
51
53
 
54
+ def load_collection?
55
+ resource_base.respond_to?(:accessible_by) &&
56
+ !current_ability.has_block?(authorization_action, resource_class)
57
+ end
58
+
59
+ def load_collection
60
+ resource_base.accessible_by(current_ability)
61
+ end
62
+
52
63
  def build_resource
53
- resource = resource_base.send(@options[:singleton] ? "build_#{name}" : "new")
64
+ if @options[:singleton] && resource_base.respond_to?("build_#{name}")
65
+ resource = resource_base.send("build_#{name}")
66
+ else
67
+ resource = resource_base.send("new")
68
+ end
54
69
  initial_attributes.each do |name, value|
55
70
  resource.send("#{name}=", value)
56
71
  end
@@ -59,11 +74,11 @@ module CanCan
59
74
  end
60
75
 
61
76
  def initial_attributes
62
- @controller.current_ability.attributes_for(@params[:action].to_sym, resource_class)
77
+ current_ability.attributes_for(@params[:action].to_sym, resource_class)
63
78
  end
64
79
 
65
80
  def find_resource
66
- if @options[:singleton]
81
+ if @options[:singleton] && resource_base.respond_to?(name)
67
82
  resource_base.send(name)
68
83
  else
69
84
  @options[:find_by] ? resource_base.send("find_by_#{@options[:find_by]}!", id_param) : resource_base.find(id_param)
@@ -98,16 +113,35 @@ module CanCan
98
113
  parent_resource ? {parent_resource => resource_class} : resource_class
99
114
  end
100
115
 
116
+ def resource_instance=(instance)
117
+ @controller.instance_variable_set("@#{instance_name}", instance)
118
+ end
119
+
101
120
  def resource_instance
102
121
  @controller.instance_variable_get("@#{instance_name}")
103
122
  end
104
123
 
124
+ def collection_instance=(instance)
125
+ @controller.instance_variable_set("@#{instance_name.to_s.pluralize}", instance)
126
+ end
127
+
128
+ def collection_instance
129
+ @controller.instance_variable_get("@#{instance_name.to_s.pluralize}")
130
+ end
131
+
105
132
  # The object that methods (such as "find", "new" or "build") are called on.
106
133
  # If the :through option is passed it will go through an association on that instance.
134
+ # If the :shallow option is passed it will use the resource_class if there's no parent
107
135
  # If the :singleton option is passed it won't use the association because it needs to be handled later.
108
136
  def resource_base
109
- if parent_resource
110
- @options[:singleton] ? parent_resource : parent_resource.send(name.to_s.pluralize)
137
+ if @options[:through]
138
+ if parent_resource
139
+ @options[:singleton] ? parent_resource : parent_resource.send(@options[:through_association] || name.to_s.pluralize)
140
+ elsif @options[:shallow]
141
+ resource_class
142
+ else
143
+ raise AccessDenied # maybe this should be a record not found error instead?
144
+ end
111
145
  else
112
146
  resource_class
113
147
  end
@@ -115,7 +149,19 @@ module CanCan
115
149
 
116
150
  # The object to load this resource through.
117
151
  def parent_resource
118
- @options[:through] && [@options[:through]].flatten.map { |i| @controller.instance_variable_get("@#{i}") }.compact.first
152
+ @options[:through] && [@options[:through]].flatten.map { |i| fetch_parent(i) }.compact.first
153
+ end
154
+
155
+ def fetch_parent(name)
156
+ if @controller.instance_variable_defined? "@#{name}"
157
+ @controller.instance_variable_get("@#{name}")
158
+ elsif @controller.respond_to? name
159
+ @controller.send(name)
160
+ end
161
+ end
162
+
163
+ def current_ability
164
+ @controller.send(:current_ability)
119
165
  end
120
166
 
121
167
  def name
@@ -0,0 +1,18 @@
1
+ module CanCan
2
+ # For use with Inherited Resources
3
+ class InheritedResource < ControllerResource # :nodoc:
4
+ def load_resource_instance
5
+ if parent?
6
+ @controller.send :parent
7
+ elsif new_actions.include? @params[:action].to_sym
8
+ @controller.send :build_resource
9
+ else
10
+ @controller.send :resource
11
+ end
12
+ end
13
+
14
+ def resource_base
15
+ @controller.send :end_of_association_chain
16
+ end
17
+ end
18
+ end
data/lib/cancan.rb CHANGED
@@ -5,3 +5,4 @@ require 'cancan/controller_additions'
5
5
  require 'cancan/active_record_additions'
6
6
  require 'cancan/exceptions'
7
7
  require 'cancan/query'
8
+ require 'cancan/inherited_resource'
@@ -24,6 +24,14 @@ describe CanCan::Ability do
24
24
  @ability.can?(:read, :some_symbol).should == true
25
25
  end
26
26
 
27
+ it "should pass nil to a block when no instance is passed" do
28
+ @ability.can :read, Symbol do |sym|
29
+ sym.should be_nil
30
+ true
31
+ end
32
+ @ability.can?(:read, Symbol).should be_true
33
+ end
34
+
27
35
  it "should pass to previous can definition, if block returns false or nil" do
28
36
  @ability.can :read, Symbol
29
37
  @ability.can :read, Integer do |i|
@@ -250,6 +258,32 @@ describe CanCan::Ability do
250
258
  @ability.can?(:read, Range).should be_true
251
259
  end
252
260
 
261
+ it "should stop at cannot definition when no hash is present" do
262
+ @ability.can :read, :all
263
+ @ability.cannot :read, Range
264
+ @ability.can?(:read, 1..5).should be_false
265
+ @ability.can?(:read, Range).should be_false
266
+ end
267
+
268
+ it "should allow to check ability for Module" do
269
+ module B; end
270
+ class A; include B; end
271
+ @ability.can :read, B
272
+ @ability.can?(:read, A).should be_true
273
+ @ability.can?(:read, A.new).should be_true
274
+ end
275
+
276
+ it "should pass nil to a block for ability on Module when no instance is passed" do
277
+ module B; end
278
+ class A; include B; end
279
+ @ability.can :read, B do |sym|
280
+ sym.should be_nil
281
+ true
282
+ end
283
+ @ability.can?(:read, B).should be_true
284
+ @ability.can?(:read, A).should be_true
285
+ end
286
+
253
287
  it "passing a hash of subjects should check permissions through association" do
254
288
  @ability.can :read, Range, :string => {:length => 3}
255
289
  @ability.can?(:read, "foo" => Range).should be_true
@@ -282,6 +316,22 @@ describe CanCan::Ability do
282
316
  lambda { @ability.authorize!(:read, :foo) }.should_not raise_error
283
317
  end
284
318
 
319
+ it "should know when block is used in conditions" do
320
+ @ability.can :read, :foo
321
+ @ability.should_not have_block(:read, :foo)
322
+ @ability.can :read, :foo do |foo|
323
+ false
324
+ end
325
+ @ability.should have_block(:read, :foo)
326
+ end
327
+
328
+ it "should know when raw sql is used in conditions" do
329
+ @ability.can :read, :foo
330
+ @ability.should_not have_raw_sql(:read, :foo)
331
+ @ability.can :read, :foo, 'false'
332
+ @ability.should have_raw_sql(:read, :foo)
333
+ end
334
+
285
335
  it "should raise access denied exception with default message if not specified" do
286
336
  begin
287
337
  @ability.authorize! :read, :foo
@@ -327,5 +377,11 @@ describe CanCan::Ability do
327
377
  @ability.unauthorized_message(:update, Array).should == "modify array"
328
378
  @ability.unauthorized_message(:edit, Array).should == "modify array"
329
379
  end
380
+
381
+ it "should have variables for action and subject" do
382
+ I18n.backend.store_translations :en, :unauthorized => {:manage => {:all => "%{action} %{subject}"}} # old syntax for now in case testing with old I18n
383
+ @ability.unauthorized_message(:update, Array).should == "update array"
384
+ @ability.unauthorized_message(:edit, 1..3).should == "edit range"
385
+ end
330
386
  end
331
387
  end
@@ -10,12 +10,14 @@ describe CanCan::ActiveRecordAdditions do
10
10
  end
11
11
 
12
12
  it "should call where('true=false') when no ability is defined so no records are found" do
13
+ stub(@model_class).joins { true } # just so it responds to .joins as well
13
14
  stub(@model_class).where('true=false').stub!.joins(nil) { :no_match }
14
15
  @model_class.accessible_by(@ability, :read).should == :no_match
15
16
  end
16
17
 
17
18
  it "should call where with matching ability conditions" do
18
19
  @ability.can :read, @model_class, :foo => {:bar => 1}
20
+ stub(@model_class).joins { true } # just so it responds to .joins as well
19
21
  stub(@model_class).where(:foos => { :bar => 1 }).stub!.joins([:foo]) { :found_records }
20
22
  @model_class.accessible_by(@ability, :read).should == :found_records
21
23
  end
@@ -48,4 +50,26 @@ describe CanCan::ActiveRecordAdditions do
48
50
  # @ability.associations_hash(:read, @model_class).should == [{:too => [:far]}, :foo]
49
51
  @model_class.accessible_by(@ability).should == :found_records
50
52
  end
53
+
54
+ it "should allow to define sql conditions by not hash" do
55
+ @ability.can :read, @model_class, :foo => 1
56
+ @ability.can :read, @model_class, ['bar = ?', 1]
57
+ stub(@model_class).scoped( :conditions => '(bar = 1) OR (foo=1)', :joins => nil ) { :found_records }
58
+ stub(@model_class).scoped{|*args| args.inspect}
59
+ @model_class.accessible_by(@ability).should == :found_records
60
+ end
61
+
62
+ it "should not allow to fetch records when ability with just block present" do
63
+ @ability.can :read, @model_class do false end
64
+ lambda {
65
+ @model_class.accessible_by(@ability)
66
+ }.should raise_error(CanCan::Error)
67
+ end
68
+
69
+ it "should not allow to check ability on object when nonhash sql ability definition without block present" do
70
+ @ability.can :read, @model_class, ['bar = ?', 1]
71
+ lambda {
72
+ @ability.can? :read, @model_class.new
73
+ }.should raise_error(CanCan::Error)
74
+ end
51
75
  end
@@ -54,9 +54,9 @@ describe CanCan::ControllerAdditions do
54
54
  @controller_class.load_resource :foo => :bar, :only => [:show, :index]
55
55
  end
56
56
 
57
- it "skip_authorization should set up a before filter which sets @_authorized to true" do
57
+ it "skip_authorization_check should set up a before filter which sets @_authorized to true" do
58
58
  mock(@controller_class).before_filter(:filter_options) { |options, block| block.call(@controller) }
59
- @controller_class.skip_authorization(:filter_options)
59
+ @controller_class.skip_authorization_check(:filter_options)
60
60
  @controller.instance_variable_get(:@_authorized).should be_true
61
61
  end
62
62
 
@@ -74,4 +74,13 @@ describe CanCan::ControllerAdditions do
74
74
  @controller_class.check_authorization(:some_options)
75
75
  }.should_not raise_error(CanCan::AuthorizationNotPerformed)
76
76
  end
77
+
78
+ it "cancan_resource_class should be ControllerResource by default" do
79
+ @controller.class.cancan_resource_class.should == CanCan::ControllerResource
80
+ end
81
+
82
+ it "cancan_resource_class should be InheritedResource when class includes InheritedResources::Actions" do
83
+ stub(@controller.class).ancestors { ["InheritedResources::Actions"] }
84
+ @controller.class.cancan_resource_class.should == CanCan::InheritedResource
85
+ end
77
86
  end
@@ -4,16 +4,17 @@ describe CanCan::ControllerResource do
4
4
  before(:each) do
5
5
  @params = HashWithIndifferentAccess.new(:controller => "projects")
6
6
  @controller = Object.new # simple stub for now
7
+ @ability = Ability.new(nil)
7
8
  stub(@controller).params { @params }
8
- stub(@controller).current_ability.stub!.attributes_for { {} }
9
+ stub(@controller).current_ability { @ability }
9
10
  end
10
11
 
11
12
  it "should load the resource into an instance variable if params[:id] is specified" do
12
- @params.merge!(:action => "show", :id => 123)
13
- stub(Project).find(123) { :some_project }
13
+ project = Project.create!
14
+ @params.merge!(:action => "show", :id => project.id)
14
15
  resource = CanCan::ControllerResource.new(@controller)
15
16
  resource.load_resource
16
- @controller.instance_variable_get(:@project).should == :some_project
17
+ @controller.instance_variable_get(:@project).should == project
17
18
  end
18
19
 
19
20
  it "should not load resource into an instance variable if already set" do
@@ -25,19 +26,19 @@ describe CanCan::ControllerResource do
25
26
  end
26
27
 
27
28
  it "should properly load resource for namespaced controller" do
28
- @params.merge!(:controller => "admin/projects", :action => "show", :id => 123)
29
- stub(Project).find(123) { :some_project }
29
+ project = Project.create!
30
+ @params.merge!(:controller => "admin/projects", :action => "show", :id => project.id)
30
31
  resource = CanCan::ControllerResource.new(@controller)
31
32
  resource.load_resource
32
- @controller.instance_variable_get(:@project).should == :some_project
33
+ @controller.instance_variable_get(:@project).should == project
33
34
  end
34
35
 
35
36
  it "should properly load resource for namespaced controller when using '::' for namespace" do
36
- @params.merge!(:controller => "Admin::ProjectsController", :action => "show", :id => 123)
37
- stub(Project).find(123) { :some_project }
37
+ project = Project.create!
38
+ @params.merge!(:controller => "Admin::ProjectsController", :action => "show", :id => project.id)
38
39
  resource = CanCan::ControllerResource.new(@controller)
39
40
  resource.load_resource
40
- @controller.instance_variable_get(:@project).should == :some_project
41
+ @controller.instance_variable_get(:@project).should == project
41
42
  end
42
43
 
43
44
  it "should build a new resource with hash if params[:id] is not specified" do
@@ -49,7 +50,7 @@ describe CanCan::ControllerResource do
49
50
 
50
51
  it "should build a new resource with attributes from current ability" do
51
52
  @params.merge!(:action => "new")
52
- stub(@controller).current_ability.stub!.attributes_for(:new, Project) { {:name => "from conditions"} }
53
+ @ability.can(:create, Project, :name => "from conditions")
53
54
  resource = CanCan::ControllerResource.new(@controller)
54
55
  resource.load_resource
55
56
  @controller.instance_variable_get(:@project).name.should == "from conditions"
@@ -57,17 +58,37 @@ describe CanCan::ControllerResource do
57
58
 
58
59
  it "should override initial attributes with params" do
59
60
  @params.merge!(:action => "new", :project => {:name => "from params"})
60
- stub(@controller).current_ability.stub!.attributes_for(:new, Project) { {:name => "foobar"} }
61
+ @ability.can(:create, Project, :name => "from conditions")
61
62
  resource = CanCan::ControllerResource.new(@controller)
62
63
  resource.load_resource
63
64
  @controller.instance_variable_get(:@project).name.should == "from params"
64
65
  end
65
66
 
66
- it "should not build a resource when on index action" do
67
+ it "should build a collection when on index action when class responds to accessible_by" do
68
+ stub(Project).accessible_by(@ability) { :found_projects }
69
+ @params[:action] = "index"
70
+ resource = CanCan::ControllerResource.new(@controller, :project)
71
+ resource.load_resource
72
+ @controller.instance_variable_get(:@project).should be_nil
73
+ @controller.instance_variable_get(:@projects).should == :found_projects
74
+ end
75
+
76
+ it "should not build a collection when on index action when class does not respond to accessible_by" do
77
+ @params[:action] = "index"
78
+ resource = CanCan::ControllerResource.new(@controller)
79
+ resource.load_resource
80
+ @controller.instance_variable_get(:@project).should be_nil
81
+ @controller.instance_variable_defined?(:@projects).should be_false
82
+ end
83
+
84
+ it "should not use accessible_by when defining abilities through a block" do
85
+ stub(Project).accessible_by(@ability) { :found_projects }
67
86
  @params[:action] = "index"
87
+ @ability.can(:read, Project) { |p| false }
68
88
  resource = CanCan::ControllerResource.new(@controller)
69
89
  resource.load_resource
70
90
  @controller.instance_variable_get(:@project).should be_nil
91
+ @controller.instance_variable_defined?(:@projects).should be_false
71
92
  end
72
93
 
73
94
  it "should perform authorization using controller action and loaded model" do
@@ -136,14 +157,14 @@ describe CanCan::ControllerResource do
136
157
  end
137
158
 
138
159
  it "should load parent resource through proper id parameter" do
139
- @params.merge!(:action => "index", :project_id => 123)
140
- stub(Project).find(123) { :some_project }
160
+ project = Project.create!
161
+ @params.merge!(:action => "index", :project_id => project.id)
141
162
  resource = CanCan::ControllerResource.new(@controller, :project, :parent => true)
142
163
  resource.load_resource
143
- @controller.instance_variable_get(:@project).should == :some_project
164
+ @controller.instance_variable_get(:@project).should == project
144
165
  end
145
166
 
146
- it "should load resource through the association of another parent resource" do
167
+ it "should load resource through the association of another parent resource using instance variable" do
147
168
  @params.merge!(:action => "show", :id => 123)
148
169
  category = Object.new
149
170
  @controller.instance_variable_set(:@category, category)
@@ -153,14 +174,44 @@ describe CanCan::ControllerResource do
153
174
  @controller.instance_variable_get(:@project).should == :some_project
154
175
  end
155
176
 
156
- it "should not load through parent resource if instance isn't loaded" do
177
+ it "should load resource through the custom association name" do
178
+ @params.merge!(:action => "show", :id => 123)
179
+ category = Object.new
180
+ @controller.instance_variable_set(:@category, category)
181
+ stub(category).custom_projects.stub!.find(123) { :some_project }
182
+ resource = CanCan::ControllerResource.new(@controller, :through => :category, :through_association => :custom_projects)
183
+ resource.load_resource
184
+ @controller.instance_variable_get(:@project).should == :some_project
185
+ end
186
+
187
+ it "should load resource through the association of another parent resource using method" do
157
188
  @params.merge!(:action => "show", :id => 123)
158
- stub(Project).find(123) { :some_project }
189
+ category = Object.new
190
+ stub(@controller).category { category }
191
+ stub(category).projects.stub!.find(123) { :some_project }
159
192
  resource = CanCan::ControllerResource.new(@controller, :through => :category)
160
193
  resource.load_resource
161
194
  @controller.instance_variable_get(:@project).should == :some_project
162
195
  end
163
196
 
197
+ it "should not load through parent resource if instance isn't loaded when shallow" do
198
+ project = Project.create!
199
+ @params.merge!(:action => "show", :id => project.id)
200
+ resource = CanCan::ControllerResource.new(@controller, :through => :category, :shallow => true)
201
+ resource.load_resource
202
+ @controller.instance_variable_get(:@project).should == project
203
+ end
204
+
205
+ it "should raise AccessDenied when attempting to load resource through nil" do
206
+ project = Project.create!
207
+ @params.merge!(:action => "show", :id => project.id)
208
+ resource = CanCan::ControllerResource.new(@controller, :through => :category)
209
+ lambda {
210
+ resource.load_resource
211
+ }.should raise_error(CanCan::AccessDenied)
212
+ @controller.instance_variable_get(:@project).should be_nil
213
+ end
214
+
164
215
  it "should authorize nested resource through parent association on index action" do
165
216
  @params.merge!(:action => "index")
166
217
  category = Object.new
@@ -200,20 +251,35 @@ describe CanCan::ControllerResource do
200
251
  @controller.instance_variable_get(:@project).name.should == "foobar"
201
252
  end
202
253
 
254
+ it "should find record through has_one association with :singleton and :shallow options" do
255
+ project = Project.create!
256
+ @params.merge!(:action => "show", :id => project.id)
257
+ resource = CanCan::ControllerResource.new(@controller, :through => :category, :singleton => true, :shallow => true)
258
+ resource.load_resource
259
+ @controller.instance_variable_get(:@project).should == project
260
+ end
261
+
262
+ it "should build record through has_one association with :singleton and :shallow options" do
263
+ @params.merge!(:action => "create", :project => {:name => "foobar"})
264
+ resource = CanCan::ControllerResource.new(@controller, :through => :category, :singleton => true, :shallow => true)
265
+ resource.load_resource
266
+ @controller.instance_variable_get(:@project).name.should == "foobar"
267
+ end
268
+
203
269
  it "should only authorize :read action on parent resource" do
204
- @params.merge!(:action => "new", :project_id => 123)
205
- stub(Project).find(123) { :some_project }
206
- stub(@controller).authorize!(:read, :some_project) { raise CanCan::AccessDenied }
270
+ project = Project.create!
271
+ @params.merge!(:action => "new", :project_id => project.id)
272
+ stub(@controller).authorize!(:read, project) { raise CanCan::AccessDenied }
207
273
  resource = CanCan::ControllerResource.new(@controller, :project, :parent => true)
208
274
  lambda { resource.load_and_authorize_resource }.should raise_error(CanCan::AccessDenied)
209
275
  end
210
276
 
211
277
  it "should load the model using a custom class" do
212
- @params.merge!(:action => "show", :id => 123)
213
- stub(Project).find(123) { :some_project }
278
+ project = Project.create!
279
+ @params.merge!(:action => "show", :id => project.id)
214
280
  resource = CanCan::ControllerResource.new(@controller, :class => Project)
215
281
  resource.load_resource
216
- @controller.instance_variable_get(:@project).should == :some_project
282
+ @controller.instance_variable_get(:@project).should == project
217
283
  end
218
284
 
219
285
  it "should authorize based on resource name if class is false" do
@@ -224,20 +290,20 @@ describe CanCan::ControllerResource do
224
290
  end
225
291
 
226
292
  it "should load and authorize using custom instance name" do
227
- @params.merge!(:action => "show", :id => 123)
228
- stub(Project).find(123) { :some_project }
229
- stub(@controller).authorize!(:show, :some_project) { raise CanCan::AccessDenied }
293
+ project = Project.create!
294
+ @params.merge!(:action => "show", :id => project.id)
295
+ stub(@controller).authorize!(:show, project) { raise CanCan::AccessDenied }
230
296
  resource = CanCan::ControllerResource.new(@controller, :instance_name => :custom_project)
231
297
  lambda { resource.load_and_authorize_resource }.should raise_error(CanCan::AccessDenied)
232
- @controller.instance_variable_get(:@custom_project).should == :some_project
298
+ @controller.instance_variable_get(:@custom_project).should == project
233
299
  end
234
300
 
235
301
  it "should load resource using custom find_by attribute" do
236
- @params.merge!(:action => "show", :id => 123)
237
- stub(Project).find_by_name!(123) { :some_project }
302
+ project = Project.create!(:name => "foo")
303
+ @params.merge!(:action => "show", :id => "foo")
238
304
  resource = CanCan::ControllerResource.new(@controller, :find_by => :name)
239
305
  resource.load_resource
240
- @controller.instance_variable_get(:@project).should == :some_project
306
+ @controller.instance_variable_get(:@project).should == project
241
307
  end
242
308
 
243
309
  it "should raise ImplementationRemoved when adding :name option" do
@@ -0,0 +1,40 @@
1
+ require "spec_helper"
2
+
3
+ describe CanCan::InheritedResource do
4
+ before(:each) do
5
+ @params = HashWithIndifferentAccess.new(:controller => "projects")
6
+ @controller = Object.new # simple stub for now
7
+ @ability = Ability.new(nil)
8
+ stub(@controller).params { @params }
9
+ stub(@controller).current_ability { @ability }
10
+ end
11
+
12
+ it "show should load resource through @controller.resource" do
13
+ @params[:action] = "show"
14
+ stub(@controller).resource { :project_resource }
15
+ CanCan::InheritedResource.new(@controller).load_resource
16
+ @controller.instance_variable_get(:@project).should == :project_resource
17
+ end
18
+
19
+ it "new should load through @controller.build_resource" do
20
+ @params[:action] = "new"
21
+ stub(@controller).build_resource { :project_resource }
22
+ CanCan::InheritedResource.new(@controller).load_resource
23
+ @controller.instance_variable_get(:@project).should == :project_resource
24
+ end
25
+
26
+ it "index should load through @controller.parent when parent" do
27
+ @params[:action] = "index"
28
+ stub(@controller).parent { :project_resource }
29
+ CanCan::InheritedResource.new(@controller, :parent => true).load_resource
30
+ @controller.instance_variable_get(:@project).should == :project_resource
31
+ end
32
+
33
+ it "index should load through @controller.end_of_association_chain" do
34
+ @params[:action] = "index"
35
+ stub(Project).accessible_by(@ability) { :projects }
36
+ stub(@controller).end_of_association_chain { Project }
37
+ CanCan::InheritedResource.new(@controller).load_resource
38
+ @controller.instance_variable_get(:@projects).should == :projects
39
+ end
40
+ end
data/spec/matchers.rb CHANGED
@@ -1,4 +1,4 @@
1
- Spec::Matchers.define :orderlessly_match do |original_string|
1
+ RSpec::Matchers.define :orderlessly_match do |original_string|
2
2
  match do |given_string|
3
3
  original_string.split('').sort == given_string.split('').sort
4
4
  end
data/spec/spec_helper.rb CHANGED
@@ -1,15 +1,17 @@
1
1
  require 'rubygems'
2
- require 'spec'
3
- require 'active_support'
4
- require 'active_record'
5
- require 'action_controller'
6
- require 'action_view'
2
+ require 'bundler/setup'
3
+ Bundler.require(:default)
4
+ require 'supermodel' # shouldn't Bundler do this already?
5
+ require 'active_support/all'
7
6
  require 'matchers'
8
- require 'cancan'
9
7
  require 'cancan/matchers'
10
8
 
11
- Spec::Runner.configure do |config|
9
+ RSpec.configure do |config|
12
10
  config.mock_with :rr
11
+ config.before(:each) do
12
+ Project.delete_all
13
+ Category.delete_all
14
+ end
13
15
  end
14
16
 
15
17
  class Ability
@@ -19,17 +21,12 @@ class Ability
19
21
  end
20
22
  end
21
23
 
22
- # Generic class to mimic a model
23
- class Project
24
- attr_accessor :name
25
-
26
- def initialize(attributes = {})
27
- @name = attributes[:name]
28
- end
24
+ class Category < SuperModel::Base
25
+ has_many :projects
26
+ end
29
27
 
30
- def attributes=(attributes)
31
- @name = attributes[:name] if attributes[:name]
32
- end
28
+ class Project < SuperModel::Base
29
+ belongs_to :category
33
30
 
34
31
  class << self
35
32
  protected
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cancan
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: true
4
+ hash: 5
5
+ prerelease: false
5
6
  segments:
6
7
  - 1
7
8
  - 4
8
- - 0
9
- - beta1
10
- version: 1.4.0.beta1
9
+ - 1
10
+ version: 1.4.1
11
11
  platform: ruby
12
12
  authors:
13
13
  - Ryan Bates
@@ -15,10 +15,75 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-09-03 00:00:00 -07:00
18
+ date: 2010-11-12 00:00:00 -08:00
19
19
  default_executable:
20
- dependencies: []
21
-
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 62196431
30
+ segments:
31
+ - 2
32
+ - 0
33
+ - 0
34
+ - beta
35
+ - 22
36
+ version: 2.0.0.beta.22
37
+ type: :development
38
+ version_requirements: *id001
39
+ - !ruby/object:Gem::Dependency
40
+ name: rails
41
+ prerelease: false
42
+ requirement: &id002 !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ hash: 7
48
+ segments:
49
+ - 3
50
+ - 0
51
+ - 0
52
+ version: 3.0.0
53
+ type: :development
54
+ version_requirements: *id002
55
+ - !ruby/object:Gem::Dependency
56
+ name: rr
57
+ prerelease: false
58
+ requirement: &id003 !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ~>
62
+ - !ruby/object:Gem::Version
63
+ hash: 33
64
+ segments:
65
+ - 0
66
+ - 10
67
+ - 11
68
+ version: 0.10.11
69
+ type: :development
70
+ version_requirements: *id003
71
+ - !ruby/object:Gem::Dependency
72
+ name: supermodel
73
+ prerelease: false
74
+ requirement: &id004 !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ~>
78
+ - !ruby/object:Gem::Version
79
+ hash: 19
80
+ segments:
81
+ - 0
82
+ - 1
83
+ - 4
84
+ version: 0.1.4
85
+ type: :development
86
+ version_requirements: *id004
22
87
  description: Simple authorization solution for Rails which is decoupled from user roles. All permissions are stored in a single location.
23
88
  email: ryan@railscasts.com
24
89
  executables: []
@@ -34,6 +99,7 @@ files:
34
99
  - lib/cancan/controller_additions.rb
35
100
  - lib/cancan/controller_resource.rb
36
101
  - lib/cancan/exceptions.rb
102
+ - lib/cancan/inherited_resource.rb
37
103
  - lib/cancan/matchers.rb
38
104
  - lib/cancan/query.rb
39
105
  - lib/cancan.rb
@@ -43,12 +109,14 @@ files:
43
109
  - spec/cancan/controller_additions_spec.rb
44
110
  - spec/cancan/controller_resource_spec.rb
45
111
  - spec/cancan/exceptions_spec.rb
112
+ - spec/cancan/inherited_resource_spec.rb
46
113
  - spec/cancan/matchers_spec.rb
47
114
  - spec/cancan/query_spec.rb
48
115
  - spec/matchers.rb
49
116
  - spec/spec.opts
50
117
  - spec/spec_helper.rb
51
118
  - CHANGELOG.rdoc
119
+ - Gemfile
52
120
  - LICENSE
53
121
  - Rakefile
54
122
  - README.rdoc
@@ -63,16 +131,20 @@ rdoc_options: []
63
131
  require_paths:
64
132
  - lib
65
133
  required_ruby_version: !ruby/object:Gem::Requirement
134
+ none: false
66
135
  requirements:
67
136
  - - ">="
68
137
  - !ruby/object:Gem::Version
138
+ hash: 3
69
139
  segments:
70
140
  - 0
71
141
  version: "0"
72
142
  required_rubygems_version: !ruby/object:Gem::Requirement
143
+ none: false
73
144
  requirements:
74
145
  - - ">="
75
146
  - !ruby/object:Gem::Version
147
+ hash: 19
76
148
  segments:
77
149
  - 1
78
150
  - 3
@@ -81,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
81
153
  requirements: []
82
154
 
83
155
  rubyforge_project: cancan
84
- rubygems_version: 1.3.6
156
+ rubygems_version: 1.3.7
85
157
  signing_key:
86
158
  specification_version: 3
87
159
  summary: Simple authorization solution for Rails.