cancancan 1.13.1 → 1.14.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 59a95c6a9629c67f5004699cdbb205108d814ad5
4
- data.tar.gz: 4f9cd96fcf1d2a3519b07605619240403b6b33eb
3
+ metadata.gz: 597f9c67d00dc9af7805456aca9464c41b795f42
4
+ data.tar.gz: e52df51e0f69a1f98509e7d8ae7ab8e14affb418
5
5
  SHA512:
6
- metadata.gz: da1ce9db915cc5305d31d88c0c8f437088ceeab1f2b744a3a258923d59b6da743dd86a643a39c4602afe313c13cb39f1361d220144086a05913c41f5398428ed
7
- data.tar.gz: 09c524f8d9928aa7029a9814fae9d45dcd8bf27ecf52f756cbafd02a716bc599f0ab12b4a3fdb37a71aa7ab5893b734b313989f1450a5d3792b77c9abdf7890a
6
+ metadata.gz: df305f2d2c391e2ce49bf98f0cdbe6b0cc0d96140467bb3efa66032ce61fe0ada2984ab94bfc956fbd5a8e3d1251ae7f64e3fe5773a815d172abb62f5f71db18
7
+ data.tar.gz: 33dcb0f4db08c07cf7e62103588177c628ddf7f8e219495b2e74bec94c9e2608c9b38daf2009626ef51aa77e97f6cdfc4c12d08a946f2c41d2b99e7385a8e7ea
data/.travis.yml CHANGED
@@ -5,8 +5,7 @@ rvm:
5
5
  - 2.0.0
6
6
  - 2.1.0
7
7
  - 2.2.0
8
- - jruby
9
- - rbx
8
+ - jruby-9.0.5.0
10
9
  gemfile:
11
10
  - gemfiles/activerecord_3.2.gemfile
12
11
  - gemfiles/activerecord_4.0.gemfile
@@ -18,8 +17,6 @@ services:
18
17
  - mongodb
19
18
  matrix:
20
19
  fast_finish: true
21
- allow_failures:
22
- - rvm: rbx
23
20
  exclude:
24
21
  - rvm: 2.2.0
25
22
  gemfile: gemfiles/activerecord_3.2.gemfile
data/CHANGELOG.rdoc CHANGED
@@ -2,6 +2,8 @@ Develop
2
2
 
3
3
  Unreleased
4
4
 
5
+ * Add support for rails 4 enum's (markpmitchell)
6
+
5
7
  1.13.1 (Oct 8th, 2015)
6
8
 
7
9
  * Fix #merge with empty Ability (jhawthorn)
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # CanCanCan
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/cancancan.svg)](http://badge.fury.io/rb/cancancan)
4
- [![Travis badge](https://travis-ci.org/CanCanCommunity/cancancan.png?branch=master)](https://travis-ci.org/CanCanCommunity/cancancan)
5
- [![Code Climate Badge](https://codeclimate.com/github/CanCanCommunity/cancancan.png)](https://codeclimate.com/github/CanCanCommunity/cancancan)
6
- [![Inch CI](http://inch-ci.org/github/CanCanCommunity/cancancan.png)](http://inch-ci.org/github/CanCanCommunity/cancancan)
4
+ [![Travis badge](https://travis-ci.org/CanCanCommunity/cancancan.svg?branch=develop)](https://travis-ci.org/CanCanCommunity/cancancan)
5
+ [![Code Climate Badge](https://codeclimate.com/github/CanCanCommunity/cancancan.svg)](https://codeclimate.com/github/CanCanCommunity/cancancan)
6
+ [![Inch CI](http://inch-ci.org/github/CanCanCommunity/cancancan.svg)](http://inch-ci.org/github/CanCanCommunity/cancancan)
7
7
 
8
8
  [Wiki](https://github.com/CanCanCommunity/cancancan/wiki) | [RDocs](http://rdoc.info/projects/CanCanCommunity/cancancan) | [Screencast](http://railscasts.com/episodes/192-authorization-with-cancan) | [IRC: #cancancan (freenode)](http://webchat.freenode.net/?channels=cancancan)
9
9
 
@@ -193,9 +193,12 @@ This will raise an exception if authorization is not performed in an action. If
193
193
  * [Changing Defaults](https://github.com/CanCanCommunity/cancancan/wiki/Changing-Defaults)
194
194
  * [See more](https://github.com/CanCanCommunity/cancancan/wiki)
195
195
 
196
- ## Questions or Problems?
196
+ ## Questions?
197
+ If you have any question or doubt regarding CanCanCan which you cannot find the solution to in the [documentation](https://github.com/CanCanCommunity/cancancan/wiki) or our [mailing list](http://groups.google.com/group/cancancan), please [open a question on Stackoverflow](http://stackoverflow.com/questions/ask?tags=cancancan) with tag [cancancan](http://stackoverflow.com/questions/tagged/cancancan)
197
198
 
198
- If you have any issues with CanCan which you cannot find the solution to in the [documentation](https://github.com/CanCanCommunity/cancancan/wiki) or our mailing list: http://groups.google.com/group/cancancan, please add an [issue on GitHub](https://github.com/CanCanCommunity/cancancan/issues) or fork the project and send a pull request.
199
+ ## Bugs?
200
+
201
+ If you find a bug please add an [issue on GitHub](https://github.com/CanCanCommunity/cancancan/issues) or fork the project and send a pull request.
199
202
 
200
203
 
201
204
  ## Development
@@ -61,14 +61,11 @@ module CanCan
61
61
  #
62
62
  # Also see the RSpec Matchers to aid in testing.
63
63
  def can?(action, subject, *extra_args)
64
- subject = extract_subjects(subject)
65
-
66
- match = subject.map do |subject|
67
- relevant_rules_for_match(action, subject).detect do |rule|
68
- rule.matches_conditions?(action, subject, extra_args)
64
+ match = extract_subjects(subject).lazy.map do |a_subject|
65
+ relevant_rules_for_match(action, a_subject).detect do |rule|
66
+ rule.matches_conditions?(action, a_subject, extra_args)
69
67
  end
70
- end.compact.first
71
-
68
+ end.reject(&:nil?).first
72
69
  match ? match.base_behavior : false
73
70
  end
74
71
  # Convenience method which works the same as "can?" but returns the opposite value.
@@ -322,7 +319,7 @@ module CanCan
322
319
 
323
320
  # It translates to an array the subject or the hash with multiple subjects given to can?.
324
321
  def extract_subjects(subject)
325
- subject = if subject.kind_of?(Hash) && subject.key?(:any)
322
+ if subject.kind_of?(Hash) && subject.key?(:any)
326
323
  subject[:any]
327
324
  else
328
325
  [subject]
@@ -369,9 +366,24 @@ module CanCan
369
366
  rule.relevant? action, subject
370
367
  end
371
368
  relevant.reverse!.uniq!
369
+ optimize_order! relevant
372
370
  relevant
373
371
  end
374
372
 
373
+ # Optimizes the order of the rules, so that rules with the :all subject are evaluated first.
374
+ def optimize_order!(rules)
375
+ first_can_in_group = -1
376
+ rules.each_with_index do |rule, i|
377
+ (first_can_in_group = -1) and next unless rule.base_behavior
378
+ (first_can_in_group = i) and next if first_can_in_group == -1
379
+ if rule.subjects == [:all]
380
+ rules[i] = rules[first_can_in_group]
381
+ rules[first_can_in_group] = rule
382
+ first_can_in_group += 1
383
+ end
384
+ end
385
+ end
386
+
375
387
  def possible_relevant_rules(subject)
376
388
  if subject.is_a?(Hash)
377
389
  rules
@@ -262,9 +262,7 @@ module CanCan
262
262
  end
263
263
 
264
264
  def namespaced_name
265
- [namespace, name.camelize].flatten.map(&:camelize).join('::').singularize.constantize
266
- rescue NameError
267
- name
265
+ ([namespace, name] * '/').singularize.camelize.safe_constantize || name
268
266
  end
269
267
 
270
268
  def name_from_controller
@@ -18,6 +18,23 @@ module CanCan
18
18
  relation
19
19
  end
20
20
 
21
+ def self.override_condition_matching?(subject, name, value)
22
+ # ActiveRecord introduced enums in version 4.1.
23
+ ActiveRecord::VERSION::MINOR >= 1 &&
24
+ subject.class.defined_enums.include?(name.to_s)
25
+ end
26
+
27
+ def self.matches_condition?(subject, name, value)
28
+ # Get the mapping from enum strings to values.
29
+ enum = subject.class.send(name.to_s.pluralize)
30
+ # Get the value of the attribute as an integer.
31
+ attribute = enum[subject.send(name)]
32
+ # Check to see if the value matches the condition.
33
+ value.is_a?(Enumerable) ?
34
+ (value.include? attribute) :
35
+ attribute == value
36
+ end
37
+
21
38
  # Rails 4.2 deprecates `sanitize_sql_hash_for_conditions`
22
39
  def sanitize_sql(conditions)
23
40
  if ActiveRecord::VERSION::MINOR >= 2 && Hash === conditions
@@ -35,15 +35,36 @@ module CanCan
35
35
 
36
36
  rules.inject(@model_class.all) do |records, rule|
37
37
  if process_can_rules && rule.base_behavior
38
- records.or rule.conditions
38
+ records.or simplify_relations(@model_class, rule.conditions)
39
39
  elsif !rule.base_behavior
40
- records.excludes rule.conditions
40
+ records.excludes simplify_relations(@model_class, rule.conditions)
41
41
  else
42
42
  records
43
43
  end
44
44
  end
45
45
  end
46
46
  end
47
+
48
+ private
49
+ # Look for criteria on relations and replace with simple id queries
50
+ # eg.
51
+ # {user: {:tags.all => []}} becomes {"user_id" => {"$in" => [__, ..]}}
52
+ # {user: {:session => {:tags.all => []}}} becomes {"user_id" => {"session_id" => {"$in" => [__, ..]} }}
53
+ def simplify_relations model_class, conditions
54
+ model_relations = model_class.relations.with_indifferent_access
55
+ Hash[
56
+ conditions.map {|k,v|
57
+ if relation = model_relations[k]
58
+ relation_class_name = relation[:class_name].blank? ? k.to_s.classify : relation[:class_name]
59
+ v = simplify_relations(relation_class_name.constantize, v)
60
+ relation_ids = relation_class_name.constantize.where(v).only(:id).map(&:id)
61
+ k = "#{k}_id"
62
+ v = { "$in" => relation_ids }
63
+ end
64
+ [k,v]
65
+ }
66
+ ]
67
+ end
47
68
  end
48
69
  end
49
70
  end
data/lib/cancan/rule.rb CHANGED
@@ -112,15 +112,15 @@ module CanCan
112
112
 
113
113
  conditions.all? do |name, value|
114
114
  if adapter.override_condition_matching?(subject, name, value)
115
- return adapter.matches_condition?(subject, name, value)
115
+ adapter.matches_condition?(subject, name, value)
116
+ else
117
+ condition_match?(subject.send(name), value)
116
118
  end
117
-
118
- condition_match?(subject.send(name), value)
119
119
  end
120
120
  end
121
121
 
122
122
  def nested_subject_matches_conditions?(subject_hash)
123
- parent, child = subject_hash.first
123
+ parent, _child = subject_hash.first
124
124
  matches_conditions_hash?(parent, @conditions[parent.class.name.downcase.to_sym] || {})
125
125
  end
126
126
 
@@ -140,6 +140,7 @@ module CanCan
140
140
  case value
141
141
  when Hash then hash_condition_match?(attribute, value)
142
142
  when String then attribute == value
143
+ when Range then value.cover?(attribute)
143
144
  when Enumerable then value.include?(attribute)
144
145
  else attribute == value
145
146
  end
@@ -1,3 +1,3 @@
1
1
  module CanCan
2
- VERSION = "1.13.1"
2
+ VERSION = "1.14.0"
3
3
  end
data/spec/README.rdoc CHANGED
@@ -16,7 +16,7 @@ You can then run all test sets:
16
16
 
17
17
  Or individual ones:
18
18
 
19
- appraisal rails_3.0 rake
19
+ appraisal activerecord_3.2 rake
20
20
 
21
21
  A list of the tests is in the +Appraisal+ file.
22
22
 
@@ -52,6 +52,22 @@ describe CanCan::Ability do
52
52
  expect(@ability.can?(:read, 6)).to be(true)
53
53
  end
54
54
 
55
+ it "performs can(_, :all) before other checks when can(_, :all) is defined before" do
56
+ @ability.can :manage, :all
57
+ @ability.can :edit, String do |_string|
58
+ fail 'Performed a check for :edit before the check for :all'
59
+ end
60
+ expect { @ability.can? :edit, 'a' }.to_not raise_exception
61
+ end
62
+
63
+ it "performs can(_, :all) before other checks when can(_, :all) is defined after" do
64
+ @ability.can :edit, String do |_string|
65
+ fail 'Performed a check for :edit before the check for :all'
66
+ end
67
+ @ability.can :manage, :all
68
+ expect { @ability.can? :edit, 'a' }.to_not raise_exception
69
+ end
70
+
55
71
  it "does not pass class with object if :all objects are accepted" do
56
72
  @ability.can :preview, :all do |object|
57
73
  expect(object).to eq(123)
@@ -294,6 +310,13 @@ describe CanCan::Ability do
294
310
  expect(@ability.can?(:read, 4..40)).to be(false)
295
311
  end
296
312
 
313
+ it "allows a range of time in conditions hash" do
314
+ @ability.can :read, Range, :begin => 1.day.from_now..3.days.from_now
315
+ expect(@ability.can?(:read, 1.day.from_now..10.days.from_now)).to be(true)
316
+ expect(@ability.can?(:read, 2.days.from_now..20.days.from_now)).to be(true)
317
+ expect(@ability.can?(:read, 4.days.from_now..40.days.from_now)).to be(false)
318
+ end
319
+
297
320
  it "allows nested hashes in conditions hash" do
298
321
  @ability.can :read, Range, :begin => { :to_i => 5 }
299
322
  expect(@ability.can?(:read, 5..7)).to be(true)
@@ -501,6 +501,17 @@ describe CanCan::ControllerResource do
501
501
  end
502
502
  end
503
503
 
504
+ context 'when @name passed as symbol' do
505
+ it 'returns namespaced #resource_class' do
506
+ module Admin; end
507
+ class Admin::Dashboard; end;
508
+ params.merge!(:controller => "admin/dashboard")
509
+ resource = CanCan::ControllerResource.new(controller, :dashboard)
510
+
511
+ expect(resource.send(:resource_class)).to eq Admin::Dashboard
512
+ end
513
+ end
514
+
504
515
  it "calls the santitizer when the parameter hash matches our object" do
505
516
  params.merge!(:action => 'create', :model => { :name => 'test' })
506
517
  allow(controller).to receive(:create_params).and_return({})
@@ -37,6 +37,75 @@ if defined? CanCan::ModelAdapters::ActiveRecord4Adapter
37
37
 
38
38
  expect(Parent.accessible_by(@ability).order(:created_at => :asc).includes(:children).first.children).to eq [child2, child1]
39
39
  end
40
+
41
+ if ActiveRecord::VERSION::MINOR >= 1
42
+ it "allows filters on enums" do
43
+ ActiveRecord::Schema.define do
44
+ create_table(:shapes) do |t|
45
+ t.integer :color, default: 0, null: false
46
+ end
47
+ end
48
+
49
+ class Shape < ActiveRecord::Base
50
+ enum color: [:red, :green, :blue]
51
+ end
52
+
53
+ red = Shape.create!(color: :red)
54
+ green = Shape.create!(color: :green)
55
+ blue = Shape.create!(color: :blue)
56
+
57
+ # A condition with a single value.
58
+ @ability.can :read, Shape, color: Shape.colors[:green]
59
+
60
+ expect(@ability.cannot? :read, red).to be true
61
+ expect(@ability.can? :read, green).to be true
62
+ expect(@ability.cannot? :read, blue).to be true
63
+
64
+ accessible = Shape.accessible_by(@ability)
65
+ expect(accessible).to contain_exactly(green)
66
+
67
+ # A condition with multiple values.
68
+ @ability.can :update, Shape, color: [Shape.colors[:red],
69
+ Shape.colors[:blue]]
70
+
71
+ expect(@ability.can? :update, red).to be true
72
+ expect(@ability.cannot? :update, green).to be true
73
+ expect(@ability.can? :update, blue).to be true
74
+
75
+ accessible = Shape.accessible_by(@ability, :update)
76
+ expect(accessible).to contain_exactly(red, blue)
77
+ end
78
+
79
+ it "allows dual filter on enums" do
80
+ ActiveRecord::Schema.define do
81
+ create_table(:discs) do |t|
82
+ t.integer :color, default: 0, null: false
83
+ t.integer :shape, default: 3, null: false
84
+ end
85
+ end
86
+
87
+ class Disc < ActiveRecord::Base
88
+ enum color: [:red, :green, :blue]
89
+ enum shape: { triangle: 3, rectangle: 4 }
90
+ end
91
+
92
+ red_triangle = Disc.create!(color: Disc.colors[:red], shape: Disc.shapes[:triangle])
93
+ green_triangle = Disc.create!(color: Disc.colors[:green], shape: Disc.shapes[:triangle])
94
+ green_rectangle = Disc.create!(color: Disc.colors[:green], shape: Disc.shapes[:rectangle])
95
+ blue_rectangle = Disc.create!(color: Disc.colors[:blue], shape: Disc.shapes[:rectangle])
96
+
97
+ # A condition with a dual filter.
98
+ @ability.can :read, Disc, color: Disc.colors[:green], shape: Disc.shapes[:rectangle]
99
+
100
+ expect(@ability.cannot? :read, red_triangle).to be true
101
+ expect(@ability.cannot? :read, green_triangle).to be true
102
+ expect(@ability.can? :read, green_rectangle).to be true
103
+ expect(@ability.cannot? :read, blue_rectangle).to be true
104
+
105
+ accessible = Disc.accessible_by(@ability)
106
+ expect(accessible).to contain_exactly(green_rectangle)
107
+ end
108
+ end
40
109
  end
41
110
 
42
111
  if Gem::Specification.find_all_by_name('pg').any?
@@ -380,5 +380,26 @@ if defined? CanCan::ModelAdapters::ActiveRecordAdapter
380
380
  expect(Namespace::TableX.accessible_by(ability)).to eq([table_x])
381
381
  end
382
382
  end
383
+
384
+ context 'when conditions are non iterable ranges' do
385
+ before :each do
386
+ ActiveRecord::Schema.define do
387
+ create_table( :courses ) do |t|
388
+ t.datetime :start_at
389
+ end
390
+ end
391
+
392
+ class Course < ActiveRecord::Base
393
+ end
394
+ end
395
+
396
+ it 'fetches only the valid records' do
397
+ @ability.can :read, Course, :start_at => 1.day.ago..1.day.from_now
398
+ Course.create!(:start_at => 10.days.ago)
399
+ valid_course = Course.create!(:start_at => Time.now)
400
+
401
+ expect(Course.accessible_by(@ability)).to eq([valid_course])
402
+ end
403
+ end
383
404
  end
384
405
  end
@@ -12,6 +12,13 @@ if defined? CanCan::ModelAdapters::MongoidAdapter
12
12
  include Mongoid::Document
13
13
 
14
14
  referenced_in :mongoid_category
15
+ references_many :mongoid_sub_projects
16
+ end
17
+
18
+ class MongoidSubProject
19
+ include Mongoid::Document
20
+
21
+ referenced_in :mongoid_project
15
22
  end
16
23
 
17
24
  Mongoid.configure do |config|
@@ -222,6 +229,19 @@ if defined? CanCan::ModelAdapters::MongoidAdapter
222
229
  MongoidProject.accessible_by(@ability)
223
230
  }.to raise_error(CanCan::Error)
224
231
  end
232
+
233
+ it "can handle nested queries for accessible_by" do
234
+ @ability.can :read, MongoidSubProject, {:mongoid_project => {:mongoid_category => { :name => 'Authorization'}}}
235
+ cat1 = MongoidCategory.create name: 'Authentication'
236
+ cat2 = MongoidCategory.create name: 'Authorization'
237
+ proj1 = cat1.mongoid_projects.create name: 'Proj1'
238
+ proj2 = cat2.mongoid_projects.create name: 'Proj2'
239
+ sub1 = proj1.mongoid_sub_projects.create name: 'Sub1'
240
+ sub2 = proj2.mongoid_sub_projects.create name: 'Sub2'
241
+ expect(MongoidSubProject.accessible_by(@ability)).to match_array([sub1])
242
+ end
243
+
244
+
225
245
  end
226
246
  end
227
247
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cancancan
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.13.1
4
+ version: 1.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryan Rite
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2015-10-08 00:00:00.000000000 Z
13
+ date: 2016-05-14 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: bundler
@@ -151,7 +151,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
151
151
  version: '0'
152
152
  requirements: []
153
153
  rubyforge_project:
154
- rubygems_version: 2.2.3
154
+ rubygems_version: 2.5.1
155
155
  signing_key:
156
156
  specification_version: 4
157
157
  summary: Simple authorization solution for Rails.