From c81cf44861e4f849af2fff92b9b9a17dac25796d Mon Sep 17 00:00:00 2001 From: Tim Raymond Date: Fri, 1 May 2015 14:48:35 -0400 Subject: [PATCH 01/30] Reduce unnecessary allocations in Rule creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pattern of doing `[var].flatten` to coerce values to Arrays makes three Array allocations every time. The first is for the array enclosing var, the other two are in the C implementation of `flatten` to perform a depth-first traversal over any nested arrays found within the outer-most array[1]. Rules are allocated a *lot* during the typical execution of an application using CanCanCan for authorization. Since the parameters to Ability#can are expected to not be deeply nested, we can cut out the extra allocations by using Kernel#Array. This benchmark: [Array#flatten] Mem diff: 18.40625 MB [Kernel#Array] Mem diff: 1.01953125 MB Shows a savings of 17MB of allocations when instantiating 10,000 Rules[2]. This translates to a 2x speed improvement in Rule instantiation time: Calculating ------------------------------------- Array#flatten 16.147k i/100ms Kernel#Array 34.565k i/100ms ------------------------------------------------- Array#flatten 218.381k (± 9.7%) i/s - 1.098M Kernel#Array 460.892k (±30.7%) i/s - 2.108M Comparison: Kernel#Array: 460892.2 i/s Array#flatten: 218380.6 i/s - 2.11x slower [1] The first array used by flatten is a stack to push sub-arrays onto while traversing, the second is for the result array. [2] Memory figures are RSS, which doesn't consider shared memory, but we have none for this simple benchmark. GC was disabled during the benchmark eliminate the effects of unpredictable GC activity. Most of the intermediary arrays are immediately garbage, but the act of allocating them increases the work the garbage collector has to do. Benchmark/ips was used with GC enabled in the second benchmark. Benchmark script used is available here: https://gist.github.com/timraymond/8d7014e0c7804f0fe508 --- lib/cancan/rule.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cancan/rule.rb b/lib/cancan/rule.rb index 4f31ccf9..7c413f97 100644 --- a/lib/cancan/rule.rb +++ b/lib/cancan/rule.rb @@ -14,8 +14,8 @@ def initialize(base_behavior, action, subject, conditions, block) raise Error, "You are not able to supply a block with a hash of conditions in #{action} #{subject} ability. Use either one." if conditions.kind_of?(Hash) && !block.nil? @match_all = action.nil? && subject.nil? @base_behavior = base_behavior - @actions = [action].flatten - @subjects = [subject].flatten + @actions = Array(action) + @subjects = Array(subject) @conditions = conditions || {} @block = block end From 20eeb4c69d752821c8cd6b815399549d52c3209b Mon Sep 17 00:00:00 2001 From: Oliver Klee Date: Wed, 13 Jul 2016 22:24:43 +0200 Subject: [PATCH 02/30] Drop support for Rails < 4.2 According to http://guides.rubyonrails.org/maintenance_policy.html , Rails 3.2, 4.0 and 4.1 have reached their end of life (as Rails 5.0 has been released). So we stop supporting it, allowing for faster builds on Travis, code removal and simpler code. Fixes #329 --- .travis.yml | 3 - Appraisals | 45 -------- CHANGELOG.rdoc | 2 + README.md | 6 +- gemfiles/activerecord_3.2.gemfile | 17 --- gemfiles/activerecord_4.0.gemfile | 18 ---- gemfiles/activerecord_4.1.gemfile | 18 ---- lib/cancan.rb | 9 +- .../model_adapters/active_record_3_adapter.rb | 16 --- .../model_adapters/active_record_4_adapter.rb | 6 +- .../active_record_4_adapter_spec.rb | 100 +++++++++--------- 11 files changed, 58 insertions(+), 182 deletions(-) delete mode 100644 gemfiles/activerecord_3.2.gemfile delete mode 100644 gemfiles/activerecord_4.0.gemfile delete mode 100644 gemfiles/activerecord_4.1.gemfile delete mode 100644 lib/cancan/model_adapters/active_record_3_adapter.rb diff --git a/.travis.yml b/.travis.yml index f90b5852..ff5eb7a7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,9 +8,6 @@ rvm: - 2.2.2 - jruby-9.0.5.0 gemfile: - - gemfiles/activerecord_3.2.gemfile - - gemfiles/activerecord_4.0.gemfile - - gemfiles/activerecord_4.1.gemfile - gemfiles/activerecord_4.2.gemfile - gemfiles/activerecord_5.0.gemfile - gemfiles/mongoid_2.x.gemfile diff --git a/Appraisals b/Appraisals index a9d9f5d3..7ce0f0d0 100644 --- a/Appraisals +++ b/Appraisals @@ -1,48 +1,3 @@ -appraise "activerecord_3.2" do - gem "activerecord", "~> 3.2.0", :require => "active_record" - gem "actionpack", "~> 3.2.0", :require => "action_pack" - - gemfile.platforms :jruby do - gem "activerecord-jdbcsqlite3-adapter" - gem "jdbc-sqlite3" - end - - gemfile.platforms :ruby, :mswin, :mingw do - gem "sqlite3" - end -end - -appraise "activerecord_4.0" do - gem "activerecord", "~> 4.0.5", :require => "active_record" - gem "activesupport", "~> 4.0.5", :require => "active_support/all" - gem "actionpack", "~> 4.0.5", :require => "action_pack" - - - gemfile.platforms :jruby do - gem "activerecord-jdbcsqlite3-adapter" - gem "jdbc-sqlite3" - end - - gemfile.platforms :ruby, :mswin, :mingw do - gem "sqlite3" - end -end - -appraise "activerecord_4.1" do - gem "activerecord", "~> 4.1.1", :require => "active_record" - gem "activesupport", "~> 4.1.1", :require => "active_support/all" - gem "actionpack", "~> 4.1.1", :require => "action_pack" - - gemfile.platforms :jruby do - gem "activerecord-jdbcsqlite3-adapter" - gem "jdbc-sqlite3" - end - - gemfile.platforms :ruby, :mswin, :mingw do - gem "sqlite3" - end -end - appraise "activerecord_4.2" do gem "activerecord", "~> 4.2.0", :require => "active_record" gem 'activesupport', '~> 4.2.0', :require => 'active_support/all' diff --git a/CHANGELOG.rdoc b/CHANGELOG.rdoc index e7f8e964..c46b1663 100644 --- a/CHANGELOG.rdoc +++ b/CHANGELOG.rdoc @@ -2,6 +2,8 @@ Develop Unreleased +* Drop support for Rails < 4.2 (oliverklee) + 1.15.0 (June 13th, 2016) * Add support for Rails 5 (craig1410) diff --git a/README.md b/README.md index f4c5f478..962939c4 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Any help is greatly appreciated, feel free to submit pull-requests or open issue ## Installation -In **Rails 3 and 4**, add this to your Gemfile and run the `bundle install` command. +Add this to your Gemfile and run the `bundle install` command. gem 'cancancan', '~> 1.10' @@ -40,7 +40,7 @@ end ### 1. Define Abilities -User permissions are defined in an `Ability` class. CanCan 1.5 includes a Rails 3 and 4 generator for creating this class. +User permissions are defined in an `Ability` class. CanCan 1.5 includes a Rails 4.2 and 5 generator for creating this class. rails g cancan:ability @@ -85,7 +85,7 @@ See [Authorizing Controller Actions](https://github.com/CanCanCommunity/cancanca #### Strong Parameters -When using `strong_parameters` or Rails 4+, you have to sanitize inputs before saving the record, in actions such as `:create` and `:update`. +You have to sanitize inputs before saving the record, in actions such as `:create` and `:update`. For the `:update` action, CanCan will load and authorize the resource but *not* change it automatically, so the typical usage would be something like: diff --git a/gemfiles/activerecord_3.2.gemfile b/gemfiles/activerecord_3.2.gemfile deleted file mode 100644 index 20f06238..00000000 --- a/gemfiles/activerecord_3.2.gemfile +++ /dev/null @@ -1,17 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "activerecord", "~> 3.2.0", :require => "active_record" -gem "actionpack", "~> 3.2.0", :require => "action_pack" - -platforms :jruby do - gem "activerecord-jdbcsqlite3-adapter" - gem "jdbc-sqlite3" -end - -platforms :ruby, :mswin, :mingw do - gem "sqlite3" -end - -gemspec :path => "../" diff --git a/gemfiles/activerecord_4.0.gemfile b/gemfiles/activerecord_4.0.gemfile deleted file mode 100644 index 1803439c..00000000 --- a/gemfiles/activerecord_4.0.gemfile +++ /dev/null @@ -1,18 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "activerecord", "~> 4.0.5", :require => "active_record" -gem "activesupport", "~> 4.0.5", :require => "active_support/all" -gem "actionpack", "~> 4.0.5", :require => "action_pack" - -platforms :jruby do - gem "activerecord-jdbcsqlite3-adapter" - gem "jdbc-sqlite3" -end - -platforms :ruby, :mswin, :mingw do - gem "sqlite3" -end - -gemspec :path => "../" diff --git a/gemfiles/activerecord_4.1.gemfile b/gemfiles/activerecord_4.1.gemfile deleted file mode 100644 index 4e3a05b4..00000000 --- a/gemfiles/activerecord_4.1.gemfile +++ /dev/null @@ -1,18 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "activerecord", "~> 4.1.1", :require => "active_record" -gem "activesupport", "~> 4.1.1", :require => "active_support/all" -gem "actionpack", "~> 4.1.1", :require => "action_pack" - -platforms :jruby do - gem "activerecord-jdbcsqlite3-adapter" - gem "jdbc-sqlite3" -end - -platforms :ruby, :mswin, :mingw do - gem "sqlite3" -end - -gemspec :path => "../" diff --git a/lib/cancan.rb b/lib/cancan.rb index bcaa3ce8..af260ce4 100644 --- a/lib/cancan.rb +++ b/lib/cancan.rb @@ -1,4 +1,4 @@ -require "cancan/version" +require 'cancan/version' require 'cancan/ability' require 'cancan/rule' require 'cancan/controller_resource' @@ -12,12 +12,7 @@ if defined? ActiveRecord require 'cancan/model_adapters/active_record_adapter' - if ActiveRecord.respond_to?(:version) && - ActiveRecord.version >= Gem::Version.new("4") - require 'cancan/model_adapters/active_record_4_adapter' - else - require 'cancan/model_adapters/active_record_3_adapter' - end + require 'cancan/model_adapters/active_record_4_adapter' end require 'cancan/model_adapters/mongoid_adapter' if defined?(Mongoid) && defined?(Mongoid::Document) diff --git a/lib/cancan/model_adapters/active_record_3_adapter.rb b/lib/cancan/model_adapters/active_record_3_adapter.rb deleted file mode 100644 index 5527bb1c..00000000 --- a/lib/cancan/model_adapters/active_record_3_adapter.rb +++ /dev/null @@ -1,16 +0,0 @@ -module CanCan - module ModelAdapters - class ActiveRecord3Adapter < AbstractAdapter - include ActiveRecordAdapter - def self.for_class?(model_class) - model_class <= ActiveRecord::Base - end - - private - - def build_relation(*where_conditions) - @model_class.where(*where_conditions).includes(joins) - end - end - end -end diff --git a/lib/cancan/model_adapters/active_record_4_adapter.rb b/lib/cancan/model_adapters/active_record_4_adapter.rb index d00b6ec1..7d5ea8e9 100644 --- a/lib/cancan/model_adapters/active_record_4_adapter.rb +++ b/lib/cancan/model_adapters/active_record_4_adapter.rb @@ -19,9 +19,7 @@ def build_relation(*where_conditions) end def self.override_condition_matching?(subject, name, value) - # ActiveRecord introduced enums in version 4.1. - (ActiveRecord::VERSION::MAJOR > 4 || ActiveRecord::VERSION::MINOR >= 1) && - subject.class.defined_enums.include?(name.to_s) + subject.class.defined_enums.include?(name.to_s) end def self.matches_condition?(subject, name, value) @@ -30,7 +28,7 @@ def self.matches_condition?(subject, name, value) # Get the value of the attribute as an integer. attribute = enum[subject.send(name)] # Check to see if the value matches the condition. - value.is_a?(Enumerable) ? + value.is_a?(Enumerable) ? (value.include? attribute) : attribute == value end diff --git a/spec/cancan/model_adapters/active_record_4_adapter_spec.rb b/spec/cancan/model_adapters/active_record_4_adapter_spec.rb index b50b2a0f..9881c821 100644 --- a/spec/cancan/model_adapters/active_record_4_adapter_spec.rb +++ b/spec/cancan/model_adapters/active_record_4_adapter_spec.rb @@ -38,73 +38,71 @@ class Child < ActiveRecord::Base expect(Parent.accessible_by(@ability).order(:created_at => :asc).includes(:children).first.children).to eq [child2, child1] end - if ActiveRecord::VERSION::MINOR >= 1 - it "allows filters on enums" do - ActiveRecord::Schema.define do - create_table(:shapes) do |t| - t.integer :color, default: 0, null: false - end + it "allows filters on enums" do + ActiveRecord::Schema.define do + create_table(:shapes) do |t| + t.integer :color, default: 0, null: false end + end - class Shape < ActiveRecord::Base - enum color: [:red, :green, :blue] - end + class Shape < ActiveRecord::Base + enum color: [:red, :green, :blue] + end - red = Shape.create!(color: :red) - green = Shape.create!(color: :green) - blue = Shape.create!(color: :blue) + red = Shape.create!(color: :red) + green = Shape.create!(color: :green) + blue = Shape.create!(color: :blue) - # A condition with a single value. - @ability.can :read, Shape, color: Shape.colors[:green] + # A condition with a single value. + @ability.can :read, Shape, color: Shape.colors[:green] - expect(@ability.cannot? :read, red).to be true - expect(@ability.can? :read, green).to be true - expect(@ability.cannot? :read, blue).to be true + expect(@ability.cannot? :read, red).to be true + expect(@ability.can? :read, green).to be true + expect(@ability.cannot? :read, blue).to be true - accessible = Shape.accessible_by(@ability) - expect(accessible).to contain_exactly(green) + accessible = Shape.accessible_by(@ability) + expect(accessible).to contain_exactly(green) - # A condition with multiple values. - @ability.can :update, Shape, color: [Shape.colors[:red], - Shape.colors[:blue]] + # A condition with multiple values. + @ability.can :update, Shape, color: [Shape.colors[:red], + Shape.colors[:blue]] - expect(@ability.can? :update, red).to be true - expect(@ability.cannot? :update, green).to be true - expect(@ability.can? :update, blue).to be true + expect(@ability.can? :update, red).to be true + expect(@ability.cannot? :update, green).to be true + expect(@ability.can? :update, blue).to be true - accessible = Shape.accessible_by(@ability, :update) - expect(accessible).to contain_exactly(red, blue) - end + accessible = Shape.accessible_by(@ability, :update) + expect(accessible).to contain_exactly(red, blue) + end - it "allows dual filter on enums" do - ActiveRecord::Schema.define do - create_table(:discs) do |t| - t.integer :color, default: 0, null: false - t.integer :shape, default: 3, null: false - end + it "allows dual filter on enums" do + ActiveRecord::Schema.define do + create_table(:discs) do |t| + t.integer :color, default: 0, null: false + t.integer :shape, default: 3, null: false end + end - class Disc < ActiveRecord::Base - enum color: [:red, :green, :blue] - enum shape: { triangle: 3, rectangle: 4 } - end + class Disc < ActiveRecord::Base + enum color: [:red, :green, :blue] + enum shape: { triangle: 3, rectangle: 4 } + end - red_triangle = Disc.create!(color: Disc.colors[:red], shape: Disc.shapes[:triangle]) - green_triangle = Disc.create!(color: Disc.colors[:green], shape: Disc.shapes[:triangle]) - green_rectangle = Disc.create!(color: Disc.colors[:green], shape: Disc.shapes[:rectangle]) - blue_rectangle = Disc.create!(color: Disc.colors[:blue], shape: Disc.shapes[:rectangle]) + red_triangle = Disc.create!(color: Disc.colors[:red], shape: Disc.shapes[:triangle]) + green_triangle = Disc.create!(color: Disc.colors[:green], shape: Disc.shapes[:triangle]) + green_rectangle = Disc.create!(color: Disc.colors[:green], shape: Disc.shapes[:rectangle]) + blue_rectangle = Disc.create!(color: Disc.colors[:blue], shape: Disc.shapes[:rectangle]) - # A condition with a dual filter. - @ability.can :read, Disc, color: Disc.colors[:green], shape: Disc.shapes[:rectangle] + # A condition with a dual filter. + @ability.can :read, Disc, color: Disc.colors[:green], shape: Disc.shapes[:rectangle] - expect(@ability.cannot? :read, red_triangle).to be true - expect(@ability.cannot? :read, green_triangle).to be true - expect(@ability.can? :read, green_rectangle).to be true - expect(@ability.cannot? :read, blue_rectangle).to be true + expect(@ability.cannot? :read, red_triangle).to be true + expect(@ability.cannot? :read, green_triangle).to be true + expect(@ability.can? :read, green_rectangle).to be true + expect(@ability.cannot? :read, blue_rectangle).to be true - accessible = Disc.accessible_by(@ability) - expect(accessible).to contain_exactly(green_rectangle) - end + accessible = Disc.accessible_by(@ability) + expect(accessible).to contain_exactly(green_rectangle) end end From 8fffa56d741f181f6b9a71e82d12a13ebfd4a30a Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Sun, 11 Dec 2016 14:45:47 +0100 Subject: [PATCH 03/30] enable activerecord 5 tests --- .travis.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index f90b5852..f3336026 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,15 +19,6 @@ services: - mongodb matrix: fast_finish: true - exclude: - - rvm: 2.0.0 - gemfile: gemfiles/activerecord_5.0.gemfile - - rvm: 2.1.0 - gemfile: gemfiles/activerecord_5.0.gemfile - - rvm: 2.2.0 - gemfile: gemfiles/activerecord_5.0.gemfile - - rvm: jruby-9.0.5.0 - gemfile: gemfiles/activerecord_5.0.gemfile notifications: recipients: - bryan@bryanrite.com From df7f30e1ce85556a2660d63873514f2930215651 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Sun, 11 Dec 2016 15:07:32 +0100 Subject: [PATCH 04/30] activerecord 5 requires ruby >= 2.3 --- .travis.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.travis.yml b/.travis.yml index f3336026..f90b5852 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,15 @@ services: - mongodb matrix: fast_finish: true + exclude: + - rvm: 2.0.0 + gemfile: gemfiles/activerecord_5.0.gemfile + - rvm: 2.1.0 + gemfile: gemfiles/activerecord_5.0.gemfile + - rvm: 2.2.0 + gemfile: gemfiles/activerecord_5.0.gemfile + - rvm: jruby-9.0.5.0 + gemfile: gemfiles/activerecord_5.0.gemfile notifications: recipients: - bryan@bryanrite.com From f7be93172a314b27ea3d94c4882d0938dfebd8a0 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Sun, 11 Dec 2016 16:00:02 +0100 Subject: [PATCH 05/30] drop support for InheritedResource --- CHANGELOG.rdoc | 2 + lib/cancan.rb | 1 - lib/cancan/controller_additions.rb | 6 +- lib/cancan/inherited_resource.rb | 20 ------- spec/cancan/controller_additions_spec.rb | 5 -- spec/cancan/inherited_resource_spec.rb | 71 ------------------------ 6 files changed, 3 insertions(+), 102 deletions(-) delete mode 100644 lib/cancan/inherited_resource.rb delete mode 100644 spec/cancan/inherited_resource_spec.rb diff --git a/CHANGELOG.rdoc b/CHANGELOG.rdoc index e7f8e964..2db0c23b 100644 --- a/CHANGELOG.rdoc +++ b/CHANGELOG.rdoc @@ -2,6 +2,8 @@ Develop Unreleased + * Drop support for InheritedResource (coorasse) + 1.15.0 (June 13th, 2016) * Add support for Rails 5 (craig1410) diff --git a/lib/cancan.rb b/lib/cancan.rb index bcaa3ce8..e2532544 100644 --- a/lib/cancan.rb +++ b/lib/cancan.rb @@ -5,7 +5,6 @@ require 'cancan/controller_additions' require 'cancan/model_additions' require 'cancan/exceptions' -require 'cancan/inherited_resource' require 'cancan/model_adapters/abstract_adapter' require 'cancan/model_adapters/default_adapter' diff --git a/lib/cancan/controller_additions.rb b/lib/cancan/controller_additions.rb index f3e0c2a8..f8bee16a 100644 --- a/lib/cancan/controller_additions.rb +++ b/lib/cancan/controller_additions.rb @@ -284,11 +284,7 @@ def skip_authorization(*args) end def cancan_resource_class - if ancestors.map(&:to_s).include? "InheritedResources::Actions" - InheritedResource - else - ControllerResource - end + ControllerResource end def cancan_skipper diff --git a/lib/cancan/inherited_resource.rb b/lib/cancan/inherited_resource.rb deleted file mode 100644 index 61bd3331..00000000 --- a/lib/cancan/inherited_resource.rb +++ /dev/null @@ -1,20 +0,0 @@ -module CanCan - # For use with Inherited Resources - class InheritedResource < ControllerResource # :nodoc: - def load_resource_instance - if parent? - @controller.send :association_chain - @controller.instance_variable_get("@#{instance_name}") - elsif new_actions.include? @params[:action].to_sym - resource = @controller.send :build_resource - assign_attributes(resource) - else - @controller.send :resource - end - end - - def resource_base - @controller.send :end_of_association_chain - end - end -end diff --git a/spec/cancan/controller_additions_spec.rb b/spec/cancan/controller_additions_spec.rb index 26223e15..f4c267d0 100644 --- a/spec/cancan/controller_additions_spec.rb +++ b/spec/cancan/controller_additions_spec.rb @@ -104,11 +104,6 @@ expect(@controller.class.cancan_resource_class).to eq(CanCan::ControllerResource) end - it "cancan_resource_class is InheritedResource when class includes InheritedResources::Actions" do - allow(@controller.class).to receive(:ancestors) { ["InheritedResources::Actions"] } - expect(@controller.class.cancan_resource_class).to eq(CanCan::InheritedResource) - end - it "cancan_skipper is an empty hash with :authorize and :load options and remember changes" do expect(@controller_class.cancan_skipper).to eq({:authorize => {}, :load => {}}) @controller_class.cancan_skipper[:load] = true diff --git a/spec/cancan/inherited_resource_spec.rb b/spec/cancan/inherited_resource_spec.rb deleted file mode 100644 index 5e74a9ac..00000000 --- a/spec/cancan/inherited_resource_spec.rb +++ /dev/null @@ -1,71 +0,0 @@ -require "spec_helper" - -describe CanCan::InheritedResource do - let(:ability) { Ability.new(nil) } - let(:params) { HashWithIndifferentAccess.new(:controller => "models") } - let(:controller_class) { Class.new } - let(:controller) { controller_class.new } - - before(:each) do - class Model - attr_accessor :name - - def initialize(attributes={}) - attributes.each do |attribute, value| - send("#{attribute}=", value) - end - end - end - - allow(controller).to receive(:params) { params } - allow(controller).to receive(:current_ability) { ability } - allow(controller_class).to receive(:cancan_skipper) { {:authorize => {}, :load => {}} } - end - - it "show loads resource through controller.resource" do - params.merge!(:action => "show", :id => 123) - allow(controller).to receive(:resource) { :model_resource } - CanCan::InheritedResource.new(controller).load_resource - expect(controller.instance_variable_get(:@model)).to eq(:model_resource) - end - - it "new loads through controller.build_resource" do - params[:action] = "new" - allow(controller).to receive(:build_resource) { :model_resource } - CanCan::InheritedResource.new(controller).load_resource - expect(controller.instance_variable_get(:@model)).to eq(:model_resource) - end - - it "index loads through controller.association_chain when parent" do - params[:action] = "index" - allow(controller).to receive(:association_chain) { controller.instance_variable_set(:@model, :model_resource) } - CanCan::InheritedResource.new(controller, :parent => true).load_resource - expect(controller.instance_variable_get(:@model)).to eq(:model_resource) - end - - it "index loads through controller.end_of_association_chain" do - params[:action] = "index" - allow(Model).to receive(:accessible_by).with(ability, :index) { :projects } - allow(controller).to receive(:end_of_association_chain) { Model } - CanCan::InheritedResource.new(controller).load_resource - expect(controller.instance_variable_get(:@models)).to eq(:projects) - end - - it "builds a new resource with attributes from current ability" do - params[:action] = "new" - ability.can(:create, Model, :name => "from conditions") - allow(controller).to receive(:build_resource) { Struct.new(:name).new } - resource = CanCan::InheritedResource.new(controller) - resource.load_resource - expect(controller.instance_variable_get(:@model).name).to eq("from conditions") - end - - it "overrides initial attributes with params" do - params.merge!(:action => "new", :model => {:name => "from params"}) - ability.can(:create, Model, :name => "from conditions") - allow(controller).to receive(:build_resource) { Struct.new(:name).new } - resource = CanCan::ControllerResource.new(controller) - resource.load_resource - expect(controller.instance_variable_get(:@model).name).to eq("from params") - end -end From ac1841ff1dfb9106eef9bc3de280c41653c04a11 Mon Sep 17 00:00:00 2001 From: gingray Date: Thu, 24 Nov 2016 14:16:34 +0300 Subject: [PATCH 06/30] add option to pass array of abilities to rspec matcher --- .rubocop_todo.yml | 3 ++ CHANGELOG.rdoc | 4 +++ lib/cancan/matchers.rb | 8 ++++- spec/cancan/matchers_spec.rb | 67 +++++++++++++++++++++++++----------- 4 files changed, 61 insertions(+), 21 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index f87c5f0a..563447d1 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -70,6 +70,9 @@ Metrics/AbcSize: # Offense count: 1 # Configuration parameters: CountComments. Metrics/BlockLength: + Exclude: + - 'lib/cancan/matchers.rb' + - '**/*_spec.rb' Max: 28 # Offense count: 2 diff --git a/CHANGELOG.rdoc b/CHANGELOG.rdoc index e7f8e964..3450d8a1 100644 --- a/CHANGELOG.rdoc +++ b/CHANGELOG.rdoc @@ -2,6 +2,10 @@ Develop Unreleased +1.15.1 (Dec 17th, 2016) + +* Add ability to rspec matcher to take array of abilities (gingray) + 1.15.0 (June 13th, 2016) * Add support for Rails 5 (craig1410) diff --git a/lib/cancan/matchers.rb b/lib/cancan/matchers.rb index 717ccb1c..1d26494b 100644 --- a/lib/cancan/matchers.rb +++ b/lib/cancan/matchers.rb @@ -9,7 +9,13 @@ Kernel.const_get(rspec_module)::Matchers.define :be_able_to do |*args| match do |ability| - ability.can?(*args) + actions = args.first + if actions.is_a? Array + break false if actions.empty? + actions.all? { |action| ability.can?(action, *args[1..-1]) } + else + ability.can?(*args) + end end # Check that RSpec is < 2.99 diff --git a/spec/cancan/matchers_spec.rb b/spec/cancan/matchers_spec.rb index 0c33ace4..1927d9d2 100644 --- a/spec/cancan/matchers_spec.rb +++ b/spec/cancan/matchers_spec.rb @@ -1,29 +1,56 @@ require 'spec_helper' describe 'be_able_to' do - it 'delegates to can?' do - expect(object = double).to receive(:can?).with(:read, 123) { true } - expect(object).to be_able_to(:read, 123) - end + subject { double } - it 'reports a nice failure message for should' do - expect(object = double).to receive(:can?).with(:read, 123) { false } - expect { - expect(object).to be_able_to(:read, 123) - }.to raise_error('expected to be able to :read 123') - end + context 'check single ability' do + it 'delegates to can?' do + is_expected.to receive(:can?).with(:read, 123) { true } + is_expected.to be_able_to(:read, 123) + end + + it 'reports a nice failure message for should' do + is_expected.to receive(:can?).with(:read, 123) { false } + expect { + is_expected.to be_able_to(:read, 123) + }.to raise_error('expected to be able to :read 123') + end + + it 'reports a nice failure message for should not' do + is_expected.to receive(:can?).with(:read, 123) { true } + expect { + is_expected.to_not be_able_to(:read, 123) + }.to raise_error('expected not to be able to :read 123') + end - it 'reports a nice failure message for should not' do - expect(object = double).to receive(:can?).with(:read, 123) { true } - expect { - expect(object).to_not be_able_to(:read, 123) - }.to raise_error('expected not to be able to :read 123') + it 'delegates additional arguments to can? and reports in failure message' do + is_expected.to receive(:can?).with(:read, 123, 456) { false } + expect { + is_expected.to be_able_to(:read, 123, 456) + }.to raise_error('expected to be able to :read 123 456') + end end - it 'delegates additional arguments to can? and reports in failure message' do - expect(object = double).to receive(:can?).with(:read, 123, 456) { false } - expect { - expect(object).to be_able_to(:read, 123, 456) - }.to raise_error('expected to be able to :read 123 456') + context 'check array of abilities' do + it 'delegates to can? with array of abilities with one action' do + is_expected.to receive(:can?).with(:read, 123) { true } + is_expected.to be_able_to([:read], 123) + end + + it 'delegates to can? with array of abilities with multiple actions' do + is_expected.to receive(:can?).with(:read, 123) { true } + is_expected.to receive(:can?).with(:update, 123) { true } + is_expected.to be_able_to([:read, :update], 123) + end + + it 'delegates to can? with array of abilities with empty array' do + is_expected.not_to be_able_to([], 123) + end + + it 'delegates to can? with array of abilities with only one eligable ability' do + is_expected.to receive(:can?).with(:read, 123) { true } + is_expected.to receive(:can?).with(:update, 123) { false } + is_expected.not_to be_able_to([:read, :update], 123) + end end end From f4b1b3f775d68b8ee8a46f86414d75c404ad82c7 Mon Sep 17 00:00:00 2001 From: Kuldeep Aggarwal Date: Sun, 26 Mar 2017 15:18:13 +0200 Subject: [PATCH 07/30] use `ActiveSupport.on_load` hook to include `CanCan::ModelAdditions` in AR::Base (#292) --- lib/cancan/model_adapters/active_record_adapter.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/cancan/model_adapters/active_record_adapter.rb b/lib/cancan/model_adapters/active_record_adapter.rb index adb9c313..2326a108 100644 --- a/lib/cancan/model_adapters/active_record_adapter.rb +++ b/lib/cancan/model_adapters/active_record_adapter.rb @@ -143,6 +143,6 @@ def clean_joins(joins_hash) end end -ActiveRecord::Base.class_eval do - include CanCan::ModelAdditions +ActiveSupport.on_load(:active_record) do + self.send :include, CanCan::ModelAdditions end From a1dd4ea1bf7e7235db082358bd05a2b9a60dd12a Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Mon, 27 Mar 2017 08:26:43 +0200 Subject: [PATCH 08/30] update rubocop --- .rubocop.yml | 1 + .rubocop_todo.yml | 6 ++++++ cancancan.gemspec | 1 + 3 files changed, 8 insertions(+) diff --git a/.rubocop.yml b/.rubocop.yml index f6f797d5..6f41b74b 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -34,5 +34,6 @@ AllCops: TargetRubyVersion: 2.0 Exclude: - 'gemfiles/vendor/bundle/**/*' + - 'Appraisals' inherit_from: .rubocop_todo.yml diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 6c1303f1..f884b536 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -46,3 +46,9 @@ Style/PredicateName: Exclude: - 'spec/**/*' - 'lib/cancan/ability.rb' + +Style/SymbolArray: + Enabled: false + +Style/InverseMethods: + Enabled: false diff --git a/cancancan.gemspec b/cancancan.gemspec index 58b7f58f..20e0cae3 100644 --- a/cancancan.gemspec +++ b/cancancan.gemspec @@ -1,4 +1,5 @@ # coding: utf-8 + lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'cancan/version' From 4bcaaa1a92864d299374948c90d0b72a36b40999 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Thu, 6 Apr 2017 08:25:39 +0200 Subject: [PATCH 09/30] fix rubocop issues --- .rubocop_todo.yml | 6 ++ Appraisals | 58 +++++++++---------- Rakefile | 2 +- cancancan.gemspec | 1 + lib/cancan/ability.rb | 2 +- lib/cancan/controller_resource.rb | 4 +- .../model_adapters/active_record_adapter.rb | 2 +- spec/cancan/ability_spec.rb | 10 ++-- spec/cancan/controller_additions_spec.rb | 26 ++++----- spec/cancan/controller_resource_spec.rb | 8 +-- spec/cancan/matchers_spec.rb | 4 +- .../active_record_4_adapter_spec.rb | 4 +- .../active_record_adapter_spec.rb | 2 +- .../model_adapters/mongoid_adapter_spec.rb | 4 +- 14 files changed, 70 insertions(+), 63 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 3ea88630..b46e4d42 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -84,3 +84,9 @@ Style/SpaceInsideHashLiteralBraces: Style/TrailingWhitespace: Exclude: - 'lib/cancan.rb' + + +# has a bug +Style/FileName: + Exclude: + - 'Appraisals' diff --git a/Appraisals b/Appraisals index 2019ea6a..d7940b85 100644 --- a/Appraisals +++ b/Appraisals @@ -1,60 +1,60 @@ -appraise "activerecord_4.2" do - gem "activerecord", "~> 4.2.0", :require => "active_record" - gem 'activesupport', '~> 4.2.0', :require => 'active_support/all' - gem "actionpack", "~> 4.2.0", :require => "action_pack" - gem "nokogiri", "~> 1.6.8", :require => "nokogiri" # TODO: fix for ruby 2.0.0 +appraise 'activerecord_4.2' do + gem 'activerecord', '~> 4.2.0', require: 'active_record' + gem 'activesupport', '~> 4.2.0', require: 'active_support/all' + gem 'actionpack', '~> 4.2.0', require: 'action_pack' + gem 'nokogiri', '~> 1.6.8', require: 'nokogiri' # TODO: fix for ruby 2.0.0 gemfile.platforms :jruby do - gem "activerecord-jdbcsqlite3-adapter" - gem "jdbc-sqlite3" + gem 'activerecord-jdbcsqlite3-adapter' + gem 'jdbc-sqlite3' end gemfile.platforms :ruby, :mswin, :mingw do - gem "sqlite3" - gem "pg" + gem 'sqlite3' + gem 'pg' end end -appraise "activerecord_5.0" do - gem "activerecord", "~> 5.0.0.rc1", :require => "active_record" - gem 'activesupport', '~> 5.0.0.rc1', :require => 'active_support/all' - gem "actionpack", "~> 5.0.0.rc1", :require => "action_pack" +appraise 'activerecord_5.0' do + gem 'activerecord', '~> 5.0.0.rc1', require: 'active_record' + gem 'activesupport', '~> 5.0.0.rc1', require: 'active_support/all' + gem 'actionpack', '~> 5.0.0.rc1', require: 'action_pack' gemfile.platforms :jruby do - gem "activerecord-jdbcsqlite3-adapter" - gem "jdbc-sqlite3" + gem 'activerecord-jdbcsqlite3-adapter' + gem 'jdbc-sqlite3' end gemfile.platforms :ruby, :mswin, :mingw do - gem "sqlite3" - gem "pg" + gem 'sqlite3' + gem 'pg' end end -appraise "mongoid_2.x" do - gem "activesupport", "~> 3.0", :require => "active_support/all" - gem "actionpack", "~> 3.0", :require => "action_pack" - gem "mongoid", "~> 2.0.0" +appraise 'mongoid_2.x' do + gem 'activesupport', '~> 3.0', require: 'active_support/all' + gem 'actionpack', '~> 3.0', require: 'action_pack' + gem 'mongoid', '~> 2.0.0' gemfile.platforms :ruby, :mswin, :mingw do - gem "bson_ext", "~> 1.1" + gem 'bson_ext', '~> 1.1' end gemfile.platforms :jruby do - gem "mongo", "~> 1.9.2" + gem 'mongo', '~> 1.9.2' end end -appraise "sequel_3.x" do - gem "sequel", "~> 3.48.0" - gem "activesupport", "~> 3.0", :require => "active_support/all" - gem "actionpack", "~> 3.0", :require => "action_pack" +appraise 'sequel_3.x' do + gem 'sequel', '~> 3.48.0' + gem 'activesupport', '~> 3.0', require: 'active_support/all' + gem 'actionpack', '~> 3.0', require: 'action_pack' gemfile.platforms :jruby do - gem "jdbc-sqlite3" + gem 'jdbc-sqlite3' end gemfile.platforms :ruby, :mswin, :mingw do - gem "sqlite3" + gem 'sqlite3' end end diff --git a/Rakefile b/Rakefile index c9306b20..0a94a51f 100644 --- a/Rakefile +++ b/Rakefile @@ -10,4 +10,4 @@ RSpec::Core::RakeTask.new do |t| t.verbose = false end -task default: [:rubocop, :spec] +task default: %i(rubocop spec) diff --git a/cancancan.gemspec b/cancancan.gemspec index 58b7f58f..20e0cae3 100644 --- a/cancancan.gemspec +++ b/cancancan.gemspec @@ -1,4 +1,5 @@ # coding: utf-8 + lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'cancan/version' diff --git a/lib/cancan/ability.rb b/lib/cancan/ability.rb index a45719cf..33fdf2b1 100644 --- a/lib/cancan/ability.rb +++ b/lib/cancan/ability.rb @@ -414,7 +414,7 @@ def relevant_rules_for_query(action, subject) def default_alias_actions { - read: [:index, :show], + read: %i(index show), create: [:new], update: [:edit] } diff --git a/lib/cancan/controller_resource.rb b/lib/cancan/controller_resource.rb index 32f9e52d..1a254936 100644 --- a/lib/cancan/controller_resource.rb +++ b/lib/cancan/controller_resource.rb @@ -303,11 +303,11 @@ def collection_actions end def new_actions - [:new, :create] + Array(@options[:new]) + %i(new create) + Array(@options[:new]) end def save_actions - [:create, :update] + %i(create update) end private diff --git a/lib/cancan/model_adapters/active_record_adapter.rb b/lib/cancan/model_adapters/active_record_adapter.rb index cb47a86a..91d230b7 100644 --- a/lib/cancan/model_adapters/active_record_adapter.rb +++ b/lib/cancan/model_adapters/active_record_adapter.rb @@ -145,5 +145,5 @@ def clean_joins(joins_hash) end ActiveSupport.on_load(:active_record) do - self.send :include, CanCan::ModelAdditions + send :include, CanCan::ModelAdditions end diff --git a/spec/cancan/ability_spec.rb b/spec/cancan/ability_spec.rb index e9d5cb02..0240a192 100644 --- a/spec/cancan/ability_spec.rb +++ b/spec/cancan/ability_spec.rb @@ -158,7 +158,7 @@ end it 'is able to specify multiple actions and match any' do - @ability.can [:read, :update], :all + @ability.can %i(read update), :all expect(@ability.can?(:read, 123)).to be(true) expect(@ability.can?(:update, 123)).to be(true) expect(@ability.can?(:count, 123)).to be(false) @@ -201,8 +201,8 @@ expect(@ability.can?(:read, :stats)).to be(true) expect(@ability.can?(:update, :stats)).to be(false) expect(@ability.can?(:read, :nonstats)).to be(false) - expect(@ability.can?(:read, any: [:stats, :nonstats])).to be(true) - expect(@ability.can?(:read, any: [:nonstats, :neitherstats])).to be(false) + expect(@ability.can?(:read, any: %i(stats nonstats))).to be(true) + expect(@ability.can?(:read, any: %i(nonstats neitherstats))).to be(false) end it 'checks ancestors of class' do @@ -261,7 +261,7 @@ it 'appends aliased actions' do @ability.alias_action :update, to: :modify @ability.alias_action :destroy, to: :modify - expect(@ability.aliased_actions[:modify]).to eq([:update, :destroy]) + expect(@ability.aliased_actions[:modify]).to eq(%i(update destroy)) end it 'clears aliased actions' do @@ -400,7 +400,7 @@ class A it 'checks permissions correctly when passing a hash of subjects with multiple definitions' do @ability.can :read, Range, string: { length: 4 } - @ability.can [:create, :read], Range, string: { upcase: 'FOO' } + @ability.can %i(create read), Range, string: { upcase: 'FOO' } expect(@ability.can?(:read, 'foo' => Range)).to be(true) expect(@ability.can?(:read, 'foobar' => Range)).to be(false) diff --git a/spec/cancan/controller_additions_spec.rb b/spec/cancan/controller_additions_spec.rb index 26f5d4e6..88a7716c 100644 --- a/spec/cancan/controller_additions_spec.rb +++ b/spec/cancan/controller_additions_spec.rb @@ -65,10 +65,10 @@ expect(cancan_resource_class = double).to receive(:load_resource) allow(CanCan::ControllerResource).to receive(:new).with(@controller, nil, foo: :bar) { cancan_resource_class } expect(@controller_class) - .to receive(callback_action(:before_action)).with(only: [:show, :index], unless: false) do |_options, &block| + .to receive(callback_action(:before_action)).with(only: %i(show index), unless: false) do |_options, &block| block.call(@controller) end - @controller_class.load_resource foo: :bar, only: [:show, :index], unless: false + @controller_class.load_resource foo: :bar, only: %i(show index), unless: false end it 'skip_authorization_check setups a before filter which sets @_authorized to true' do @@ -124,27 +124,27 @@ end it 'skip_authorize_resource adds itself to the cancan skipper with given model name and options' do - @controller_class.skip_authorize_resource(:project, only: [:index, :show]) - expect(@controller_class.cancan_skipper[:authorize][:project]).to eq(only: [:index, :show]) - @controller_class.skip_authorize_resource(only: [:index, :show]) - expect(@controller_class.cancan_skipper[:authorize][nil]).to eq(only: [:index, :show]) + @controller_class.skip_authorize_resource(:project, only: %i(index show)) + expect(@controller_class.cancan_skipper[:authorize][:project]).to eq(only: %i(index show)) + @controller_class.skip_authorize_resource(only: %i(index show)) + expect(@controller_class.cancan_skipper[:authorize][nil]).to eq(only: %i(index show)) @controller_class.skip_authorize_resource(:article) expect(@controller_class.cancan_skipper[:authorize][:article]).to eq({}) end it 'skip_load_resource adds itself to the cancan skipper with given model name and options' do - @controller_class.skip_load_resource(:project, only: [:index, :show]) - expect(@controller_class.cancan_skipper[:load][:project]).to eq(only: [:index, :show]) - @controller_class.skip_load_resource(only: [:index, :show]) - expect(@controller_class.cancan_skipper[:load][nil]).to eq(only: [:index, :show]) + @controller_class.skip_load_resource(:project, only: %i(index show)) + expect(@controller_class.cancan_skipper[:load][:project]).to eq(only: %i(index show)) + @controller_class.skip_load_resource(only: %i(index show)) + expect(@controller_class.cancan_skipper[:load][nil]).to eq(only: %i(index show)) @controller_class.skip_load_resource(:article) expect(@controller_class.cancan_skipper[:load][:article]).to eq({}) end it 'skip_load_and_authore_resource adds itself to the cancan skipper with given model name and options' do - @controller_class.skip_load_and_authorize_resource(:project, only: [:index, :show]) - expect(@controller_class.cancan_skipper[:load][:project]).to eq(only: [:index, :show]) - expect(@controller_class.cancan_skipper[:authorize][:project]).to eq(only: [:index, :show]) + @controller_class.skip_load_and_authorize_resource(:project, only: %i(index show)) + expect(@controller_class.cancan_skipper[:load][:project]).to eq(only: %i(index show)) + expect(@controller_class.cancan_skipper[:authorize][:project]).to eq(only: %i(index show)) end private diff --git a/spec/cancan/controller_resource_spec.rb b/spec/cancan/controller_resource_spec.rb index 0cc4da54..b9b9d9d8 100644 --- a/spec/cancan/controller_resource_spec.rb +++ b/spec/cancan/controller_resource_spec.rb @@ -254,7 +254,7 @@ class Dashboard; end it 'does not build a single resource when on custom collection action even with id' do params.merge!(action: 'sort', id: '123') - resource = CanCan::ControllerResource.new(controller, collection: [:sort, :list]) + resource = CanCan::ControllerResource.new(controller, collection: %i(sort list)) resource.load_resource expect(controller.instance_variable_get(:@model)).to be_nil end @@ -390,7 +390,7 @@ class Dashboard; end controller.instance_variable_set(:@category, category) allow(category.models).to receive(:find).with('123') { :some_model } - resource = CanCan::ControllerResource.new(controller, through: [:category, :user]) + resource = CanCan::ControllerResource.new(controller, through: %i(category user)) resource.load_resource expect(controller.instance_variable_get(:@model)).to eq(:some_model) end @@ -596,7 +596,7 @@ class Section; end end it 'skips resource behavior for :only actions in array' do - allow(controller_class).to receive(:cancan_skipper) { { load: { nil => { only: [:index, :show] } } } } + allow(controller_class).to receive(:cancan_skipper) { { load: { nil => { only: %i(index show) } } } } params[:action] = 'index' expect(CanCan::ControllerResource.new(controller).skip?(:load)).to be(true) expect(CanCan::ControllerResource.new(controller, :some_resource).skip?(:load)).to be(false) @@ -616,7 +616,7 @@ class Section; end end it 'skips resource behavior :except actions in array' do - allow(controller_class).to receive(:cancan_skipper) { { load: { nil => { except: [:index, :show] } } } } + allow(controller_class).to receive(:cancan_skipper) { { load: { nil => { except: %i(index show) } } } } params[:action] = 'index' expect(CanCan::ControllerResource.new(controller).skip?(:load)).to be_falsey params[:action] = 'show' diff --git a/spec/cancan/matchers_spec.rb b/spec/cancan/matchers_spec.rb index 1927d9d2..2e825892 100644 --- a/spec/cancan/matchers_spec.rb +++ b/spec/cancan/matchers_spec.rb @@ -40,7 +40,7 @@ it 'delegates to can? with array of abilities with multiple actions' do is_expected.to receive(:can?).with(:read, 123) { true } is_expected.to receive(:can?).with(:update, 123) { true } - is_expected.to be_able_to([:read, :update], 123) + is_expected.to be_able_to(%i(read update), 123) end it 'delegates to can? with array of abilities with empty array' do @@ -50,7 +50,7 @@ it 'delegates to can? with array of abilities with only one eligable ability' do is_expected.to receive(:can?).with(:read, 123) { true } is_expected.to receive(:can?).with(:update, 123) { false } - is_expected.not_to be_able_to([:read, :update], 123) + is_expected.not_to be_able_to(%i(read update), 123) end end end diff --git a/spec/cancan/model_adapters/active_record_4_adapter_spec.rb b/spec/cancan/model_adapters/active_record_4_adapter_spec.rb index c041ab88..b3c16581 100644 --- a/spec/cancan/model_adapters/active_record_4_adapter_spec.rb +++ b/spec/cancan/model_adapters/active_record_4_adapter_spec.rb @@ -48,7 +48,7 @@ class Child < ActiveRecord::Base end class Shape < ActiveRecord::Base - enum color: [:red, :green, :blue] + enum color: %i(red green blue) end red = Shape.create!(color: :red) @@ -86,7 +86,7 @@ class Shape < ActiveRecord::Base end class Disc < ActiveRecord::Base - enum color: [:red, :green, :blue] + enum color: %i(red green blue) enum shape: { triangle: 3, rectangle: 4 } end diff --git a/spec/cancan/model_adapters/active_record_adapter_spec.rb b/spec/cancan/model_adapters/active_record_adapter_spec.rb index 501a8093..b145e431 100644 --- a/spec/cancan/model_adapters/active_record_adapter_spec.rb +++ b/spec/cancan/model_adapters/active_record_adapter_spec.rb @@ -317,7 +317,7 @@ class User < ActiveRecord::Base it 'merges separate joins into a single array' do @ability.can :read, Article, project: { blocked: false } @ability.can :read, Article, company: { admin: true } - expect(@ability.model_adapter(Article, :read).joins.inspect).to orderlessly_match([:company, :project].inspect) + expect(@ability.model_adapter(Article, :read).joins.inspect).to orderlessly_match(%i(company project).inspect) end it 'merges same joins into a single array' do diff --git a/spec/cancan/model_adapters/mongoid_adapter_spec.rb b/spec/cancan/model_adapters/mongoid_adapter_spec.rb index 387d0940..67ac64d7 100644 --- a/spec/cancan/model_adapters/mongoid_adapter_spec.rb +++ b/spec/cancan/model_adapters/mongoid_adapter_spec.rb @@ -32,8 +32,8 @@ class MongoidSubProject end after(:each) do - Mongoid.master.collections.select do |collection| - collection.name !~ /system/ + Mongoid.master.collections.reject do |collection| + collection.name =~ /system/ end.each(&:drop) end From 9ea6d7858829b779c31dd2794319c0b0ba9e2a1a Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Thu, 6 Apr 2017 08:32:06 +0200 Subject: [PATCH 10/30] add support for newer versins of ruby --- .travis.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5eb4c52b..73b54a28 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,11 +2,12 @@ language: ruby cache: bundler sudo: false rvm: - - 2.0.0 - - 2.1.0 - 2.2.0 - - 2.2.2 + - 2.2.7 + - 2.3.4 + - 2.4.1 - jruby-9.0.5.0 + - jruby-9.1.8.0 gemfile: - gemfiles/activerecord_4.2.gemfile - gemfiles/activerecord_5.0.gemfile @@ -17,12 +18,10 @@ services: matrix: fast_finish: true exclude: - - rvm: 2.0.0 - gemfile: gemfiles/activerecord_5.0.gemfile - - rvm: 2.1.0 - gemfile: gemfiles/activerecord_5.0.gemfile - rvm: 2.2.0 gemfile: gemfiles/activerecord_5.0.gemfile + - rvm: 2.2.7 + gemfile: gemfiles/activerecord_5.0.gemfile - rvm: jruby-9.0.5.0 gemfile: gemfiles/activerecord_5.0.gemfile notifications: From 8e9e6d8b2d168c884e5c4e73a07ef511c5e33e53 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Thu, 6 Apr 2017 08:43:40 +0200 Subject: [PATCH 11/30] fix new issues with latest rubocop version --- .rubocop_todo.yml | 4 +++ Rakefile | 2 +- lib/cancan/ability.rb | 2 +- lib/cancan/controller_resource.rb | 4 +-- spec/cancan/ability_spec.rb | 20 +++++++------- spec/cancan/controller_additions_spec.rb | 26 +++++++++---------- spec/cancan/controller_resource_spec.rb | 8 +++--- spec/cancan/matchers_spec.rb | 4 +-- .../active_record_4_adapter_spec.rb | 4 +-- .../active_record_adapter_spec.rb | 2 +- .../model_adapters/mongoid_adapter_spec.rb | 18 ++++++------- 11 files changed, 49 insertions(+), 45 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b46e4d42..357eaf5e 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -90,3 +90,7 @@ Style/TrailingWhitespace: Style/FileName: Exclude: - 'Appraisals' + +# disagree +Lint/AmbiguousBlockAssociation: + Enabled: false diff --git a/Rakefile b/Rakefile index 0a94a51f..322251c7 100644 --- a/Rakefile +++ b/Rakefile @@ -10,4 +10,4 @@ RSpec::Core::RakeTask.new do |t| t.verbose = false end -task default: %i(rubocop spec) +task default: %i[rubocop spec] diff --git a/lib/cancan/ability.rb b/lib/cancan/ability.rb index 33fdf2b1..a7661a43 100644 --- a/lib/cancan/ability.rb +++ b/lib/cancan/ability.rb @@ -414,7 +414,7 @@ def relevant_rules_for_query(action, subject) def default_alias_actions { - read: %i(index show), + read: %i[index show], create: [:new], update: [:edit] } diff --git a/lib/cancan/controller_resource.rb b/lib/cancan/controller_resource.rb index 1a254936..40104e98 100644 --- a/lib/cancan/controller_resource.rb +++ b/lib/cancan/controller_resource.rb @@ -303,11 +303,11 @@ def collection_actions end def new_actions - %i(new create) + Array(@options[:new]) + %i[new create] + Array(@options[:new]) end def save_actions - %i(create update) + %i[create update] end private diff --git a/spec/cancan/ability_spec.rb b/spec/cancan/ability_spec.rb index 0240a192..e2b630a7 100644 --- a/spec/cancan/ability_spec.rb +++ b/spec/cancan/ability_spec.rb @@ -158,7 +158,7 @@ end it 'is able to specify multiple actions and match any' do - @ability.can %i(read update), :all + @ability.can %i[read update], :all expect(@ability.can?(:read, 123)).to be(true) expect(@ability.can?(:update, 123)).to be(true) expect(@ability.can?(:count, 123)).to be(false) @@ -188,9 +188,9 @@ expected_list = { can: { manage: ['all'], learn: ['Range'] }, - cannot: { read: %w(String Hash), - index: %w(String Hash), - show: %w(String Hash), + cannot: { read: %w[String Hash], + index: %w[String Hash], + show: %w[String Hash], preview: ['Array'] } } expect(@ability.permissions).to eq(expected_list) @@ -201,8 +201,8 @@ expect(@ability.can?(:read, :stats)).to be(true) expect(@ability.can?(:update, :stats)).to be(false) expect(@ability.can?(:read, :nonstats)).to be(false) - expect(@ability.can?(:read, any: %i(stats nonstats))).to be(true) - expect(@ability.can?(:read, any: %i(nonstats neitherstats))).to be(false) + expect(@ability.can?(:read, any: %i[stats nonstats])).to be(true) + expect(@ability.can?(:read, any: %i[nonstats neitherstats])).to be(false) end it 'checks ancestors of class' do @@ -218,7 +218,7 @@ @ability.cannot :read, Integer expect(@ability.can?(:read, 'foo')).to be(true) expect(@ability.can?(:read, 123)).to be(false) - expect(@ability.can?(:read, any: %w(foo bar))).to be(true) + expect(@ability.can?(:read, any: %w[foo bar])).to be(true) expect(@ability.can?(:read, any: [123, 'foo'])).to be(false) expect(@ability.can?(:read, any: [123, 456])).to be(false) end @@ -261,7 +261,7 @@ it 'appends aliased actions' do @ability.alias_action :update, to: :modify @ability.alias_action :destroy, to: :modify - expect(@ability.aliased_actions[:modify]).to eq(%i(update destroy)) + expect(@ability.aliased_actions[:modify]).to eq(%i[update destroy]) end it 'clears aliased actions' do @@ -400,7 +400,7 @@ class A it 'checks permissions correctly when passing a hash of subjects with multiple definitions' do @ability.can :read, Range, string: { length: 4 } - @ability.can %i(create read), Range, string: { upcase: 'FOO' } + @ability.can %i[create read], Range, string: { upcase: 'FOO' } expect(@ability.can?(:read, 'foo' => Range)).to be(true) expect(@ability.can?(:read, 'foobar' => Range)).to be(false) @@ -418,7 +418,7 @@ class Container < Hash it "has initial attributes based on hash conditions of 'new' action" do @ability.can :manage, Range, foo: 'foo', hash: { skip: 'hashes' } - @ability.can :create, Range, bar: 123, array: %w(skip arrays) + @ability.can :create, Range, bar: 123, array: %w[skip arrays] @ability.can :new, Range, baz: 'baz', range: 1..3 @ability.cannot :new, Range, ignore: 'me' expect(@ability.attributes_for(:new, Range)).to eq(foo: 'foo', bar: 123, baz: 'baz') diff --git a/spec/cancan/controller_additions_spec.rb b/spec/cancan/controller_additions_spec.rb index 88a7716c..7a9712c9 100644 --- a/spec/cancan/controller_additions_spec.rb +++ b/spec/cancan/controller_additions_spec.rb @@ -65,10 +65,10 @@ expect(cancan_resource_class = double).to receive(:load_resource) allow(CanCan::ControllerResource).to receive(:new).with(@controller, nil, foo: :bar) { cancan_resource_class } expect(@controller_class) - .to receive(callback_action(:before_action)).with(only: %i(show index), unless: false) do |_options, &block| + .to receive(callback_action(:before_action)).with(only: %i[show index], unless: false) do |_options, &block| block.call(@controller) end - @controller_class.load_resource foo: :bar, only: %i(show index), unless: false + @controller_class.load_resource foo: :bar, only: %i[show index], unless: false end it 'skip_authorization_check setups a before filter which sets @_authorized to true' do @@ -124,27 +124,27 @@ end it 'skip_authorize_resource adds itself to the cancan skipper with given model name and options' do - @controller_class.skip_authorize_resource(:project, only: %i(index show)) - expect(@controller_class.cancan_skipper[:authorize][:project]).to eq(only: %i(index show)) - @controller_class.skip_authorize_resource(only: %i(index show)) - expect(@controller_class.cancan_skipper[:authorize][nil]).to eq(only: %i(index show)) + @controller_class.skip_authorize_resource(:project, only: %i[index show]) + expect(@controller_class.cancan_skipper[:authorize][:project]).to eq(only: %i[index show]) + @controller_class.skip_authorize_resource(only: %i[index show]) + expect(@controller_class.cancan_skipper[:authorize][nil]).to eq(only: %i[index show]) @controller_class.skip_authorize_resource(:article) expect(@controller_class.cancan_skipper[:authorize][:article]).to eq({}) end it 'skip_load_resource adds itself to the cancan skipper with given model name and options' do - @controller_class.skip_load_resource(:project, only: %i(index show)) - expect(@controller_class.cancan_skipper[:load][:project]).to eq(only: %i(index show)) - @controller_class.skip_load_resource(only: %i(index show)) - expect(@controller_class.cancan_skipper[:load][nil]).to eq(only: %i(index show)) + @controller_class.skip_load_resource(:project, only: %i[index show]) + expect(@controller_class.cancan_skipper[:load][:project]).to eq(only: %i[index show]) + @controller_class.skip_load_resource(only: %i[index show]) + expect(@controller_class.cancan_skipper[:load][nil]).to eq(only: %i[index show]) @controller_class.skip_load_resource(:article) expect(@controller_class.cancan_skipper[:load][:article]).to eq({}) end it 'skip_load_and_authore_resource adds itself to the cancan skipper with given model name and options' do - @controller_class.skip_load_and_authorize_resource(:project, only: %i(index show)) - expect(@controller_class.cancan_skipper[:load][:project]).to eq(only: %i(index show)) - expect(@controller_class.cancan_skipper[:authorize][:project]).to eq(only: %i(index show)) + @controller_class.skip_load_and_authorize_resource(:project, only: %i[index show]) + expect(@controller_class.cancan_skipper[:load][:project]).to eq(only: %i[index show]) + expect(@controller_class.cancan_skipper[:authorize][:project]).to eq(only: %i[index show]) end private diff --git a/spec/cancan/controller_resource_spec.rb b/spec/cancan/controller_resource_spec.rb index b9b9d9d8..794c8087 100644 --- a/spec/cancan/controller_resource_spec.rb +++ b/spec/cancan/controller_resource_spec.rb @@ -254,7 +254,7 @@ class Dashboard; end it 'does not build a single resource when on custom collection action even with id' do params.merge!(action: 'sort', id: '123') - resource = CanCan::ControllerResource.new(controller, collection: %i(sort list)) + resource = CanCan::ControllerResource.new(controller, collection: %i[sort list]) resource.load_resource expect(controller.instance_variable_get(:@model)).to be_nil end @@ -390,7 +390,7 @@ class Dashboard; end controller.instance_variable_set(:@category, category) allow(category.models).to receive(:find).with('123') { :some_model } - resource = CanCan::ControllerResource.new(controller, through: %i(category user)) + resource = CanCan::ControllerResource.new(controller, through: %i[category user]) resource.load_resource expect(controller.instance_variable_get(:@model)).to eq(:some_model) end @@ -596,7 +596,7 @@ class Section; end end it 'skips resource behavior for :only actions in array' do - allow(controller_class).to receive(:cancan_skipper) { { load: { nil => { only: %i(index show) } } } } + allow(controller_class).to receive(:cancan_skipper) { { load: { nil => { only: %i[index show] } } } } params[:action] = 'index' expect(CanCan::ControllerResource.new(controller).skip?(:load)).to be(true) expect(CanCan::ControllerResource.new(controller, :some_resource).skip?(:load)).to be(false) @@ -616,7 +616,7 @@ class Section; end end it 'skips resource behavior :except actions in array' do - allow(controller_class).to receive(:cancan_skipper) { { load: { nil => { except: %i(index show) } } } } + allow(controller_class).to receive(:cancan_skipper) { { load: { nil => { except: %i[index show] } } } } params[:action] = 'index' expect(CanCan::ControllerResource.new(controller).skip?(:load)).to be_falsey params[:action] = 'show' diff --git a/spec/cancan/matchers_spec.rb b/spec/cancan/matchers_spec.rb index 2e825892..0e81ee82 100644 --- a/spec/cancan/matchers_spec.rb +++ b/spec/cancan/matchers_spec.rb @@ -40,7 +40,7 @@ it 'delegates to can? with array of abilities with multiple actions' do is_expected.to receive(:can?).with(:read, 123) { true } is_expected.to receive(:can?).with(:update, 123) { true } - is_expected.to be_able_to(%i(read update), 123) + is_expected.to be_able_to(%i[read update], 123) end it 'delegates to can? with array of abilities with empty array' do @@ -50,7 +50,7 @@ it 'delegates to can? with array of abilities with only one eligable ability' do is_expected.to receive(:can?).with(:read, 123) { true } is_expected.to receive(:can?).with(:update, 123) { false } - is_expected.not_to be_able_to(%i(read update), 123) + is_expected.not_to be_able_to(%i[read update], 123) end end end diff --git a/spec/cancan/model_adapters/active_record_4_adapter_spec.rb b/spec/cancan/model_adapters/active_record_4_adapter_spec.rb index b3c16581..9016fdc1 100644 --- a/spec/cancan/model_adapters/active_record_4_adapter_spec.rb +++ b/spec/cancan/model_adapters/active_record_4_adapter_spec.rb @@ -48,7 +48,7 @@ class Child < ActiveRecord::Base end class Shape < ActiveRecord::Base - enum color: %i(red green blue) + enum color: %i[red green blue] end red = Shape.create!(color: :red) @@ -86,7 +86,7 @@ class Shape < ActiveRecord::Base end class Disc < ActiveRecord::Base - enum color: %i(red green blue) + enum color: %i[red green blue] enum shape: { triangle: 3, rectangle: 4 } end diff --git a/spec/cancan/model_adapters/active_record_adapter_spec.rb b/spec/cancan/model_adapters/active_record_adapter_spec.rb index b145e431..17417b57 100644 --- a/spec/cancan/model_adapters/active_record_adapter_spec.rb +++ b/spec/cancan/model_adapters/active_record_adapter_spec.rb @@ -317,7 +317,7 @@ class User < ActiveRecord::Base it 'merges separate joins into a single array' do @ability.can :read, Article, project: { blocked: false } @ability.can :read, Article, company: { admin: true } - expect(@ability.model_adapter(Article, :read).joins.inspect).to orderlessly_match(%i(company project).inspect) + expect(@ability.model_adapter(Article, :read).joins.inspect).to orderlessly_match(%i[company project].inspect) end it 'merges same joins into a single array' do diff --git a/spec/cancan/model_adapters/mongoid_adapter_spec.rb b/spec/cancan/model_adapters/mongoid_adapter_spec.rb index 67ac64d7..dff8ab87 100644 --- a/spec/cancan/model_adapters/mongoid_adapter_spec.rb +++ b/spec/cancan/model_adapters/mongoid_adapter_spec.rb @@ -56,8 +56,8 @@ class MongoidSubProject end it 'is able to read hashes when field is array' do - one_to_three = MongoidProject.create(numbers: %w(one two three)) - two_to_five = MongoidProject.create(numbers: %w(two three four five)) + one_to_three = MongoidProject.create(numbers: %w[one two three]) + two_to_five = MongoidProject.create(numbers: %w[two three four five]) @ability.can :foo, MongoidProject, numbers: 'one' expect(@ability).to be_able_to(:foo, one_to_three) @@ -122,7 +122,7 @@ class MongoidSubProject describe 'Mongoid::Criteria where clause Symbol extensions using MongoDB expressions' do it 'handles :field.in' do obj = MongoidProject.create(title: 'Sir') - @ability.can :read, MongoidProject, :title.in => %w(Sir Madam) + @ability.can :read, MongoidProject, :title.in => %w[Sir Madam] expect(@ability.can?(:read, obj)).to eq(true) expect(MongoidProject.accessible_by(@ability, :read)).to eq([obj]) @@ -133,7 +133,7 @@ class MongoidSubProject describe 'activates only when there are Criteria in the hash' do it 'Calls where on the model class when there are criteria' do obj = MongoidProject.create(title: 'Bird') - @conditions = { :title.nin => %w(Fork Spoon) } + @conditions = { :title.nin => %w[Fork Spoon] } @ability.can :read, MongoidProject, @conditions expect(@ability).to be_able_to(:read, obj) @@ -148,7 +148,7 @@ class MongoidSubProject it 'handles :field.nin' do obj = MongoidProject.create(title: 'Sir') - @ability.can :read, MongoidProject, :title.nin => %w(Lord Madam) + @ability.can :read, MongoidProject, :title.nin => %w[Lord Madam] expect(@ability.can?(:read, obj)).to eq(true) expect(MongoidProject.accessible_by(@ability, :read)).to eq([obj]) @@ -157,17 +157,17 @@ class MongoidSubProject end it 'handles :field.size' do - obj = MongoidProject.create(titles: %w(Palatin Margrave)) + obj = MongoidProject.create(titles: %w[Palatin Margrave]) @ability.can :read, MongoidProject, :titles.size => 2 expect(@ability.can?(:read, obj)).to eq(true) expect(MongoidProject.accessible_by(@ability, :read)).to eq([obj]) - obj2 = MongoidProject.create(titles: %w(Palatin Margrave Marquis)) + obj2 = MongoidProject.create(titles: %w[Palatin Margrave Marquis]) expect(@ability.can?(:read, obj2)).to be(false) end it 'handles :field.exists' do - obj = MongoidProject.create(titles: %w(Palatin Margrave)) + obj = MongoidProject.create(titles: %w[Palatin Margrave]) @ability.can :read, MongoidProject, :titles.exists => true expect(@ability.can?(:read, obj)).to eq(true) expect(MongoidProject.accessible_by(@ability, :read)).to eq([obj]) @@ -188,7 +188,7 @@ class MongoidSubProject it 'handles instance not saved to database' do obj = MongoidProject.new(title: 'Sir') - @ability.can :read, MongoidProject, :title.in => %w(Sir Madam) + @ability.can :read, MongoidProject, :title.in => %w[Sir Madam] expect(@ability.can?(:read, obj)).to eq(true) # accessible_by only returns saved records From 62aecda839f915a9e650d60055d9a038cef0fcec Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Thu, 6 Apr 2017 08:57:13 +0200 Subject: [PATCH 12/30] drop support for mongoid and sequel --- .rubocop_todo.yml | 5 - .travis.yml | 2 - Appraisals | 32 +-- CHANGELOG.rdoc | 3 + README.md | 10 + gemfiles/activerecord_5.0.2.gemfile | 19 ++ gemfiles/activerecord_5.1.0.rc1.gemfile | 19 ++ gemfiles/mongoid_2.x.gemfile | 17 -- gemfiles/sequel_3.x.gemfile | 17 -- lib/cancan.rb | 3 - lib/cancan/model_adapters/mongoid_adapter.rb | 80 ------ lib/cancan/model_adapters/sequel_adapter.rb | 87 ------- .../model_adapters/mongoid_adapter_spec.rb | 246 ------------------ .../model_adapters/sequel_adapter_spec.rb | 129 --------- 14 files changed, 61 insertions(+), 608 deletions(-) create mode 100644 gemfiles/activerecord_5.0.2.gemfile create mode 100644 gemfiles/activerecord_5.1.0.rc1.gemfile delete mode 100644 gemfiles/mongoid_2.x.gemfile delete mode 100644 gemfiles/sequel_3.x.gemfile delete mode 100644 lib/cancan/model_adapters/mongoid_adapter.rb delete mode 100644 lib/cancan/model_adapters/sequel_adapter.rb delete mode 100644 spec/cancan/model_adapters/mongoid_adapter_spec.rb delete mode 100644 spec/cancan/model_adapters/sequel_adapter_spec.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 357eaf5e..1bb21e5e 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -28,11 +28,6 @@ Metrics/MethodLength: Metrics/PerceivedComplexity: Max: 10 -# Offense count: 1 -Performance/FixedSize: - Exclude: - - 'spec/cancan/model_adapters/mongoid_adapter_spec.rb' - # Offense count: 3 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, ProceduralMethods, FunctionalMethods, IgnoredMethods. diff --git a/.travis.yml b/.travis.yml index 73b54a28..f7aff4f8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,8 +11,6 @@ rvm: gemfile: - gemfiles/activerecord_4.2.gemfile - gemfiles/activerecord_5.0.gemfile - - gemfiles/mongoid_2.x.gemfile - - gemfiles/sequel_3.x.gemfile services: - mongodb matrix: diff --git a/Appraisals b/Appraisals index d7940b85..ef960dc6 100644 --- a/Appraisals +++ b/Appraisals @@ -15,10 +15,10 @@ appraise 'activerecord_4.2' do end end -appraise 'activerecord_5.0' do - gem 'activerecord', '~> 5.0.0.rc1', require: 'active_record' - gem 'activesupport', '~> 5.0.0.rc1', require: 'active_support/all' - gem 'actionpack', '~> 5.0.0.rc1', require: 'action_pack' +appraise 'activerecord_5.0.2' do + gem 'activerecord', '~> 5.0.2', require: 'active_record' + gem 'activesupport', '~> 5.0.2', require: 'active_support/all' + gem 'actionpack', '~> 5.0.2', require: 'action_pack' gemfile.platforms :jruby do gem 'activerecord-jdbcsqlite3-adapter' @@ -31,30 +31,18 @@ appraise 'activerecord_5.0' do end end -appraise 'mongoid_2.x' do - gem 'activesupport', '~> 3.0', require: 'active_support/all' - gem 'actionpack', '~> 3.0', require: 'action_pack' - gem 'mongoid', '~> 2.0.0' - - gemfile.platforms :ruby, :mswin, :mingw do - gem 'bson_ext', '~> 1.1' - end - - gemfile.platforms :jruby do - gem 'mongo', '~> 1.9.2' - end -end - -appraise 'sequel_3.x' do - gem 'sequel', '~> 3.48.0' - gem 'activesupport', '~> 3.0', require: 'active_support/all' - gem 'actionpack', '~> 3.0', require: 'action_pack' +appraise 'activerecord_5.1.0.rc1' do + gem 'activerecord', '~> 5.1.0.rc1', require: 'active_record' + gem 'activesupport', '~> 5.1.0.rc1', require: 'active_support/all' + gem 'actionpack', '~> 5.1.0.rc1', require: 'action_pack' gemfile.platforms :jruby do + gem 'activerecord-jdbcsqlite3-adapter' gem 'jdbc-sqlite3' end gemfile.platforms :ruby, :mswin, :mingw do gem 'sqlite3' + gem 'pg' end end diff --git a/CHANGELOG.rdoc b/CHANGELOG.rdoc index da98d26a..96f04685 100644 --- a/CHANGELOG.rdoc +++ b/CHANGELOG.rdoc @@ -5,7 +5,10 @@ Unreleased 2.0.0 (TBD) * Drop support for Rails < 4.2 (oliverklee) +* Drop support for ruby < 2.2 (coorasse) * Drop support for InheritedResource (coorasse) +* Drop support for Sequel (coorasse) +* Drop support for Mongoid (coorasse) * Add ability to rspec matcher to take array of abilities (gingray) diff --git a/README.md b/README.md index 56cdd180..a78279c4 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,16 @@ and run the `bundle install` command. For Rails < 4.2 use: gem 'cancancan', '~> 1.10' + +## Version 2.0 + +Version 2.0 drops support for Mongoid and Sequel. + +Please use `gem 'cancancan', '~> 1.10'` for them. + +If you are interested in supporting them, contribute to the sibling gems `cancancan-sequel` and `cancancan-mongoid`. + +Version 2.0 drops also support for Rails < 4.2 and ruby < 2.2 so again use the version 1 of the Gem for these. ## Getting Started diff --git a/gemfiles/activerecord_5.0.2.gemfile b/gemfiles/activerecord_5.0.2.gemfile new file mode 100644 index 00000000..68b527f1 --- /dev/null +++ b/gemfiles/activerecord_5.0.2.gemfile @@ -0,0 +1,19 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "activerecord", "~> 5.0.2", :require => "active_record" +gem "activesupport", "~> 5.0.2", :require => "active_support/all" +gem "actionpack", "~> 5.0.2", :require => "action_pack" + +platforms :jruby do + gem "activerecord-jdbcsqlite3-adapter" + gem "jdbc-sqlite3" +end + +platforms :ruby, :mswin, :mingw do + gem "sqlite3" + gem "pg" +end + +gemspec :path => "../" diff --git a/gemfiles/activerecord_5.1.0.rc1.gemfile b/gemfiles/activerecord_5.1.0.rc1.gemfile new file mode 100644 index 00000000..a9100ce4 --- /dev/null +++ b/gemfiles/activerecord_5.1.0.rc1.gemfile @@ -0,0 +1,19 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "activerecord", "~> 5.1.0.rc1", :require => "active_record" +gem "activesupport", "~> 5.1.0.rc1", :require => "active_support/all" +gem "actionpack", "~> 5.1.0.rc1", :require => "action_pack" + +platforms :jruby do + gem "activerecord-jdbcsqlite3-adapter" + gem "jdbc-sqlite3" +end + +platforms :ruby, :mswin, :mingw do + gem "sqlite3" + gem "pg" +end + +gemspec :path => "../" diff --git a/gemfiles/mongoid_2.x.gemfile b/gemfiles/mongoid_2.x.gemfile deleted file mode 100644 index 02591c8d..00000000 --- a/gemfiles/mongoid_2.x.gemfile +++ /dev/null @@ -1,17 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "activesupport", "~> 3.0", :require => "active_support/all" -gem "actionpack", "~> 3.0", :require => "action_pack" -gem "mongoid", "~> 2.0.0" - -platforms :ruby, :mswin, :mingw do - gem "bson_ext", "~> 1.1" -end - -platforms :jruby do - gem "mongo", "~> 1.9.2" -end - -gemspec :path => "../" diff --git a/gemfiles/sequel_3.x.gemfile b/gemfiles/sequel_3.x.gemfile deleted file mode 100644 index e01bdc6a..00000000 --- a/gemfiles/sequel_3.x.gemfile +++ /dev/null @@ -1,17 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "sequel", "~> 3.48.0" -gem "activesupport", "~> 3.0", :require => "active_support/all" -gem "actionpack", "~> 3.0", :require => "action_pack" - -platforms :jruby do - gem "jdbc-sqlite3" -end - -platforms :ruby, :mswin, :mingw do - gem "sqlite3" -end - -gemspec :path => "../" diff --git a/lib/cancan.rb b/lib/cancan.rb index ddbe4aa2..92a8ee88 100644 --- a/lib/cancan.rb +++ b/lib/cancan.rb @@ -13,6 +13,3 @@ require 'cancan/model_adapters/active_record_adapter' require 'cancan/model_adapters/active_record_4_adapter' end - -require 'cancan/model_adapters/mongoid_adapter' if defined?(Mongoid) && defined?(Mongoid::Document) -require 'cancan/model_adapters/sequel_adapter' if defined? Sequel diff --git a/lib/cancan/model_adapters/mongoid_adapter.rb b/lib/cancan/model_adapters/mongoid_adapter.rb deleted file mode 100644 index 1771de11..00000000 --- a/lib/cancan/model_adapters/mongoid_adapter.rb +++ /dev/null @@ -1,80 +0,0 @@ -module CanCan - module ModelAdapters - class MongoidAdapter < AbstractAdapter - def self.for_class?(model_class) - model_class <= Mongoid::Document - end - - def self.override_conditions_hash_matching?(subject, conditions) - conditions.any? do |k, _v| - key_is_not_symbol = -> { !k.is_a?(Symbol) } - subject_value_is_array = lambda do - subject.respond_to?(k) && subject.send(k).is_a?(Array) - end - - key_is_not_symbol.call || subject_value_is_array.call - end - end - - def self.matches_conditions_hash?(subject, conditions) - # To avoid hitting the db, retrieve the raw Mongo selector from - # the Mongoid Criteria and use Mongoid::Matchers#matches? - subject.matches?(subject.class.where(conditions).selector) - end - - def database_records - if @rules.empty? - @model_class.where(_id: { '$exists' => false, '$type' => 7 }) # return no records in Mongoid - elsif @rules.size == 1 && @rules[0].conditions.is_a?(Mongoid::Criteria) - @rules[0].conditions - else - # we only need to process can rules if - # there are no rules with empty conditions - database_records_from_multiple_rules - end - end - - def database_records_from_multiple_rules - rules = @rules.reject { |rule| rule.conditions.empty? && rule.base_behavior } - process_can_rules = @rules.count == rules.count - - rules.inject(@model_class.all) do |records, rule| - if process_can_rules && rule.base_behavior - records.or simplify_relations(@model_class, rule.conditions) - elsif !rule.base_behavior - records.excludes simplify_relations(@model_class, rule.conditions) - else - records - end - end - end - - private - - # Look for criteria on relations and replace with simple id queries - # eg. - # {user: {:tags.all => []}} becomes {"user_id" => {"$in" => [__, ..]}} - # {user: {:session => {:tags.all => []}}} becomes {"user_id" => {"session_id" => {"$in" => [__, ..]} }} - def simplify_relations(model_class, conditions) - model_relations = model_class.relations.with_indifferent_access - Hash[ - conditions.map do |k, v| - if (relation = model_relations[k]) - relation_class_name = relation[:class_name].blank? ? k.to_s.classify : relation[:class_name] - v = simplify_relations(relation_class_name.constantize, v) - relation_ids = relation_class_name.constantize.where(v).only(:id).map(&:id) - k = "#{k}_id" - v = { '$in' => relation_ids } - end - [k, v] - end - ] - end - end - end -end - -# simplest way to add `accessible_by` to all Mongoid Documents -module Mongoid::Document::ClassMethods - include CanCan::ModelAdditions::ClassMethods -end diff --git a/lib/cancan/model_adapters/sequel_adapter.rb b/lib/cancan/model_adapters/sequel_adapter.rb deleted file mode 100644 index 5044a21d..00000000 --- a/lib/cancan/model_adapters/sequel_adapter.rb +++ /dev/null @@ -1,87 +0,0 @@ -module CanCan - module ModelAdapters - class SequelAdapter < AbstractAdapter - def self.for_class?(model_class) - model_class <= Sequel::Model - end - - def self.find(model_class, id) - model_class[id] - end - - def self.override_condition_matching?(_subject, _name, value) - value.is_a?(Hash) - end - - def self.matches_condition?(subject, name, value) - obj = subject.send(name) - if obj.nil? - false - else - value.each do |k, v| - if v.is_a?(Hash) - return false unless matches_condition?(obj, k, v) - elsif obj.send(k) != v - return false - end - end - end - end - - def database_records - if @rules.empty? - @model_class.where('1=0') - else - # only need to process can rules if there are no can rule with empty conditions - rules = @rules.reject { |rule| rule.base_behavior && rule.conditions.empty? } - rules.reject!(&:base_behavior) if rules.count < @rules.count - - can_condition_added = false - rules.reverse.inject(@model_class.dataset) do |records, rule| - normalized_conditions = normalize_conditions(rule.conditions) - if rule.base_behavior - if can_condition_added - records.or normalized_conditions - else - can_condition_added = true - records.where normalized_conditions - end - else - records.exclude normalized_conditions - end - end - end - end - - private - - def normalize_conditions(conditions, model_class = @model_class) - return conditions unless conditions.is_a? Hash - conditions.each_with_object({}) do |(name, value), result_hash| - if value.is_a? Hash - value = value.dup - association_class = model_class.association_reflection(name).associated_class - nested_resulted = value.each_with_object({}) do |(k, v), nested| - if v.is_a?(Hash) - value.delete(k) - nested_class = association_class.association_reflection(k).associated_class - nested[k] = nested_class.where(normalize_conditions(v, association_class)) - else - nested[k] = v - end - nested - end - result_hash[name] = association_class.where(nested_resulted) - else - result_hash[name] = value - end - result_hash - end - end - end - end -end - -Sequel::Model.class_eval do - include CanCan::ModelAdditions -end diff --git a/spec/cancan/model_adapters/mongoid_adapter_spec.rb b/spec/cancan/model_adapters/mongoid_adapter_spec.rb deleted file mode 100644 index dff8ab87..00000000 --- a/spec/cancan/model_adapters/mongoid_adapter_spec.rb +++ /dev/null @@ -1,246 +0,0 @@ -require 'spec_helper' - -if defined? CanCan::ModelAdapters::MongoidAdapter - - class MongoidCategory - include Mongoid::Document - - references_many :mongoid_projects - end - - class MongoidProject - include Mongoid::Document - - referenced_in :mongoid_category - references_many :mongoid_sub_projects - end - - class MongoidSubProject - include Mongoid::Document - - referenced_in :mongoid_project - end - - Mongoid.configure do |config| - config.master = Mongo::Connection.new('127.0.0.1', 27_017).db('cancan_mongoid_spec') - end - - describe CanCan::ModelAdapters::MongoidAdapter do - context 'Mongoid defined' do - before(:each) do - (@ability = double).extend(CanCan::Ability) - end - - after(:each) do - Mongoid.master.collections.reject do |collection| - collection.name =~ /system/ - end.each(&:drop) - end - - it 'is for only Mongoid classes' do - expect(CanCan::ModelAdapters::MongoidAdapter).not_to be_for_class(Object) - expect(CanCan::ModelAdapters::MongoidAdapter).to be_for_class(MongoidProject) - expect(CanCan::ModelAdapters::AbstractAdapter.adapter_class(MongoidProject)) - .to eq(CanCan::ModelAdapters::MongoidAdapter) - end - - it 'finds record' do - project = MongoidProject.create - expect(CanCan::ModelAdapters::MongoidAdapter.find(MongoidProject, project.id)).to eq(project) - end - - it 'compares properties on mongoid documents with the conditions hash' do - model = MongoidProject.new - @ability.can :read, MongoidProject, id: model.id - expect(@ability).to be_able_to(:read, model) - end - - it 'is able to read hashes when field is array' do - one_to_three = MongoidProject.create(numbers: %w[one two three]) - two_to_five = MongoidProject.create(numbers: %w[two three four five]) - - @ability.can :foo, MongoidProject, numbers: 'one' - expect(@ability).to be_able_to(:foo, one_to_three) - expect(@ability).not_to be_able_to(:foo, two_to_five) - end - - it 'returns [] when no ability is defined so no records are found' do - MongoidProject.create(title: 'Sir') - MongoidProject.create(title: 'Lord') - MongoidProject.create(title: 'Dude') - - expect(MongoidProject.accessible_by(@ability, :read).entries).to eq([]) - end - - it 'returns the correct records based on the defined ability' do - @ability.can :read, MongoidProject, title: 'Sir' - sir = MongoidProject.create(title: 'Sir') - MongoidProject.create(title: 'Lord') - MongoidProject.create(title: 'Dude') - - expect(MongoidProject.accessible_by(@ability, :read).entries).to eq([sir]) - end - - it 'returns the correct records when a mix of can and cannot rules in defined ability' do - @ability.can :manage, MongoidProject, title: 'Sir' - @ability.cannot :destroy, MongoidProject - - sir = MongoidProject.create(title: 'Sir') - MongoidProject.create(title: 'Lord') - MongoidProject.create(title: 'Dude') - - expect(MongoidProject.accessible_by(@ability, :destroy).entries).to eq([sir]) - end - - it 'is able to mix empty conditions and hashes' do - @ability.can :read, MongoidProject - @ability.can :read, MongoidProject, title: 'Sir' - MongoidProject.create(title: 'Sir') - MongoidProject.create(title: 'Lord') - - expect(MongoidProject.accessible_by(@ability, :read).count).to eq(2) - end - - it 'returns everything when the defined ability is access all' do - @ability.can :manage, :all - sir = MongoidProject.create(title: 'Sir') - lord = MongoidProject.create(title: 'Lord') - dude = MongoidProject.create(title: 'Dude') - - expect(MongoidProject.accessible_by(@ability, :read).entries).to eq([sir, lord, dude]) - end - - it 'allows a scope for conditions' do - @ability.can :read, MongoidProject, MongoidProject.where(title: 'Sir') - sir = MongoidProject.create(title: 'Sir') - MongoidProject.create(title: 'Lord') - MongoidProject.create(title: 'Dude') - - expect(MongoidProject.accessible_by(@ability, :read).entries).to eq([sir]) - end - - describe 'Mongoid::Criteria where clause Symbol extensions using MongoDB expressions' do - it 'handles :field.in' do - obj = MongoidProject.create(title: 'Sir') - @ability.can :read, MongoidProject, :title.in => %w[Sir Madam] - expect(@ability.can?(:read, obj)).to eq(true) - expect(MongoidProject.accessible_by(@ability, :read)).to eq([obj]) - - obj2 = MongoidProject.create(title: 'Lord') - expect(@ability.can?(:read, obj2)).to be(false) - end - - describe 'activates only when there are Criteria in the hash' do - it 'Calls where on the model class when there are criteria' do - obj = MongoidProject.create(title: 'Bird') - @conditions = { :title.nin => %w[Fork Spoon] } - - @ability.can :read, MongoidProject, @conditions - expect(@ability).to be_able_to(:read, obj) - end - it 'Calls the base version if there are no mongoid criteria' do - obj = MongoidProject.new(title: 'Bird') - @conditions = { id: obj.id } - @ability.can :read, MongoidProject, @conditions - expect(@ability).to be_able_to(:read, obj) - end - end - - it 'handles :field.nin' do - obj = MongoidProject.create(title: 'Sir') - @ability.can :read, MongoidProject, :title.nin => %w[Lord Madam] - expect(@ability.can?(:read, obj)).to eq(true) - expect(MongoidProject.accessible_by(@ability, :read)).to eq([obj]) - - obj2 = MongoidProject.create(title: 'Lord') - expect(@ability.can?(:read, obj2)).to be(false) - end - - it 'handles :field.size' do - obj = MongoidProject.create(titles: %w[Palatin Margrave]) - @ability.can :read, MongoidProject, :titles.size => 2 - expect(@ability.can?(:read, obj)).to eq(true) - expect(MongoidProject.accessible_by(@ability, :read)).to eq([obj]) - - obj2 = MongoidProject.create(titles: %w[Palatin Margrave Marquis]) - expect(@ability.can?(:read, obj2)).to be(false) - end - - it 'handles :field.exists' do - obj = MongoidProject.create(titles: %w[Palatin Margrave]) - @ability.can :read, MongoidProject, :titles.exists => true - expect(@ability.can?(:read, obj)).to eq(true) - expect(MongoidProject.accessible_by(@ability, :read)).to eq([obj]) - - obj2 = MongoidProject.create - expect(@ability.can?(:read, obj2)).to be(false) - end - - it 'handles :field.gt' do - obj = MongoidProject.create(age: 50) - @ability.can :read, MongoidProject, :age.gt => 45 - expect(@ability.can?(:read, obj)).to eq(true) - expect(MongoidProject.accessible_by(@ability, :read)).to eq([obj]) - - obj2 = MongoidProject.create(age: 40) - expect(@ability.can?(:read, obj2)).to be(false) - end - - it 'handles instance not saved to database' do - obj = MongoidProject.new(title: 'Sir') - @ability.can :read, MongoidProject, :title.in => %w[Sir Madam] - expect(@ability.can?(:read, obj)).to eq(true) - - # accessible_by only returns saved records - expect(MongoidProject.accessible_by(@ability, :read).entries).to eq([]) - - obj2 = MongoidProject.new(title: 'Lord') - expect(@ability.can?(:read, obj2)).to be(false) - end - end - - it 'calls where with matching ability conditions' do - obj = MongoidProject.create(foo: { bar: 1 }) - @ability.can :read, MongoidProject, foo: { bar: 1 } - expect(MongoidProject.accessible_by(@ability, :read).entries.first).to eq(obj) - end - - it 'excludes from the result if set to cannot' do - obj = MongoidProject.create(bar: 1) - MongoidProject.create(bar: 2) - @ability.can :read, MongoidProject - @ability.cannot :read, MongoidProject, bar: 2 - expect(MongoidProject.accessible_by(@ability, :read).entries).to eq([obj]) - end - - it 'combines the rules' do - obj = MongoidProject.create(bar: 1) - obj2 = MongoidProject.create(bar: 2) - MongoidProject.create(bar: 3) - @ability.can :read, MongoidProject, bar: 1 - @ability.can :read, MongoidProject, bar: 2 - expect(MongoidProject.accessible_by(@ability, :read).entries).to match_array([obj, obj2]) - end - - it 'does not allow to fetch records when ability with just block present' do - @ability.can :read, MongoidProject do - false - end - expect do - MongoidProject.accessible_by(@ability) - end.to raise_error(CanCan::Error) - end - - it 'can handle nested queries for accessible_by' do - @ability.can :read, MongoidSubProject, mongoid_project: { mongoid_category: { name: 'Authorization' } } - cat1 = MongoidCategory.create name: 'Authentication' - cat2 = MongoidCategory.create name: 'Authorization' - proj1 = cat1.mongoid_projects.create name: 'Proj1' - proj2 = cat2.mongoid_projects.create name: 'Proj2' - sub1 = proj1.mongoid_sub_projects.create name: 'Sub1' - proj2.mongoid_sub_projects.create name: 'Sub2' - expect(MongoidSubProject.accessible_by(@ability)).to match_array([sub1]) - end - end - end -end diff --git a/spec/cancan/model_adapters/sequel_adapter_spec.rb b/spec/cancan/model_adapters/sequel_adapter_spec.rb deleted file mode 100644 index 21063824..00000000 --- a/spec/cancan/model_adapters/sequel_adapter_spec.rb +++ /dev/null @@ -1,129 +0,0 @@ -require 'spec_helper' - -if defined? CanCan::ModelAdapters::SequelAdapter - describe CanCan::ModelAdapters::SequelAdapter do - DB = if RUBY_PLATFORM == 'java' - Sequel.connect('jdbc:sqlite:db.sqlite3') - else - Sequel.sqlite - end - - DB.create_table :users do - primary_key :id - String :name - end - - class User < Sequel::Model - one_to_many :articles - end - - DB.create_table :articles do - primary_key :id - String :name - TrueClass :published - TrueClass :secret - Integer :priority - foreign_key :user_id, :users - end - - class Article < Sequel::Model - many_to_one :user - one_to_many :comments - end - - DB.create_table :comments do - primary_key :id - TrueClass :spam - foreign_key :article_id, :articles - end - - class Comment < Sequel::Model - many_to_one :article - end - - before(:each) do - Comment.dataset.delete - Article.dataset.delete - User.dataset.delete - (@ability = double).extend(CanCan::Ability) - end - - it 'should be for only sequel model classes' do - expect(CanCan::ModelAdapters::SequelAdapter).to_not be_for_class(Object) - expect(CanCan::ModelAdapters::SequelAdapter).to be_for_class(Article) - expect(CanCan::ModelAdapters::AbstractAdapter.adapter_class(Article)).to eq CanCan::ModelAdapters::SequelAdapter - end - - it 'should find record' do - article = Article.create - expect(CanCan::ModelAdapters::SequelAdapter.find(Article, article.id)).to eq article - end - - it 'should not fetch any records when no abilities are defined' do - Article.create - expect(Article.accessible_by(@ability).all).to be_empty - end - - it 'should fetch all articles when one can read all' do - @ability.can :read, Article - article = Article.create - expect(Article.accessible_by(@ability).all).to eq [article] - end - - it 'should fetch only the articles that are published' do - @ability.can :read, Article, published: true - article1 = Article.create(published: true) - Article.create(published: false) - expect(Article.accessible_by(@ability).all).to eq [article1] - end - - it 'should fetch any articles which are published or secret' do - @ability.can :read, Article, published: true - @ability.can :read, Article, secret: true - article1 = Article.create(published: true, secret: false) - article2 = Article.create(published: true, secret: true) - article3 = Article.create(published: false, secret: true) - Article.create(published: false, secret: false) - expect(Article.accessible_by(@ability).all).to eq([article1, article2, article3]) - end - - it 'should fetch only the articles that are published and not secret' do - @ability.can :read, Article, published: true - @ability.cannot :read, Article, secret: true - article1 = Article.create(published: true, secret: false) - Article.create(published: true, secret: true) - Article.create(published: false, secret: true) - Article.create(published: false, secret: false) - expect(Article.accessible_by(@ability).all).to eq [article1] - end - - it 'should only read comments for articles which are published' do - @ability.can :read, Comment, article: { published: true } - comment1 = Comment.create(article: Article.create(published: true)) - Comment.create(article: Article.create(published: false)) - expect(Comment.accessible_by(@ability).all).to eq [comment1] - end - - it "should only read comments for articles which are published and user is 'me'" do - @ability.can :read, Comment, article: { user: { name: 'me' }, published: true } - user1 = User.create(name: 'me') - comment1 = Comment.create(article: Article.create(published: true, user: user1)) - Comment.create(article: Article.create(published: true)) - Comment.create(article: Article.create(published: false, user: user1)) - expect(Comment.accessible_by(@ability).all).to eq [comment1] - end - - it 'should allow conditions in SQL and merge with hash conditions' do - @ability.can :read, Article, published: true - @ability.can :read, Article, ['secret=?', true], &:secret - @ability.cannot :read, Article, 'priority > 1' do |article| - article.priority > 1 - end - article1 = Article.create(published: true, secret: false, priority: 1) - article2 = Article.create(published: true, secret: true, priority: 1) - Article.create(published: true, secret: true, priority: 2) - Article.create(published: false, secret: false, priority: 2) - expect(Article.accessible_by(@ability).all).to eq [article1, article2] - end - end -end From 0c26e6e06c0a6c934f80da455346b698cbd77575 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Thu, 6 Apr 2017 09:11:30 +0200 Subject: [PATCH 13/30] exclude non working versiosn --- .travis.yml | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index f7aff4f8..58ca78cf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,18 +10,29 @@ rvm: - jruby-9.1.8.0 gemfile: - gemfiles/activerecord_4.2.gemfile - - gemfiles/activerecord_5.0.gemfile + - gemfiles/activerecord_5.0.2.gemfile + - gemfiles/activerecord_5.1.0.rc1.gemfile services: - mongodb matrix: fast_finish: true exclude: - rvm: 2.2.0 - gemfile: gemfiles/activerecord_5.0.gemfile + gemfile: gemfiles/activerecord_5.0.2.gemfile - rvm: 2.2.7 - gemfile: gemfiles/activerecord_5.0.gemfile + gemfile: gemfiles/activerecord_5.0.2.gemfile + - rvm: 2.2.0 + gemfile: gemfiles/activerecord_5.1.0.rc1.gemfile + - rvm: 2.2.7 + gemfile: gemfiles/activerecord_5.1.0.rc1.gemfile + - rvm: jruby-9.0.5.0 + gemfile: gemfiles/activerecord_5.0.2.gemfile + - rvm: jruby-9.1.8.0 + gemfile: gemfiles/activerecord_5.0.2.gemfile - rvm: jruby-9.0.5.0 - gemfile: gemfiles/activerecord_5.0.gemfile + gemfile: gemfiles/activerecord_5.1.0.rc1.gemfile + - rvm: jruby-9.1.8.0 + gemfile: gemfiles/activerecord_5.1.0.rc1.gemfile notifications: email: recipients: From 48e9c0c623f21cf5715f917daa2f699f0388d6af Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Thu, 6 Apr 2017 09:29:54 +0200 Subject: [PATCH 14/30] update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b37f416..8570e717 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ All permissions are defined in a single location (the `Ability` class) and not d Add this to your Gemfile: - gem 'cancancan' + gem 'cancancan', '~> 1.10' and run the `bundle install` command. From 59f65a37dbfcd1334d7ebfbaccf1097490b11d27 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Thu, 6 Apr 2017 13:51:07 +0200 Subject: [PATCH 15/30] fix rubocop version --- gemfiles/activerecord_3.2.gemfile | 1 + gemfiles/activerecord_4.0.gemfile | 1 + gemfiles/activerecord_4.1.gemfile | 1 + gemfiles/activerecord_4.2.gemfile | 1 + gemfiles/activerecord_5.0.gemfile | 1 + gemfiles/mongoid_2.x.gemfile | 1 + gemfiles/sequel_3.x.gemfile | 1 + 7 files changed, 7 insertions(+) diff --git a/gemfiles/activerecord_3.2.gemfile b/gemfiles/activerecord_3.2.gemfile index 20f06238..f0886661 100644 --- a/gemfiles/activerecord_3.2.gemfile +++ b/gemfiles/activerecord_3.2.gemfile @@ -4,6 +4,7 @@ source "https://rubygems.org" gem "activerecord", "~> 3.2.0", :require => "active_record" gem "actionpack", "~> 3.2.0", :require => "action_pack" +gem "rubocop", "0.48.0" platforms :jruby do gem "activerecord-jdbcsqlite3-adapter" diff --git a/gemfiles/activerecord_4.0.gemfile b/gemfiles/activerecord_4.0.gemfile index 1803439c..a8c5bf6b 100644 --- a/gemfiles/activerecord_4.0.gemfile +++ b/gemfiles/activerecord_4.0.gemfile @@ -5,6 +5,7 @@ source "https://rubygems.org" gem "activerecord", "~> 4.0.5", :require => "active_record" gem "activesupport", "~> 4.0.5", :require => "active_support/all" gem "actionpack", "~> 4.0.5", :require => "action_pack" +gem "rubocop", "0.48.0" platforms :jruby do gem "activerecord-jdbcsqlite3-adapter" diff --git a/gemfiles/activerecord_4.1.gemfile b/gemfiles/activerecord_4.1.gemfile index 4e3a05b4..750172c4 100644 --- a/gemfiles/activerecord_4.1.gemfile +++ b/gemfiles/activerecord_4.1.gemfile @@ -5,6 +5,7 @@ source "https://rubygems.org" gem "activerecord", "~> 4.1.1", :require => "active_record" gem "activesupport", "~> 4.1.1", :require => "active_support/all" gem "actionpack", "~> 4.1.1", :require => "action_pack" +gem "rubocop", "0.48.0" platforms :jruby do gem "activerecord-jdbcsqlite3-adapter" diff --git a/gemfiles/activerecord_4.2.gemfile b/gemfiles/activerecord_4.2.gemfile index 2048dce6..25f5e073 100644 --- a/gemfiles/activerecord_4.2.gemfile +++ b/gemfiles/activerecord_4.2.gemfile @@ -6,6 +6,7 @@ gem "activerecord", "~> 4.2.0", :require => "active_record" gem "activesupport", "~> 4.2.0", :require => "active_support/all" gem "actionpack", "~> 4.2.0", :require => "action_pack" gem "nokogiri", "~> 1.6.8", :require => "nokogiri" +gem "rubocop", "0.48.0" platforms :jruby do gem "activerecord-jdbcsqlite3-adapter" diff --git a/gemfiles/activerecord_5.0.gemfile b/gemfiles/activerecord_5.0.gemfile index 0f2de615..fef1fec4 100644 --- a/gemfiles/activerecord_5.0.gemfile +++ b/gemfiles/activerecord_5.0.gemfile @@ -5,6 +5,7 @@ source "https://rubygems.org" gem "activerecord", "~> 5.0.0.rc1", :require => "active_record" gem "activesupport", "~> 5.0.0.rc1", :require => "active_support/all" gem "actionpack", "~> 5.0.0.rc1", :require => "action_pack" +gem "rubocop", "0.48.0" platforms :jruby do gem "activerecord-jdbcsqlite3-adapter" diff --git a/gemfiles/mongoid_2.x.gemfile b/gemfiles/mongoid_2.x.gemfile index 02591c8d..3b9ae4d8 100644 --- a/gemfiles/mongoid_2.x.gemfile +++ b/gemfiles/mongoid_2.x.gemfile @@ -5,6 +5,7 @@ source "https://rubygems.org" gem "activesupport", "~> 3.0", :require => "active_support/all" gem "actionpack", "~> 3.0", :require => "action_pack" gem "mongoid", "~> 2.0.0" +gem "rubocop", "0.48.0" platforms :ruby, :mswin, :mingw do gem "bson_ext", "~> 1.1" diff --git a/gemfiles/sequel_3.x.gemfile b/gemfiles/sequel_3.x.gemfile index e01bdc6a..a167284f 100644 --- a/gemfiles/sequel_3.x.gemfile +++ b/gemfiles/sequel_3.x.gemfile @@ -5,6 +5,7 @@ source "https://rubygems.org" gem "sequel", "~> 3.48.0" gem "activesupport", "~> 3.0", :require => "active_support/all" gem "actionpack", "~> 3.0", :require => "action_pack" +gem "rubocop", "0.48.0" platforms :jruby do gem "jdbc-sqlite3" From 8c519bda902fc9231325248138998e07bad3515e Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Thu, 6 Apr 2017 14:36:46 +0200 Subject: [PATCH 16/30] update the README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 76cb63b4..5bd634d0 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [Screencast](http://railscasts.com/episodes/192-authorization-with-cancan) | [Gitter](https://gitter.im/CanCanCommunity/cancancan) -CanCanCan is an authorization library for Ruby 2.0+ and Ruby on Rails 3+ which restricts what resources a given user is allowed to access. +CanCanCan is an authorization library for Ruby >= 2.2.0 and Ruby on Rails >= 4.2 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. From 45ba648bcec0e41387d3887cf572291e0552f664 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Thu, 6 Apr 2017 14:39:59 +0200 Subject: [PATCH 17/30] update changelog --- CHANGELOG.rdoc | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rdoc b/CHANGELOG.rdoc index 260b87ac..3a278db6 100644 --- a/CHANGELOG.rdoc +++ b/CHANGELOG.rdoc @@ -10,6 +10,7 @@ Unreleased * Drop support for Sequel (coorasse) * Drop support for Mongoid (coorasse) * Add ability to rspec matcher to take array of abilities (gingray) +* Increase Performance (timraymond) (https://github.com/CanCanCommunity/cancancan/pull/204) 1.17.0 (March 26th, 2017) From 957e3027ba9e123482ba343c43dffa4bf94055b6 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Tue, 2 May 2017 11:34:41 +0200 Subject: [PATCH 18/30] remove deprecated methods and options --- CHANGELOG.rdoc | 2 ++ lib/cancan/controller_additions.rb | 10 ---------- lib/cancan/controller_resource.rb | 6 ------ spec/cancan/controller_additions_spec.rb | 4 ---- spec/cancan/controller_resource_spec.rb | 18 ------------------ 5 files changed, 2 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.rdoc b/CHANGELOG.rdoc index 3a278db6..92c7e67e 100644 --- a/CHANGELOG.rdoc +++ b/CHANGELOG.rdoc @@ -11,6 +11,8 @@ Unreleased * Drop support for Mongoid (coorasse) * Add ability to rspec matcher to take array of abilities (gingray) * Increase Performance (timraymond) (https://github.com/CanCanCommunity/cancancan/pull/204) +* Removed controller methods: skip_authorization, unauthorized! (coorasse) +* Removed options: nested, name, resource (coorasse) 1.17.0 (March 26th, 2017) diff --git a/lib/cancan/controller_additions.rb b/lib/cancan/controller_additions.rb index 8484f5b9..258b49d8 100644 --- a/lib/cancan/controller_additions.rb +++ b/lib/cancan/controller_additions.rb @@ -280,12 +280,6 @@ def skip_authorization_check(*args) send(:before_action, *args, &block) end - def skip_authorization(*_args) - raise ImplementationRemoved, - 'The CanCan skip_authorization method has been renamed to skip_authorization_check. '\ - 'Please update your code.' - end - def cancan_resource_class ControllerResource end @@ -341,10 +335,6 @@ def authorize!(*args) current_ability.authorize!(*args) end - def unauthorized!(_message = nil) - raise ImplementationRemoved, 'The unauthorized! method has been removed from CanCan, use authorize! instead.' - end - # Creates and returns the current user's ability and caches it. If you # want to override how the Ability is defined then this is the place. # Just define the method in the controller to change behavior. diff --git a/lib/cancan/controller_resource.rb b/lib/cancan/controller_resource.rb index 40104e98..ef7a3bb7 100644 --- a/lib/cancan/controller_resource.rb +++ b/lib/cancan/controller_resource.rb @@ -22,12 +22,6 @@ def initialize(controller, *args) @params = controller.params @options = args.extract_options! @name = args.first - nested_err = 'The :nested option is no longer supported, instead use :through with separate load/authorize call.' - raise CanCan::ImplementationRemoved, nested_err if @options[:nested] - name_err = 'The :name option is no longer supported, instead pass the name as the first argument.' - raise CanCan::ImplementationRemoved, name_err if @options[:name] - resource_err = 'The :resource option has been renamed back to :class, use false if no class.' - raise CanCan::ImplementationRemoved, resource_err if @options[:resource] end def load_and_authorize_resource diff --git a/spec/cancan/controller_additions_spec.rb b/spec/cancan/controller_additions_spec.rb index 7a9712c9..f7349d3d 100644 --- a/spec/cancan/controller_additions_spec.rb +++ b/spec/cancan/controller_additions_spec.rb @@ -10,10 +10,6 @@ @controller_class.send(:include, CanCan::ControllerAdditions) end - it "raises ImplementationRemoved when attempting to call 'unauthorized!' on a controller" do - expect { @controller.unauthorized! }.to raise_error(CanCan::ImplementationRemoved) - end - it 'authorize! assigns @_authorized instance variable and pass args to current ability' do allow(@controller.current_ability).to receive(:authorize!).with(:foo, :bar) @controller.authorize!(:foo, :bar) diff --git a/spec/cancan/controller_resource_spec.rb b/spec/cancan/controller_resource_spec.rb index 794c8087..8b5c71dd 100644 --- a/spec/cancan/controller_resource_spec.rb +++ b/spec/cancan/controller_resource_spec.rb @@ -577,24 +577,6 @@ class Section; end expect(resource.send(:resource_class)).to eq(Section) end - it 'raises ImplementationRemoved when adding :name option' do - expect do - CanCan::ControllerResource.new(controller, name: :foo) - end.to raise_error(CanCan::ImplementationRemoved) - end - - it 'raises ImplementationRemoved exception when specifying :resource option since it is no longer used' do - expect do - CanCan::ControllerResource.new(controller, resource: Model) - end.to raise_error(CanCan::ImplementationRemoved) - end - - it 'raises ImplementationRemoved exception when passing :nested option' do - expect do - CanCan::ControllerResource.new(controller, nested: :model) - end.to raise_error(CanCan::ImplementationRemoved) - end - it 'skips resource behavior for :only actions in array' do allow(controller_class).to receive(:cancan_skipper) { { load: { nil => { only: %i[index show] } } } } params[:action] = 'index' From ab389ba8527c34858ebc00f740a009bb8d67fa08 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Tue, 2 May 2017 11:39:36 +0200 Subject: [PATCH 19/30] fix rubocop issues --- .travis.yml | 3 ++- Rakefile | 6 +----- gemfiles/activerecord_4.2.gemfile | 1 - gemfiles/activerecord_5.0.2.gemfile | 1 - gemfiles/activerecord_5.0.gemfile | 2 +- gemfiles/activerecord_5.1.0.rc1.gemfile | 1 - 6 files changed, 4 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 58ca78cf..d8bc934e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,4 +41,5 @@ notifications: - zora.fuchs@renuo.ch on_success: change on_failure: change - +script: + - bundle exec rubocop && bundle exec rake diff --git a/Rakefile b/Rakefile index 322251c7..5f62d901 100644 --- a/Rakefile +++ b/Rakefile @@ -1,13 +1,9 @@ require 'bundler/gem_tasks' require 'rspec/core/rake_task' -require 'rubocop/rake_task' - -desc 'Run Rubocop' -RuboCop::RakeTask.new desc 'Run RSpec' RSpec::Core::RakeTask.new do |t| t.verbose = false end -task default: %i[rubocop spec] +task default: :spec diff --git a/gemfiles/activerecord_4.2.gemfile b/gemfiles/activerecord_4.2.gemfile index 25f5e073..2048dce6 100644 --- a/gemfiles/activerecord_4.2.gemfile +++ b/gemfiles/activerecord_4.2.gemfile @@ -6,7 +6,6 @@ gem "activerecord", "~> 4.2.0", :require => "active_record" gem "activesupport", "~> 4.2.0", :require => "active_support/all" gem "actionpack", "~> 4.2.0", :require => "action_pack" gem "nokogiri", "~> 1.6.8", :require => "nokogiri" -gem "rubocop", "0.48.0" platforms :jruby do gem "activerecord-jdbcsqlite3-adapter" diff --git a/gemfiles/activerecord_5.0.2.gemfile b/gemfiles/activerecord_5.0.2.gemfile index ef1b00a0..68b527f1 100644 --- a/gemfiles/activerecord_5.0.2.gemfile +++ b/gemfiles/activerecord_5.0.2.gemfile @@ -5,7 +5,6 @@ source "https://rubygems.org" gem "activerecord", "~> 5.0.2", :require => "active_record" gem "activesupport", "~> 5.0.2", :require => "active_support/all" gem "actionpack", "~> 5.0.2", :require => "action_pack" -gem "rubocop", "0.48.0" platforms :jruby do gem "activerecord-jdbcsqlite3-adapter" diff --git a/gemfiles/activerecord_5.0.gemfile b/gemfiles/activerecord_5.0.gemfile index fef1fec4..7ca0de8c 100644 --- a/gemfiles/activerecord_5.0.gemfile +++ b/gemfiles/activerecord_5.0.gemfile @@ -5,7 +5,7 @@ source "https://rubygems.org" gem "activerecord", "~> 5.0.0.rc1", :require => "active_record" gem "activesupport", "~> 5.0.0.rc1", :require => "active_support/all" gem "actionpack", "~> 5.0.0.rc1", :require => "action_pack" -gem "rubocop", "0.48.0" +gem "rubocop", "0.48.1" platforms :jruby do gem "activerecord-jdbcsqlite3-adapter" diff --git a/gemfiles/activerecord_5.1.0.rc1.gemfile b/gemfiles/activerecord_5.1.0.rc1.gemfile index 4ceaf4d4..a9100ce4 100644 --- a/gemfiles/activerecord_5.1.0.rc1.gemfile +++ b/gemfiles/activerecord_5.1.0.rc1.gemfile @@ -5,7 +5,6 @@ source "https://rubygems.org" gem "activerecord", "~> 5.1.0.rc1", :require => "active_record" gem "activesupport", "~> 5.1.0.rc1", :require => "active_support/all" gem "actionpack", "~> 5.1.0.rc1", :require => "action_pack" -gem "rubocop", "0.48.0" platforms :jruby do gem "activerecord-jdbcsqlite3-adapter" From 29bcc8478c1f25871559be283c987aa9524185aa Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Tue, 2 May 2017 12:05:04 +0200 Subject: [PATCH 20/30] use released rails 5.1 --- Appraisals | 8 ++++---- ...ecord_5.1.0.rc1.gemfile => activerecord_5.1.0.gemfile} | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) rename gemfiles/{activerecord_5.1.0.rc1.gemfile => activerecord_5.1.0.gemfile} (56%) diff --git a/Appraisals b/Appraisals index ef960dc6..1a41f355 100644 --- a/Appraisals +++ b/Appraisals @@ -31,10 +31,10 @@ appraise 'activerecord_5.0.2' do end end -appraise 'activerecord_5.1.0.rc1' do - gem 'activerecord', '~> 5.1.0.rc1', require: 'active_record' - gem 'activesupport', '~> 5.1.0.rc1', require: 'active_support/all' - gem 'actionpack', '~> 5.1.0.rc1', require: 'action_pack' +appraise 'activerecord_5.1.0' do + gem 'activerecord', '~> 5.1.0', require: 'active_record' + gem 'activesupport', '~> 5.1.0', require: 'active_support/all' + gem 'actionpack', '~> 5.1.0', require: 'action_pack' gemfile.platforms :jruby do gem 'activerecord-jdbcsqlite3-adapter' diff --git a/gemfiles/activerecord_5.1.0.rc1.gemfile b/gemfiles/activerecord_5.1.0.gemfile similarity index 56% rename from gemfiles/activerecord_5.1.0.rc1.gemfile rename to gemfiles/activerecord_5.1.0.gemfile index a9100ce4..2b136a9f 100644 --- a/gemfiles/activerecord_5.1.0.rc1.gemfile +++ b/gemfiles/activerecord_5.1.0.gemfile @@ -2,9 +2,9 @@ source "https://rubygems.org" -gem "activerecord", "~> 5.1.0.rc1", :require => "active_record" -gem "activesupport", "~> 5.1.0.rc1", :require => "active_support/all" -gem "actionpack", "~> 5.1.0.rc1", :require => "action_pack" +gem "activerecord", "~> 5.1.0", :require => "active_record" +gem "activesupport", "~> 5.1.0", :require => "active_support/all" +gem "actionpack", "~> 5.1.0", :require => "action_pack" platforms :jruby do gem "activerecord-jdbcsqlite3-adapter" From 0819e631b3b87da928b2d23a861017b0def4edc6 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Tue, 2 May 2017 13:40:30 +0200 Subject: [PATCH 21/30] use new gemfile --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index d8bc934e..4e70edd4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ rvm: gemfile: - gemfiles/activerecord_4.2.gemfile - gemfiles/activerecord_5.0.2.gemfile - - gemfiles/activerecord_5.1.0.rc1.gemfile + - gemfiles/activerecord_5.1.0.gemfile services: - mongodb matrix: @@ -30,9 +30,9 @@ matrix: - rvm: jruby-9.1.8.0 gemfile: gemfiles/activerecord_5.0.2.gemfile - rvm: jruby-9.0.5.0 - gemfile: gemfiles/activerecord_5.1.0.rc1.gemfile + gemfile: gemfiles/activerecord_5.1.0.gemfile - rvm: jruby-9.1.8.0 - gemfile: gemfiles/activerecord_5.1.0.rc1.gemfile + gemfile: gemfiles/activerecord_5.1.0.gemfile notifications: email: recipients: From f244634960abdf5a2053545e7a9d6ec83f4d4666 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Tue, 2 May 2017 22:55:45 +0200 Subject: [PATCH 22/30] exclude non working enviroments --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4e70edd4..3a83b68c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,9 +22,9 @@ matrix: - rvm: 2.2.7 gemfile: gemfiles/activerecord_5.0.2.gemfile - rvm: 2.2.0 - gemfile: gemfiles/activerecord_5.1.0.rc1.gemfile + gemfile: gemfiles/activerecord_5.1.0.gemfile - rvm: 2.2.7 - gemfile: gemfiles/activerecord_5.1.0.rc1.gemfile + gemfile: gemfiles/activerecord_5.1.0.gemfile - rvm: jruby-9.0.5.0 gemfile: gemfiles/activerecord_5.0.2.gemfile - rvm: jruby-9.1.8.0 From 070e866aa78e72726bb825592d6b4ce630fe7592 Mon Sep 17 00:00:00 2001 From: Darwin D Wu Date: Thu, 4 May 2017 17:55:37 -0700 Subject: [PATCH 23/30] don't proceed with an empty string --- lib/cancan/controller_resource.rb | 2 +- spec/cancan/controller_resource_spec.rb | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/cancan/controller_resource.rb b/lib/cancan/controller_resource.rb index ef7a3bb7..492584dd 100644 --- a/lib/cancan/controller_resource.rb +++ b/lib/cancan/controller_resource.rb @@ -128,7 +128,7 @@ def parent_authorization_action end def id_param - @params[id_param_key].to_s if @params[id_param_key] + @params[id_param_key].to_s if @params[id_param_key].present? end def id_param_key diff --git a/spec/cancan/controller_resource_spec.rb b/spec/cancan/controller_resource_spec.rb index 8b5c71dd..baedf28b 100644 --- a/spec/cancan/controller_resource_spec.rb +++ b/spec/cancan/controller_resource_spec.rb @@ -412,6 +412,13 @@ class Dashboard; end expect(controller.instance_variable_get(:@model)).to be_nil end + it 'does not try to load resource for other action if params[:id] is blank' do + params.merge!(action: 'list', id: '') + resource = CanCan::ControllerResource.new(controller) + resource.load_resource + expect(controller.instance_variable_get(:@model)).to be_nil + end + it 'finds record through has_one association with :singleton and :shallow options' do model = Model.new allow(Model).to receive(:find).with('123') { model } From 7a35bdb84314d2270c815fc7679c4f806545350b Mon Sep 17 00:00:00 2001 From: Thomas Hutterer Date: Tue, 9 May 2017 09:54:06 +0200 Subject: [PATCH 24/30] Add another "Can" to a leftover "CanCan" in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8570e717..dae40236 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ def update_params end ``` -For the `:create` action, CanCan will try to initialize a new instance with sanitized input by seeing if your +For the `:create` action, CanCanCan will try to initialize a new instance with sanitized input by seeing if your controller will respond to the following methods (in order): 1. `create_params` From 22b64eb50223a0cfb8fe1cac8f174272d771455f Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Thu, 18 May 2017 08:49:19 +0200 Subject: [PATCH 25/30] change travis config --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3a83b68c..2c9a4f53 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,10 +19,10 @@ matrix: exclude: - rvm: 2.2.0 gemfile: gemfiles/activerecord_5.0.2.gemfile - - rvm: 2.2.7 - gemfile: gemfiles/activerecord_5.0.2.gemfile - rvm: 2.2.0 gemfile: gemfiles/activerecord_5.1.0.gemfile + - rvm: 2.2.7 + gemfile: gemfiles/activerecord_5.0.2.gemfile - rvm: 2.2.7 gemfile: gemfiles/activerecord_5.1.0.gemfile - rvm: jruby-9.0.5.0 From ea3dc2222aa5f2aa486110cecfad6609ca308ea6 Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Thu, 18 May 2017 11:17:24 +0200 Subject: [PATCH 26/30] update README, version and gemspec --- README.md | 6 ++++-- cancancan.gemspec | 11 +++++------ lib/cancan/version.rb | 2 +- logo/cancancan.jpg | Bin 0 -> 50495 bytes 4 files changed, 10 insertions(+), 9 deletions(-) create mode 100644 logo/cancancan.jpg diff --git a/README.md b/README.md index 5098a40d..228a20fd 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # CanCanCan +![CanCanCan Logo](/logo/cancancan.jpg) + [![Gem Version](https://badge.fury.io/rb/cancancan.svg)](http://badge.fury.io/rb/cancancan) [![Travis badge](https://travis-ci.org/CanCanCommunity/cancancan.svg?branch=develop)](https://travis-ci.org/CanCanCommunity/cancancan) [![Code Climate Badge](https://codeclimate.com/github/CanCanCommunity/cancancan.svg)](https://codeclimate.com/github/CanCanCommunity/cancancan) @@ -25,7 +27,7 @@ and run the `bundle install` command. For Rails < 4.2 use: - gem 'cancancan', '~> 1.10' + gem 'cancancan', '~> 1.10' ## Version 2.0 @@ -35,7 +37,7 @@ Please use `gem 'cancancan', '~> 1.10'` for them. If you are interested in supporting them, contribute to the sibling gems `cancancan-sequel` and `cancancan-mongoid`. -Version 2.0 drops also support for Rails < 4.2 and ruby < 2.2 so again use the version 1 of the Gem for these. +Version 2.0 drops also support for Rails < 4.2 and ruby < 2.2 so, again, use the version 1 of the Gem for these. ## Getting Started diff --git a/cancancan.gemspec b/cancancan.gemspec index 20e0cae3..7f21c568 100644 --- a/cancancan.gemspec +++ b/cancancan.gemspec @@ -15,16 +15,15 @@ Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.license = 'MIT' - s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR) - s.test_files = `git ls-files -- Appraisals {spec,features,gemfiles}/*`.split($INPUT_RECORD_SEPARATOR) - s.executables = `git ls-files -- bin/*`.split($INPUT_RECORD_SEPARATOR).map { |f| File.basename(f) } + # s.files = `git ls-files lib init.rb`.split($INPUT_RECORD_SEPARATOR) + s.files = `git ls-files lib init.rb cancancan.gemspec`.split($INPUT_RECORD_SEPARATOR) s.require_paths = ['lib'] s.required_ruby_version = '>= 2.0.0' s.add_development_dependency 'bundler', '~> 1.3' s.add_development_dependency 'rubocop', '~> 0.46' - s.add_development_dependency 'rake', '~> 10.1.1' - s.add_development_dependency 'rspec', '~> 3.2.0' - s.add_development_dependency 'appraisal', '>= 2.0.0' + s.add_development_dependency 'rake', '~> 10.1', '>= 10.1.1' + s.add_development_dependency 'rspec', '~> 3.2', '>= 3.2.0' + s.add_development_dependency 'appraisal', '~> 2.0', '>= 2.0.0' end diff --git a/lib/cancan/version.rb b/lib/cancan/version.rb index 79171d34..f33b629b 100644 --- a/lib/cancan/version.rb +++ b/lib/cancan/version.rb @@ -1,3 +1,3 @@ module CanCan - VERSION = '1.17.0'.freeze + VERSION = '2.0.0.rc1'.freeze end diff --git a/logo/cancancan.jpg b/logo/cancancan.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8002610236a79ded1ff4fa4c5eb53640807b299d GIT binary patch literal 50495 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 Z8P~-5&UeCop?9$mj)CeMAr$}rn*f&5s Date: Thu, 18 May 2017 11:17:57 +0200 Subject: [PATCH 27/30] remove commented line --- cancancan.gemspec | 1 - 1 file changed, 1 deletion(-) diff --git a/cancancan.gemspec b/cancancan.gemspec index 7f21c568..92ce5c2a 100644 --- a/cancancan.gemspec +++ b/cancancan.gemspec @@ -15,7 +15,6 @@ Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.license = 'MIT' - # s.files = `git ls-files lib init.rb`.split($INPUT_RECORD_SEPARATOR) s.files = `git ls-files lib init.rb cancancan.gemspec`.split($INPUT_RECORD_SEPARATOR) s.require_paths = ['lib'] From fb7d1f55dd95402064f48babdfebe2b9b2cb903b Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Thu, 18 May 2017 11:44:01 +0200 Subject: [PATCH 28/30] release version 2.0.0 --- README.md | 10 ++++++++-- lib/cancan/version.rb | 2 +- logo/renuo.png | Bin 0 -> 7504 bytes 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 logo/renuo.png diff --git a/README.md b/README.md index 228a20fd..f2b6b43c 100644 --- a/README.md +++ b/README.md @@ -243,8 +243,14 @@ See the [CONTRIBUTING](https://github.com/CanCanCommunity/cancancan/blob/develop ## Special Thanks -CanCanCan was inspired by [declarative_authorization](https://github.com/stffn/declarative_authorization/) and -[aegis](https://github.com/makandra/aegis). +![Renuo AG](/logo/renuo.png) + +Thanks to [Renuo](https://www.renuo.ch) for currently maintaining and supporting the project. Also many thanks to the [CanCanCan contributors](https://github.com/CanCanCommunity/cancancan/contributors). See the [CHANGELOG](https://github.com/CanCanCommunity/cancancan/blob/master/CHANGELOG.rdoc) for the full list. + +CanCanCan was inspired by [declarative_authorization](https://github.com/stffn/declarative_authorization/) and +[aegis](https://github.com/makandra/aegis). + + diff --git a/lib/cancan/version.rb b/lib/cancan/version.rb index f33b629b..332d4915 100644 --- a/lib/cancan/version.rb +++ b/lib/cancan/version.rb @@ -1,3 +1,3 @@ module CanCan - VERSION = '2.0.0.rc1'.freeze + VERSION = '2.0.0'.freeze end diff --git a/logo/renuo.png b/logo/renuo.png new file mode 100644 index 0000000000000000000000000000000000000000..2524bcfa439dd88ed65bf1de2ed99384cda6cf2e GIT binary patch literal 7504 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 a;r{?*|KnGvsr--t0000 Date: Thu, 18 May 2017 11:45:02 +0200 Subject: [PATCH 29/30] add a link to renuo website --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f2b6b43c..55f54a11 100644 --- a/README.md +++ b/README.md @@ -243,7 +243,7 @@ See the [CONTRIBUTING](https://github.com/CanCanCommunity/cancancan/blob/develop ## Special Thanks -![Renuo AG](/logo/renuo.png) +[![Renuo AG](/logo/renuo.png)](https://www.renuo.ch) Thanks to [Renuo](https://www.renuo.ch) for currently maintaining and supporting the project. From 1a38442f4fe0e6f2db2229c98cded9dd6a8971db Mon Sep 17 00:00:00 2001 From: Alessandro Rodi Date: Thu, 18 May 2017 11:46:01 +0200 Subject: [PATCH 30/30] update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 55f54a11..45141b93 100644 --- a/README.md +++ b/README.md @@ -235,7 +235,7 @@ of Rails, as well as the different model adapters. When first developing, you may need to run `bundle install` and then `appraisal install`, to install the different sets. -You can then run all appraisal files (like CI does), with `appraisal rake` or just run a specific set `appraisal activerecord_3.0 rake`. +You can then run all appraisal files (like CI does), with `appraisal rake` or just run a specific set `appraisal activerecord_5.0 rake`. See the [CONTRIBUTING](https://github.com/CanCanCommunity/cancancan/blob/develop/CONTRIBUTING.md) and [spec/README](https://github.com/CanCanCommunity/cancancan/blob/master/spec/README.rdoc) for more information.