├── .gitignore ├── .hound.yml ├── .rubocop.yml ├── .rubocop_todo.yml ├── .travis.yml ├── Gemfile ├── MIT-LICENSE ├── README.md ├── Rakefile ├── TODO ├── canard.gemspec ├── lib ├── ability.rb ├── canard.rb ├── canard │ ├── abilities.rb │ ├── adapters │ │ ├── active_record.rb │ │ └── mongoid.rb │ ├── find_abilities.rb │ ├── railtie.rb │ ├── user_model.rb │ └── version.rb ├── generators │ ├── ability_definition.rb │ ├── canard │ │ └── ability │ │ │ ├── USAGE │ │ │ ├── ability_generator.rb │ │ │ └── templates │ │ │ └── abilities.rb.erb │ └── rspec │ │ └── ability │ │ ├── ability_generator.rb │ │ └── templates │ │ └── abilities_spec.rb.erb └── tasks │ └── canard.rake └── test ├── abilities └── administrators.rb ├── canard ├── abilities_test.rb ├── ability_test.rb ├── adapters │ ├── active_record_test.rb │ └── mongoid_test.rb ├── canard_test.rb ├── find_abilities_test.rb └── user_model_test.rb ├── dummy ├── Rakefile ├── app │ ├── abilities │ │ ├── admins.rb │ │ ├── authors.rb │ │ ├── editors.rb │ │ ├── guests.rb │ │ └── users.rb │ ├── controllers │ │ └── application_controller.rb │ └── models │ │ ├── activity.rb │ │ ├── member.rb │ │ ├── mongoid_user.rb │ │ ├── plain_ruby_non_user.rb │ │ ├── plain_ruby_user.rb │ │ ├── post.rb │ │ ├── user.rb │ │ ├── user_without_role.rb │ │ └── user_without_role_mask.rb ├── config.ru ├── config │ ├── application.rb │ ├── boot.rb │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ └── test.rb │ ├── initializers │ │ ├── secret_token.rb │ │ ├── session_store.rb │ │ └── wrap_parameters.rb │ ├── locales │ │ └── en.yml │ ├── mongoid3.yml │ └── routes.rb ├── db │ ├── migrate │ │ └── 20120430083231_initialize_db.rb │ └── schema.rb ├── log │ └── .gitkeep └── script │ └── rails ├── support └── reloadable.rb └── test_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.gem 3 | .bundle 4 | pkg/* 5 | .rvmrc 6 | .ruby-version 7 | .ruby-gemset 8 | Gemfile.lock 9 | *.sqlite3 10 | *.log 11 | .tm_properties 12 | .byebug_history -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | ruby: 2 | config_file: .rubocop.yml 3 | 4 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | Style/StringLiterals: 4 | Enabled: true 5 | EnforcedStyle: single_quotes 6 | Style/MutableConstant: 7 | Exclude: 8 | - 'lib/seedbank/version.rb' 9 | Lint/ScriptPermission: 10 | Exclude: 11 | - 'test/dummy/Rakefile' 12 | Metrics/LineLength: 13 | Exclude: 14 | - 'test/**/*' 15 | Max: 132 16 | Metrics/BlockLength: 17 | Exclude: 18 | - 'test/**/*' 19 | Naming/PredicateName: 20 | NameWhitelist: 21 | - 'has_roles_mask_accessors?' 22 | Style/Documentation: 23 | Exclude: 24 | - 'spec/**/*' 25 | - 'test/**/*' 26 | - 'lib/generators/rspec/ability/ability_generator.rb' 27 | 28 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2019-10-02 18:57:17 +0700 using RuboCop version 0.75.0. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 4 10 | Metrics/AbcSize: 11 | Max: 24 12 | 13 | # Offense count: 1 14 | Metrics/CyclomaticComplexity: 15 | Max: 11 16 | 17 | # Offense count: 4 18 | # Configuration parameters: CountComments, ExcludedMethods. 19 | Metrics/MethodLength: 20 | Max: 13 21 | 22 | # Offense count: 1 23 | Metrics/PerceivedComplexity: 24 | Max: 12 25 | 26 | # Offense count: 1 27 | Style/ClassVars: 28 | Exclude: 29 | - 'lib/generators/ability_definition.rb' 30 | 31 | # Offense count: 1 32 | # Cop supports --auto-correct. 33 | # Configuration parameters: EnforcedStyle. 34 | # SupportedStyles: line_count_dependent, lambda, literal 35 | Style/Lambda: 36 | Exclude: 37 | - 'lib/canard/adapters/mongoid.rb' 38 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.0 4 | - 2.1 5 | - 2.2 6 | - 2.3.3 7 | - 2.4.0 8 | - jruby 9 | services: 10 | - mongodb 11 | 12 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'http://rubygems.org' 4 | 5 | # Specify your gem's dependencies in canard.gemspec 6 | gemspec 7 | 8 | group :test do 9 | gem 'rake' 10 | end 11 | 12 | # for CRuby, Rubinius, including Windows and RubyInstaller 13 | group :development, :test do 14 | gem 'bson', '~> 1.6.4' 15 | gem 'rubocop' 16 | 17 | platform :ruby, :mswin, :mingw do 18 | gem 'bson_ext', '~> 1.6.4' 19 | gem 'sqlite3', '~> 1.3.5' 20 | end 21 | 22 | platform :jruby do 23 | gem 'activerecord-jdbcsqlite3-adapter' 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 [name of plugin creator] 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Canard 2 | ====== 3 | [![Build Status](https://travis-ci.org/james2m/canard.svg?branch=master)](https://travis-ci.org/james2m/canard) 4 | 5 | Canard brings CanCan and RoleModel together to make role-based authorization in Rails easy. Your ability 6 | definitions gain their own folder and a little structure. The easiest way to get started is with the 7 | Canard generator. Canard progressively enhances the abilities of the model by applying role abilities on 8 | top of the model's base abilities. 9 | A User model with :admin and :manager roles would be defined: 10 | 11 | class User < ActiveRecord::Base 12 | 13 | acts_as_user :roles => [ :manager, :admin ] 14 | 15 | end 16 | 17 | If a User has both the :manager and :admin roles, Canard looks first for user abilities. Then it will look for other roles in the order that they are defined: 18 | 19 | app/abilities/users.rb 20 | app/abilities/manager.rb 21 | app/abilities/admin.rb 22 | 23 | Therefore each of the later abilities can build on its predecessor. 24 | 25 | Usage 26 | ===== 27 | To generate some abilities for the User: 28 | 29 | $ rails g canard:ability user can:[read,create]:[account,statement] cannot:destroy:account 30 | create app/abilities/users.rb 31 | invoke rspec 32 | create spec/abilities/user_spec.rb 33 | 34 | This action generates an ability folder in Rails root and an associated spec: 35 | 36 | app.abilities/ 37 | users.rb 38 | spec/abilities/ 39 | users_spec.rb 40 | 41 | The resulting app/abilities/users.rb will look something like this: 42 | 43 | Canard::Abilities.for(:user) do 44 | 45 | can [:read, :create], Account 46 | cannot [:destroy], Account 47 | can [:read, :create], Statement 48 | 49 | end 50 | 51 | And its associated test spec/abilities/users_spec.rb will look something like this: 52 | 53 | require_relative '../spec_helper' 54 | require "cancan/matchers" 55 | 56 | describe Ability, "for :user" do 57 | 58 | before do 59 | @user = Factory.create(:user_user) 60 | end 61 | 62 | subject { Ability.new(@user) } 63 | 64 | describe 'on Account' do 65 | 66 | before do 67 | @account = Factory.create(:account) 68 | end 69 | 70 | it { should be_able_to( :read, @account ) } 71 | it { should be_able_to( :create, @account ) } 72 | it { should_not be_able_to( :destroy, @account ) } 73 | 74 | end 75 | # on Account 76 | 77 | describe 'on Statement' do 78 | 79 | before do 80 | @statement = Factory.create(:statement) 81 | end 82 | 83 | it { should be_able_to( :read, @statement ) } 84 | it { should be_able_to( :create, @statement ) } 85 | 86 | end 87 | # on Statement 88 | 89 | end 90 | 91 | You can also re-use abilities defined for one role in another. This allows you to 'inherit' abilities without having to assign all of the roles to the user. To do this, pass a list of role names to the includes_abilities_of method: 92 | 93 | Canard::Abilities.for(:writer) do 94 | 95 | can [:create], Post 96 | can [:read], Post, user_id: user.id 97 | 98 | end 99 | 100 | Canard::Abilities.for(:reviewer) do 101 | 102 | can [:read, :update], Post 103 | 104 | end 105 | 106 | Canard::Abilities.for(:admin) do 107 | 108 | includes_abilities_of :writer, :reviewer 109 | 110 | can [:delete], Post 111 | 112 | end 113 | 114 | A user assigned the :admin role will have all of the abilities of the :writer and :reviewer, along with their own abilities, without having to have those individual roles assigned to them. 115 | 116 | Now let's generate some abilities for the manager and admin: 117 | 118 | $ rails g canard:ability admin can:manage:[account,statement] 119 | $ rails g canard:ability manager can:edit:statement 120 | 121 | This generates two new sets of abilities in the abilities folder. Canard will apply these abilities by first 122 | loading the ability for the User model and then applying the abilities for each of the current user's roles. 123 | 124 | 125 | If there is no user (i.e. logged out), Canard creates a guest and looks for a guest ability to apply: 126 | 127 | $ rails g canard:ability guest can:create:user 128 | 129 | This would generate a signup ability for a user who was not logged in. 130 | 131 | Obviously the generators are just a starting point and should be used only to get you going. I strongly 132 | suggest that you add each new model to the abilities because the specs are easy to write and CanCan 133 | definitions are very clear and simple. 134 | 135 | Scopes 136 | ====== 137 | The :acts_as_user method will automatically define some named scopes for each role. For the User model 138 | above it will define the following scopes: 139 | 140 | | Scope | Returns | 141 | | ------------------- | --------------------------------------- | 142 | | `User.admins` | all the users with the admin role | 143 | | `User.non_admins` | all the users without the admin role | 144 | | `User.managers` | all the users with the manager role | 145 | | `User.non_managers` | all the users without the manager role | 146 | 147 | In addition to the role specific scopes it also adds some general scopes: 148 | 149 | | Scope | Returns | 150 | | ------------------- | --------------------------------------------- | 151 | | `User.with_any_role(roles)` | all the users with any of the specified roles | 152 | | `User.with_all_roles(roles)` | only the users with all the specified roles | 153 | 154 | Installation 155 | ============ 156 | 157 | Rails 3.x, 4.x & 5.x 158 | -------------------- 159 | Add the canard gem to your Gemfile: 160 | 161 | gem "canard" 162 | 163 | Add the `roles_mask` field to your user table: 164 | 165 | rails g migration add_roles_mask_to_users roles_mask:integer 166 | rake db:migrate 167 | 168 | That's it! 169 | 170 | Rails 2.x 171 | --------- 172 | 173 | Sorry, you are out of luck. Canard has only been written and tested with Rails 3 and above. 174 | 175 | Supported ORMs 176 | --------------- 177 | 178 | Canard is ORM agnostic. ActiveRecord and Mongoid (thanks David Butler) adapters are currently implemented. 179 | New adapters can easily be added, but you'd need to check to see if CanCan can also support your adapter. 180 | 181 | Further reading 182 | --------------- 183 | 184 | Canard stands on the shoulders of Ryan Bates' CanCan and Martin Rehfeld's RoleModel. You can read more 185 | about defining abilities on the CanCan wiki (https://github.com/ryanb/cancan/wiki). Canard implements 186 | the Ability class for you so you don't need the boilerplate code from Ryan's example: 187 | 188 | class Ability 189 | include CanCan::Ability 190 | 191 | def initialize(user) 192 | user ||= User.new # guest user (not logged in) 193 | if user.admin? 194 | can :manage, :all 195 | else 196 | can :read, :all 197 | end 198 | end 199 | end 200 | 201 | The Canard equivalent for non-admins would be: 202 | 203 | Canard::Abilities.for(:user) do 204 | can :read, :all 205 | end 206 | 207 | And for admins: 208 | 209 | Canard::Abilities.for(:admin) do 210 | can :manage, :all 211 | end 212 | 213 | Under the covers Canard uses RoleModel (https://github.com/martinrehfeld/role_model) to define roles. RoleModel 214 | is based on Ryan Bates' suggested approach to role based authorization which is documented in the CanCan 215 | wiki (https://github.com/ryanb/cancan/wiki/role-based-authorization). 216 | 217 | Note on Patches/Pull Request 218 | ---------------------------- 219 | 220 | * Fork the project. 221 | * Make your feature addition or bug fix. 222 | * Add tests for it (when I have some). This is important so I don't break it in a future version unintentionally. 223 | * Commit. Do not mess with rakefile, version, or history. (If you want to have your own version, that is fine but 224 | bump version in a commit by itself so I can ignore it when I pull.) 225 | * Send me a pull request. Bonus points for topic branches. 226 | 227 | Contributors 228 | ------------ 229 | 230 | git log | grep Author | sort | uniq 231 | 232 | * Alessandro Dal Grande 233 | * David Butler 234 | * Dmitriy Molodtsov 235 | * Dmytro Salko 236 | * James McCarthy 237 | * Jesse McGinnis 238 | * Joey Geiger 239 | * Jon Kinney 240 | * Justin Buchanan 241 | * Morton Jonuschat 242 | * Piotr Kuczynski 243 | * Thomas Hoen 244 | * Travis Berry 245 | 246 | If you feel like contributing there is a TODO list in the root with a few ideas and opportunities! 247 | 248 | Credits 249 | ------- 250 | 251 | Thanks to Ryan Bates for creating the awesome CanCan (http://wiki.github.com/ryanb/cancan) 252 | and Martin Rehfeld for implementing Role Based Authorization in the form of RoleModel (http://github.com/martinrehfeld/role_model). 253 | 254 | Copyright 255 | --------- 256 | Copyright (c) 2011-2017 James McCarthy, released under the MIT license 257 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/gem_tasks' 4 | require 'rake' 5 | require 'rake/testtask' 6 | 7 | Rake::TestTask.new(:test) do |t| 8 | t.libs << 'lib' << 'test' 9 | t.pattern = 'test/**/*_test.rb' 10 | t.verbose = true 11 | t.warning = false 12 | end 13 | 14 | task default: ['test'] 15 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | 0.5.0 2 | * Split the test suite so Rails is only required for Rails integration. http://blog.railsware.com/2012/01/07/testing-gem-integration-with-multiple-ruby-frameworks/ 3 | * Test the railtie (currently implicity tested in dummy app). 4 | * Expand the generated tests to produce all the standard abilities: index,show,read,new,create,edit,update,destroy. 5 | * Test the generators. 6 | * Add test unit generator. 7 | * Add install generator to allow overriding of the default tests. 8 | * Make the Ability user referece configureable in Ability. 9 | * Add DataMapper support. 10 | * Detect and use FactoryGirl syntax in generated spec. 11 | * Stop extending ActiveRecord, use composition instead. -------------------------------------------------------------------------------- /canard.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | $LOAD_PATH.push File.expand_path('lib', __dir__) 4 | require 'canard/version' 5 | 6 | Gem::Specification.new do |s| 7 | s.name = 'canard' 8 | s.version = Canard::VERSION 9 | s.date = `git log -1 --format="%cd" --date=short lib/canard/version.rb` 10 | s.authors = ['James McCarthy'] 11 | s.email = ['james2mccarthy@gmail.com'] 12 | s.homepage = 'https://github.com/james2m/canard' 13 | s.summary = 'Adds role based authorisation to Rails by combining RoleModel and CanCanCan.' 14 | s.description = 'Wraps CanCanCan and RoleModel up to make role based authorisation really easy in Rails 4+.' 15 | 16 | s.rubyforge_project = 'canard' 17 | 18 | s.files = `git ls-files`.split("\n") 19 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 20 | s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) } 21 | s.require_paths = ['lib'] 22 | 23 | s.add_development_dependency 'minitest', '~> 2' 24 | s.add_development_dependency 'mongoid', '~> 3.0' 25 | s.add_development_dependency 'rails', '~> 3.2', '>= 3.2' 26 | 27 | s.requirements << "cancan's community supported Rails4+ compatible cancancan fork." 28 | s.add_runtime_dependency 'cancancan', '>= 1', '< 3.0' 29 | s.add_runtime_dependency 'role_model', '~> 0' 30 | end 31 | -------------------------------------------------------------------------------- /lib/ability.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Canard provides a CanCan Ability class for you. The Canard Ability class 4 | # looks for and applies abilities for the object passed when a new Ability 5 | # instance is initialized. 6 | # 7 | # If the passed object has a reference to user the user is set to that. 8 | # Otherwise the passed object is assumed to be the user. This gives the 9 | # flexibility to have a seperate model for authorization from the model used 10 | # to authenticate the user. 11 | # 12 | # Abilities are applied in the order they are set with the acts_as_user method 13 | # for example for the User model 14 | # 15 | # class User < ActiveRecord::Base 16 | # 17 | # acts_as_user :roles => :manager, :admin 18 | # 19 | # end 20 | # 21 | # the abilities would be applied in the order: users, managers, admins 22 | # with each subsequent set of abilities building on or overriding the existing 23 | # abilities. 24 | # 25 | # If there is no object passed to the Ability.new method a guest ability is 26 | # created and Canard will look for a guests.rb amongst the ability definitions 27 | # and give the guest those abilities. 28 | class Ability 29 | include CanCan::Ability 30 | extend Forwardable 31 | 32 | attr_reader :user 33 | 34 | def_delegators :Canard, :ability_definitions, :ability_key 35 | 36 | def initialize(object = nil) 37 | # If object has a user attribute set the user from it otherwise assume 38 | # this is the user. 39 | @user = object.respond_to?(:user) ? object.user : object 40 | 41 | add_base_abilities 42 | add_roles_abilities if @user.respond_to?(:roles) 43 | end 44 | 45 | protected 46 | 47 | def add_base_abilities 48 | if @user 49 | # Add the base user abilities. 50 | user_class_name = String(@user.class.name) 51 | append_abilities user_class_name unless user_class_name.empty? 52 | else 53 | # If user not set then lets create a guest 54 | @user = Object.new 55 | append_abilities :guest 56 | end 57 | end 58 | 59 | def add_roles_abilities 60 | @user.roles.each { |role| append_abilities(role) } 61 | end 62 | 63 | def append_abilities(dirty_key) 64 | key = ability_key(dirty_key) 65 | instance_eval(&ability_definitions[key]) if ability_definitions.key?(key) 66 | end 67 | 68 | def includes_abilities_of(*other_roles) 69 | other_roles.each { |other_role| append_abilities(other_role) } 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /lib/canard.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'forwardable' 4 | require 'cancan' 5 | require 'role_model' 6 | require 'canard/abilities' 7 | require 'canard/version' 8 | require 'canard/user_model' 9 | require 'canard/find_abilities' 10 | require 'ability' 11 | 12 | require 'canard/railtie' if defined?(Rails) && Rails::VERSION::MAJOR >= 3 13 | -------------------------------------------------------------------------------- /lib/canard/abilities.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Canard 4 | class Abilities # :nodoc: 5 | @definitions = {} 6 | @default_path = 'app/abilities' 7 | 8 | class << self 9 | extend Forwardable 10 | 11 | def_delegators :Canard, :ability_key 12 | 13 | attr_accessor :default_path 14 | 15 | attr_writer :definition_paths 16 | 17 | attr_reader :definitions 18 | 19 | def definition_paths 20 | @definition_paths ||= [@default_path] 21 | end 22 | 23 | def for(name, &block) 24 | raise ArgumentError, 'No block of ability definitions given' unless block_given? 25 | 26 | key = ability_key(name) 27 | @definitions[key] = block 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/canard/adapters/active_record.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Canard 4 | module Adapters 5 | module ActiveRecord # :nodoc: 6 | private 7 | 8 | def add_role_scopes(**options) 9 | define_scopes(options) if active_record_table_exists? 10 | end 11 | 12 | def define_scopes(options) 13 | valid_roles.each do |role| 14 | define_scopes_for_role role, options[:prefix] 15 | end 16 | 17 | # TODO: change hard coded :role_mask to roles_attribute_name 18 | define_singleton_method(:with_any_role) do |*roles| 19 | where("#{role_mask_column} & :role_mask > 0", role_mask: mask_for(*roles)) 20 | end 21 | 22 | define_singleton_method(:with_all_roles) do |*roles| 23 | where("#{role_mask_column} & :role_mask = :role_mask", role_mask: mask_for(*roles)) 24 | end 25 | 26 | define_singleton_method(:with_only_roles) do |*roles| 27 | where("#{role_mask_column} = :role_mask", role_mask: mask_for(*roles)) 28 | end 29 | end 30 | 31 | def active_record_table_exists? 32 | respond_to?(:table_exists?) && table_exists? 33 | rescue ::ActiveRecord::NoDatabaseError, StandardError 34 | false 35 | end 36 | 37 | # TODO: extract has_roles_attribute? and change to has_roles_attribute? || super 38 | def has_roles_mask_accessors? 39 | active_record_table_exists? && column_names.include?(roles_attribute_name.to_s) || super 40 | end 41 | 42 | def define_scopes_for_role(role, prefix = nil) 43 | include_scope = [prefix, String(role).pluralize].compact.join('_') 44 | exclude_scope = "non_#{include_scope}" 45 | 46 | define_singleton_method(include_scope) do 47 | where("#{role_mask_column} & :role_mask > 0", role_mask: mask_for(role)) 48 | end 49 | 50 | define_singleton_method(exclude_scope) do 51 | where("#{role_mask_column} & :role_mask = 0 or #{role_mask_column} is null", role_mask: mask_for(role)) 52 | end 53 | end 54 | 55 | def role_mask_column 56 | "#{quoted_table_name}.#{connection.quote_column_name roles_attribute_name}" 57 | end 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/canard/adapters/mongoid.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Canard 4 | module Adapters 5 | module Mongoid # :nodoc: 6 | private 7 | 8 | def add_role_scopes(*args) 9 | options = args.extract_options! 10 | valid_roles.each do |role| 11 | define_scopes_for_role role, options[:prefix] 12 | end 13 | 14 | define_singleton_method(:with_any_role) do |*roles| 15 | where("(this.#{roles_attribute_name} & #{mask_for(*roles)}) > 0") 16 | end 17 | 18 | define_singleton_method(:with_all_roles) do |*roles| 19 | where("(this.#{roles_attribute_name} & #{mask_for(*roles)}) === #{mask_for(*roles)}") 20 | end 21 | 22 | define_singleton_method(:with_only_roles) do |*roles| 23 | where("this.#{roles_attribute_name} === #{mask_for(*roles)}") 24 | end 25 | end 26 | 27 | def has_roles_mask_accessors? 28 | fields.include?(roles_attribute_name.to_s) || super 29 | end 30 | 31 | def define_scopes_for_role(role, prefix = nil) 32 | include_scope = [prefix, String(role).pluralize].compact.join('_') 33 | exclude_scope = "non_#{include_scope}" 34 | 35 | scope include_scope, -> { where("(this.#{roles_attribute_name} & #{mask_for(role)}) > 0") } 36 | scope exclude_scope, lambda { 37 | any_of( 38 | { roles_attribute_name => { '$exists' => false } }, 39 | { roles_attribute_name => nil }, 40 | '$where' => "(this.#{roles_attribute_name} & #{mask_for(role)}) === 0" 41 | ) 42 | } 43 | end 44 | end 45 | end 46 | end 47 | 48 | Mongoid::Document::ClassMethods.send(:include, Canard::Adapters::Mongoid) 49 | Mongoid::Document::ClassMethods.send(:include, Canard::UserModel) 50 | Canard.find_abilities 51 | -------------------------------------------------------------------------------- /lib/canard/find_abilities.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Canard # :nodoc: 4 | def self.ability_definitions 5 | Abilities.definitions 6 | end 7 | 8 | def self.ability_key(class_name) 9 | String(class_name) 10 | .gsub('::', '') 11 | .gsub(/(.)([A-Z])/, '\1_\2') 12 | .downcase 13 | .to_sym 14 | end 15 | 16 | def self.load_paths 17 | Abilities.definition_paths.map { |path| File.expand_path(path) } 18 | end 19 | 20 | def self.find_abilities #:nodoc: 21 | load_paths.each do |path| 22 | Dir[File.join(path, '**', '*.rb')].sort.each do |file| 23 | load file 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/canard/railtie.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Canard 4 | class Railtie < Rails::Railtie # :nodoc: 5 | initializer 'canard.no_eager_loading', before: 'before_eager_loading' do |app| 6 | ActiveSupport::Dependencies.autoload_paths.reject! { |path| Canard.load_paths.include?(path) } 7 | # Don't eagerload our configs, we'll deal with them ourselves 8 | app.config.eager_load_paths = app.config.eager_load_paths.reject do |path| 9 | Canard.load_paths.include?(path) 10 | end 11 | 12 | app.config.watchable_dirs.merge! Hash[Canard.load_paths.product([[:rb]])] if app.config.respond_to?(:watchable_dirs) 13 | end 14 | 15 | initializer 'canard.active_record' do |_app| 16 | ActiveSupport.on_load :active_record do 17 | require 'canard/adapters/active_record' 18 | Canard::Abilities.default_path = File.expand_path('app/abilities', Rails.root) 19 | extend Canard::UserModel 20 | Canard.find_abilities 21 | end 22 | end 23 | 24 | initializer 'canard.mongoid' do |_app| 25 | require 'canard/adapters/mongoid' if defined?(Mongoid) 26 | end 27 | 28 | initializer 'canard.abilities_reloading', after: 'action_dispatch.configure' do |_app| 29 | reloader = rails5? ? ActiveSupport::Reloader : ActionDispatch::Reloader 30 | reloader.to_prepare { Canard.find_abilities } if reloader.respond_to?(:to_prepare) 31 | end 32 | 33 | rake_tasks do 34 | load File.expand_path('../tasks/canard.rake', __dir__) 35 | end 36 | 37 | private 38 | 39 | def rails5? 40 | ActionPack::VERSION::MAJOR == 5 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/canard/user_model.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Canard 4 | # Canard applies roles to a model using the acts_as_user class method. The following User model 5 | # will be given the :manager and :admin roles 6 | # 7 | # class User < ActiveRecord::Base 8 | # 9 | # acts_as_user :roles => [:manager, :admin] 10 | # 11 | # end 12 | # 13 | # If using Canard with a non ActiveRecord class you can still assign roles but you will need to 14 | # extend the class with Canard::UserModel and add a roles_mask attribute. 15 | # 16 | # class User 17 | # 18 | # extend Canard::UserModel 19 | # 20 | # attr_accessor :roles_mask 21 | # 22 | # acts_as_user :roles => [:manager, :admin] 23 | # 24 | # end 25 | # 26 | # You can choose the attribute used for the roles_mask by specifying :roles_mask. If no 27 | # roles_mask is specified it uses RoleModel's default of 'roles_mask' 28 | # 29 | # acts_as_user :roles_mask => :my_roles_mask, :roles => [:manager, :admin] 30 | # 31 | # == Scopes 32 | # 33 | # Beyond applying the roles to the model, acts_as_user also creates some useful scopes for 34 | # ActiveRecord models; 35 | # 36 | # User.with_any_role(:manager, :admin) 37 | # 38 | # returns all the managers and admins 39 | # 40 | # User.with_all_roles(:manager, :admin) 41 | # 42 | # returns only the users with both the manager and admin roles 43 | # 44 | # User.admins 45 | # 46 | # returns all the admins as 47 | # 48 | # User.managers 49 | # 50 | # returns all the users with the maager role likewise 51 | # 52 | # User.non_admins 53 | # 54 | # returns all the users who don't have the admin role and 55 | # 56 | # User.non_managers 57 | # 58 | # returns all the users who don't have the manager role. 59 | module UserModel 60 | def acts_as_user(*args) 61 | include RoleModel 62 | include InstanceMethods 63 | 64 | options = args.last.is_a?(Hash) ? args.pop : {} 65 | 66 | if defined?(ActiveRecord) && self < ActiveRecord::Base 67 | extend Adapters::ActiveRecord 68 | elsif defined?(Mongoid) && included_modules.include?(Mongoid::Document) 69 | extend Adapters::Mongoid 70 | field (options[:roles_mask] || :roles_mask), type: Integer 71 | end 72 | 73 | roles_attribute options[:roles_mask] if options.key?(:roles_mask) 74 | 75 | roles options[:roles] if options.key?(:roles) && has_roles_mask_accessors? 76 | 77 | add_role_scopes(prefix: options[:prefix]) if respond_to?(:add_role_scopes, true) 78 | end 79 | 80 | private 81 | 82 | # This is overridden by the ActiveRecord adapter as the attribute accessors 83 | # don't show up in instance_methods. 84 | def has_roles_mask_accessors? 85 | instance_method_names = instance_methods.map(&:to_s) 86 | [roles_attribute_name.to_s, "#{roles_attribute_name}="].all? do |accessor| 87 | instance_method_names.include?(accessor) 88 | end 89 | end 90 | 91 | module InstanceMethods # :nodoc: 92 | def ability 93 | @ability ||= Ability.new(self) 94 | end 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /lib/canard/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Canard 4 | VERSION = '0.6.2.pre' 5 | end 6 | -------------------------------------------------------------------------------- /lib/generators/ability_definition.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'active_support/inflector' 4 | 5 | class AbilityDefinition # :nodoc: 6 | attr_accessor :cans, :cannots 7 | 8 | def self.parse(definitions) 9 | @@ability_definitions ||= {} 10 | limitation, ability_list, model_list = definitions.split(':') 11 | ability_names = extract(ability_list) 12 | model_names = extract(model_list) 13 | model_names.each do |model_name| 14 | definition = @@ability_definitions[model_name] || AbilityDefinition.new 15 | definition.merge(limitation.pluralize, ability_names) 16 | @@ability_definitions[model_name] = definition 17 | end 18 | end 19 | 20 | def self.extract(string) 21 | string.gsub(/[\[\]\s]/, '').split(',') 22 | end 23 | 24 | def self.models 25 | @@ability_definitions 26 | end 27 | 28 | def initialize 29 | @cans = [] 30 | @cannots = [] 31 | end 32 | 33 | def merge(limitation, ability_names) 34 | combined_ability_names = instance_variable_get("@#{limitation}") | ability_names 35 | instance_variable_set("@#{limitation}", combined_ability_names) 36 | end 37 | 38 | def can 39 | @cans 40 | end 41 | 42 | def cannot 43 | @cannots 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/generators/canard/ability/USAGE: -------------------------------------------------------------------------------- 1 | Description: 2 | The canard:ability generator creates an Ability definition in the abilities 3 | directory. It also creates the shell of a spec for testing those abilities. 4 | -------------------------------------------------------------------------------- /lib/generators/canard/ability/ability_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../../ability_definition' 4 | 5 | module Canard 6 | module Generators 7 | class AbilityGenerator < Rails::Generators::NamedBase # :nodoc: 8 | source_root File.expand_path('templates', __dir__) 9 | argument :ability_definitions, 10 | type: :array, 11 | default: [], 12 | banner: 'can:[read,update]:[user,account] cannot:[create,destroy]:user' 13 | 14 | def generate_ability 15 | template 'abilities.rb.erb', Abilities.default_path + "/#{file_name.pluralize}.rb" 16 | end 17 | 18 | hook_for :test_framework, as: 'ability' 19 | 20 | private 21 | 22 | def definitions 23 | ability_definitions.each { |definition| AbilityDefinition.parse(definition) } 24 | 25 | AbilityDefinition.models.sort.each do |model, definition| 26 | yield model, definition 27 | end 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/generators/canard/ability/templates/abilities.rb.erb: -------------------------------------------------------------------------------- 1 | Canard::Abilities.for(<%= ":#{name}" -%>) do 2 | <% if ability_definitions.empty? -%> 3 | # Define abilities for the user role here. For example: 4 | # 5 | # if user.admin? 6 | # can :manage, :all 7 | # else 8 | # can :read, :all 9 | # end 10 | # 11 | # The first argument to `can` is the action you are giving the user permission to do. 12 | # If you pass :manage it will apply to every action. Other common actions here are 13 | # :read, :create, :update and :destroy. 14 | # 15 | # The second argument is the resource the user can perform the action on. If you pass 16 | # :all it will apply to every resource. Otherwise pass a Ruby class of the resource. 17 | # 18 | # The third argument is an optional hash of conditions to further filter the objects. 19 | # For example, here the user can only update published articles. 20 | # 21 | # can :update, Article, published: true 22 | # 23 | # See the wiki for details: https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities 24 | <% else -%> 25 | <% definitions do |model, definition| -%> 26 | <%= "can #{definition.cans.map(&:to_sym)}, #{model.classify}" unless definition.cans.empty? %> 27 | <%= "cannot #{definition.cannots.map(&:to_sym)}, #{model.classify}" unless definition.cannots.empty? %> 28 | <% end -%> 29 | <% end -%> 30 | end 31 | -------------------------------------------------------------------------------- /lib/generators/rspec/ability/ability_generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'generators/rspec' 4 | require_relative '../../ability_definition' 5 | 6 | module Rspec 7 | module Generators 8 | class AbilityGenerator < Base 9 | @_rspec_source_root = File.expand_path('templates', __dir__) 10 | argument :ability_definitions, type: :array, default: [], banner: 'can:abilities:models cannot:abilities:models' 11 | 12 | def generate_ability_spec 13 | template 'abilities_spec.rb.erb', "spec/abilities/#{file_name.pluralize}_spec.rb" 14 | end 15 | 16 | private 17 | 18 | def definitions 19 | ability_definitions.each { |definition| AbilityDefinition.parse(definition) } 20 | 21 | AbilityDefinition.models.sort.each do |model, definition| 22 | yield model, definition 23 | end 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/generators/rspec/ability/templates/abilities_spec.rb.erb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | require 'cancan/matchers' 3 | 4 | describe Canard::Abilities, '#<%= plural_name %>' do 5 | <% if Rails.configuration.generators.options[:rails][:fixture_replacement] == :factory_girl -%> 6 | let(:acting_<%= name %>) { FactoryGirl.create(:user<%= name == 'user' ? '' : ", :#{name}" %>) } 7 | <% elsif Rails.configuration.generators.options[:rails][:fixture_replacement] == :machinist -%> 8 | let(:acting_<%= name %>) { User.make!(:<%= name %>) } 9 | <% else -%> 10 | let(:acting_<%= name %>) { User.create(roles: %w(<%= name -%>)) } 11 | <% end -%> 12 | subject(:<%= name %>_ability) { Ability.new(acting_<%= name %>) } 13 | 14 | <% if ability_definitions.empty? -%> 15 | # # Define your ability tests thus; 16 | # describe 'on <%= name.camelize %>' do 17 | # let(:<%= name %>) { FactoryGirl.create(<%= name %>) } 18 | # 19 | # it { is_expected.to be_able_to(:index, <%= name.camelize %>) } 20 | # it { is_expected.to be_able_to(:show, <%= name %>) } 21 | # it { is_expected.to be_able_to(:read, <%= name %>) } 22 | # it { is_expected.to be_able_to(:new, <%= name %>) } 23 | # it { is_expected.to be_able_to(:create, <%= name %>) } 24 | # it { is_expected.to be_able_to(:edit, <%= name %>) } 25 | # it { is_expected.to be_able_to(:update, <%= name %>) } 26 | # it { is_expected.to be_able_to(:destroy, <%= name %>) } 27 | # end 28 | # # on <%= name.camelize %> 29 | <% else -%> 30 | <% definitions do |model, definition| -%> 31 | <% model_name = model.camelize -%> 32 | describe 'on <%= model_name -%>' do 33 | <% if Rails.configuration.generators.options[:rails][:fixture_replacement] == :factory_girl -%> 34 | let(:<%= model -%>) { FactoryGirl.create(:<%= model -%>) } 35 | <% elsif Rails.configuration.generators.options[:rails][:fixture_replacement] == :machinist -%> 36 | let(:<%= model -%>) { <%= model_name -%>.make! } 37 | <% else -%> 38 | let(:<%= model -%>) { <%= model_name -%>.create } 39 | <% end -%> 40 | 41 | <% definition.cans.each do |can| -%> 42 | it { is_expected.to be_able_to(<%= ":#{can}, #{model}" -%>) } 43 | <% end -%> 44 | <%- definition.cannots.each do |cannot| -%> 45 | it { is_expected.to_not be_able_to(<%= ":#{cannot}, #{model}" -%>) } 46 | <% end -%> 47 | end 48 | # on <%= model_name %> 49 | <% end -%> 50 | <% end -%> 51 | end 52 | -------------------------------------------------------------------------------- /lib/tasks/canard.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | namespace :canard do 4 | desc 'Upgrades deprecated ability definition syntax and moves the files from abilities to app/abilities' 5 | task upgrade: :environment do 6 | require 'rake/clean' 7 | source_path = 'abilities' 8 | destination_path = Canard::Abilities.definition_paths.first 9 | 10 | Dir.mkdir(destination_path) unless Dir.exist?(destination_path) 11 | 12 | if Dir.exist?(source_path) 13 | Dir[File.join(source_path, '*.rb')].each do |input_file| 14 | input = File.read(input_file) 15 | output = input.gsub(/abilities_for/, 'Canard::Abilities.for') 16 | output_file = File.expand_path(File.basename(input_file), destination_path) 17 | File.write(output_file, output) 18 | File.delete(input_file) 19 | end 20 | Dir.delete(source_path) 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/abilities/administrators.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Canard::Abilities.for(:administrator) do 4 | can :manage, [Activity, User] 5 | end 6 | -------------------------------------------------------------------------------- /test/canard/abilities_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'test_helper' 4 | require 'canard' 5 | 6 | describe 'Canard::Abilities' do 7 | subject { Canard::Abilities } 8 | 9 | describe 'ability_paths' do 10 | it 'defaults to app/abilities' do 11 | subject.definition_paths.must_include 'app/abilities' 12 | end 13 | 14 | it 'appends paths' do 15 | subject.definition_paths << 'other_abilities' 16 | subject.definition_paths.must_include 'other_abilities' 17 | end 18 | 19 | it 'can be overwritten' do 20 | subject.definition_paths = ['other_abilities'] 21 | 22 | subject.definition_paths.must_equal ['other_abilities'] 23 | end 24 | end 25 | 26 | describe 'default_path' do 27 | it 'defaults to app/abilities' do 28 | subject.default_path.must_equal 'app/abilities' 29 | end 30 | 31 | it 'can be changhed' do 32 | subject.default_path = 'other_abilities' 33 | 34 | subject.default_path.must_equal 'other_abilities' 35 | end 36 | end 37 | 38 | describe 'for' do 39 | it 'adds the block to the definitions' do 40 | block = -> { puts 'some block' } 41 | 42 | subject.for(:definition, &block) 43 | 44 | assert_equal block, subject.definitions[:definition] 45 | end 46 | 47 | it 'normalises the key to a symbol' do 48 | subject.for('definition') { puts 'a block' } 49 | 50 | subject.definitions.keys.must_include :definition 51 | end 52 | 53 | it 'rasises ArgumentError if no block is provided' do 54 | proc { subject.for(:definition) }.must_raise ArgumentError 55 | end 56 | 57 | it 'creates a lowercaae key' do 58 | subject.for('NotCamelCase') { puts 'a block' } 59 | 60 | subject.definitions.keys.must_include :not_camel_case 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /test/canard/ability_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'test_helper' 4 | 5 | describe Ability do 6 | before do 7 | Canard::Abilities.definition_paths = [File.expand_path('../dummy/app/abilities', __dir__)] 8 | # reload abilities because the reloader will have removed them after the railtie ran 9 | Canard.find_abilities 10 | end 11 | 12 | describe 'new' do 13 | describe 'with a user' do 14 | let(:user) { User.new } 15 | subject { Ability.new(user) } 16 | 17 | it 'assign the user to Ability#user' do 18 | subject.user.must_equal user 19 | end 20 | end 21 | 22 | describe 'with a model that references a user' do 23 | let(:user) { User.create } 24 | let(:member) { Member.new(user: user) } 25 | 26 | subject { Ability.new(member) } 27 | 28 | it 'assign the user to Ability#user' do 29 | subject.user.must_equal user 30 | end 31 | end 32 | 33 | describe 'with a user that has author role' do 34 | let(:user) { User.create(roles: [:author]) } 35 | let(:member) { Member.create(user: user) } 36 | let(:other_member) { Member.new(user: User.create) } 37 | subject { Ability.new(user) } 38 | 39 | it 'has all the abilities of the base class' do 40 | subject.can?(:edit, member).must_equal true 41 | subject.can?(:update, member).must_equal true 42 | 43 | subject.can?(:edit, other_member).wont_equal true 44 | subject.can?(:update, other_member).wont_equal true 45 | end 46 | 47 | it 'has all the abilities of an author' do 48 | subject.can?(:new, Post).must_equal true 49 | subject.can?(:create, Post).must_equal true 50 | subject.can?(:edit, Post).must_equal true 51 | subject.can?(:update, Post).must_equal true 52 | subject.can?(:show, Post).must_equal true 53 | subject.can?(:index, Post).must_equal true 54 | end 55 | 56 | it 'has no admin abilities' do 57 | subject.cannot?(:destroy, Post).must_equal true 58 | end 59 | end 60 | 61 | describe 'with a user that has admin and author roles' do 62 | let(:user) { User.create(roles: %i[author admin]) } 63 | let(:member) { Member.create(user: user) } 64 | let(:other_user) { User.create } 65 | let(:other_member) { Member.new(user: other_user) } 66 | subject { Ability.new(user) } 67 | 68 | it 'has all the abilities of the base class' do 69 | subject.can?(:edit, member).must_equal true 70 | subject.can?(:update, member).must_equal true 71 | 72 | subject.cannot?(:edit, other_member).must_equal true 73 | subject.cannot?(:update, other_member).must_equal true 74 | end 75 | 76 | it 'has all the abilities of an author' do 77 | subject.can?(:new, Post).must_equal true 78 | end 79 | 80 | it 'has the abilities of an admin' do 81 | subject.can?(:manage, Post).must_equal true 82 | subject.can?(:manage, other_user).must_equal true 83 | subject.can?(:destroy, other_user).must_equal true 84 | subject.cannot?(:destroy, user).must_equal true 85 | end 86 | end 87 | 88 | describe 'a user with editor role' do 89 | let(:user) { User.create(roles: [:editor]) } 90 | let(:member) { Member.create(user: user) } 91 | let(:other_user) { User.create } 92 | let(:other_member) { Member.new(user: other_user) } 93 | subject { Ability.new(user) } 94 | 95 | it 'has all the abilities of the base class' do 96 | subject.can?(:edit, member).must_equal true 97 | subject.can?(:update, member).must_equal true 98 | 99 | subject.cannot?(:edit, other_member).must_equal true 100 | subject.cannot?(:update, other_member).must_equal true 101 | end 102 | 103 | it "has most of the abilities of authors, except it can't create Posts" do 104 | subject.can?(:update, Post).must_equal true 105 | subject.can?(:read, Post).must_equal true 106 | subject.cannot?(:create, Post).must_equal true 107 | end 108 | end 109 | 110 | describe 'with no user' do 111 | subject { Ability.new } 112 | 113 | it 'applies the guest abilities' do 114 | subject.can?(:index, Post) 115 | subject.can?(:show, Post) 116 | 117 | subject.cannot?(:create, Post) 118 | subject.cannot?(:update, Post) 119 | subject.cannot?(:destroy, Post) 120 | 121 | subject.cannot?(:show, User) 122 | subject.cannot?(:show, Member) 123 | end 124 | end 125 | 126 | describe 'with an instance of an anonymous class that has author role' do 127 | let(:klass) do 128 | Class.new do 129 | extend Canard::UserModel 130 | attr_accessor :roles_mask 131 | acts_as_user roles: %i[author admin] 132 | def initialize(*roles) 133 | self.roles = roles 134 | end 135 | end 136 | end 137 | let(:instance) { klass.new(:author) } 138 | 139 | describe 'for base class abilities' do 140 | it 'does nothing' do 141 | proc { Ability.new(instance) }.must_be_silent 142 | end 143 | end 144 | 145 | describe 'for assigned roles' do 146 | subject { Ability.new(instance) } 147 | 148 | it 'has all the abilities of an author' do 149 | subject.can?(:new, Post).must_equal true 150 | subject.can?(:create, Post).must_equal true 151 | subject.can?(:edit, Post).must_equal true 152 | subject.can?(:update, Post).must_equal true 153 | subject.can?(:show, Post).must_equal true 154 | subject.can?(:index, Post).must_equal true 155 | end 156 | 157 | it 'has no admin abilities' do 158 | subject.cannot?(:destroy, Post).must_equal true 159 | end 160 | end 161 | end 162 | end 163 | end 164 | -------------------------------------------------------------------------------- /test/canard/adapters/active_record_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'test_helper' 4 | 5 | describe Canard::Adapters::ActiveRecord do 6 | describe 'acts_as_user' do 7 | describe 'with a role_mask' do 8 | describe 'and :roles => [] specified' do 9 | it 'sets the valid_roles for the class' do 10 | User.valid_roles.must_equal %i[viewer author admin editor] 11 | end 12 | end 13 | 14 | describe 'and no :roles => [] specified' do 15 | it 'sets no roles' do 16 | UserWithoutRole.valid_roles.must_equal [] 17 | end 18 | end 19 | end 20 | 21 | describe 'with no roles_mask' do 22 | before do 23 | UserWithoutRoleMask.acts_as_user roles: %i[viewer admin] 24 | end 25 | 26 | it 'sets no roles' do 27 | UserWithoutRoleMask.valid_roles.must_equal [] 28 | end 29 | end 30 | 31 | describe 'with no table' do 32 | subject { Class.new(ActiveRecord::Base) } 33 | 34 | it 'sets no roles' do 35 | subject.class_eval { acts_as_user roles: [:admin] } 36 | subject.valid_roles.must_equal [] 37 | end 38 | 39 | it 'does not raise any errors' do 40 | proc { subject.class_eval { acts_as_user roles: [:admin] } }.must_be_silent 41 | end 42 | 43 | it 'returns nil' do 44 | subject.class_eval { acts_as_user roles: [:admin] }.must_be_nil 45 | end 46 | end 47 | 48 | describe 'with an alternative roles_mask specified' do 49 | before do 50 | UserWithoutRoleMask.acts_as_user roles_mask: :my_roles_mask, roles: %i[viewer admin] 51 | end 52 | 53 | it 'sets no roles' do 54 | UserWithoutRoleMask.valid_roles.must_equal %i[viewer admin] 55 | end 56 | end 57 | end 58 | 59 | describe 'scopes' do 60 | describe 'on an ActiveRecord model with roles' do 61 | before do 62 | @no_role = User.create 63 | @admin_author_viewer = User.create(roles: %i[admin author viewer]) 64 | @author_viewer = User.create(roles: %i[author viewer]) 65 | @viewer = User.create(roles: [:viewer]) 66 | @admin_only = User.create(roles: [:admin]) 67 | @author_only = User.create(roles: [:author]) 68 | end 69 | 70 | after do 71 | User.delete_all 72 | end 73 | 74 | subject { User } 75 | 76 | it 'adds a scope to return instances with each role' do 77 | subject.must_respond_to :admins 78 | subject.must_respond_to :authors 79 | subject.must_respond_to :viewers 80 | end 81 | 82 | it 'adds a scope to return instances without each role' do 83 | subject.must_respond_to :non_admins 84 | subject.must_respond_to :non_authors 85 | subject.must_respond_to :non_viewers 86 | end 87 | 88 | describe 'finding instances with a role' do 89 | describe 'admins scope' do 90 | subject { User.admins.sort_by(&:id) } 91 | 92 | it 'returns only admins' do 93 | subject.must_equal [@admin_author_viewer, @admin_only].sort_by(&:id) 94 | end 95 | 96 | it "doesn't return non admins" do 97 | subject.wont_include @no_role 98 | subject.wont_include @author_viewer 99 | subject.wont_include @author_only 100 | subject.wont_include @viewer 101 | end 102 | end 103 | 104 | describe 'authors scope' do 105 | subject { User.authors.sort_by(&:id) } 106 | 107 | it 'returns only authors' do 108 | subject.must_equal [@admin_author_viewer, @author_viewer, @author_only].sort_by(&:id) 109 | end 110 | 111 | it "doesn't return non authors" do 112 | subject.wont_include @no_role 113 | subject.wont_include @admin_only 114 | subject.wont_include @viewer 115 | end 116 | end 117 | 118 | describe 'viewers scope' do 119 | subject { User.viewers.sort_by(&:id) } 120 | 121 | it 'returns only viewers' do 122 | subject.must_equal [@admin_author_viewer, @author_viewer, @viewer].sort_by(&:id) 123 | end 124 | 125 | it "doesn't return non authors" do 126 | subject.wont_include @no_role 127 | subject.wont_include @admin_only 128 | subject.wont_include @author_only 129 | end 130 | end 131 | end 132 | 133 | describe 'finding instances without a role' do 134 | describe 'non_admins scope' do 135 | subject { User.non_admins.sort_by(&:id) } 136 | 137 | it 'returns only non_admins' do 138 | subject.must_equal [@no_role, @author_viewer, @viewer, @author_only].sort_by(&:id) 139 | end 140 | 141 | it "doesn't return admins" do 142 | subject.wont_include @admin_author_viewer 143 | subject.wont_include @admin_only 144 | end 145 | end 146 | 147 | describe 'non_authors scope' do 148 | subject { User.non_authors.sort_by(&:id) } 149 | 150 | it 'returns only non_authors' do 151 | subject.must_equal [@no_role, @viewer, @admin_only].sort_by(&:id) 152 | end 153 | 154 | it "doesn't return authors" do 155 | subject.wont_include @admin_author_viewer 156 | subject.wont_include @author_viewer 157 | subject.wont_include @author_only 158 | end 159 | end 160 | 161 | describe 'non_viewers scope' do 162 | subject { User.non_viewers.sort_by(&:id) } 163 | 164 | it 'returns only non_viewers' do 165 | subject.must_equal [@no_role, @admin_only, @author_only].sort_by(&:id) 166 | end 167 | 168 | it "doesn't return viewers" do 169 | subject.wont_include @admin_author_viewer 170 | subject.wont_include @author_viewer 171 | subject.wont_include @viewer 172 | end 173 | end 174 | end 175 | 176 | describe 'with_any_role' do 177 | describe 'specifying admin only' do 178 | subject { User.with_any_role(:admin).sort_by(&:id) } 179 | 180 | it 'returns only admins' do 181 | subject.must_equal [@admin_author_viewer, @admin_only].sort_by(&:id) 182 | end 183 | 184 | it "doesn't return non admins" do 185 | subject.wont_include @no_role 186 | subject.wont_include @author_viewer 187 | subject.wont_include @author_only 188 | subject.wont_include @viewer 189 | end 190 | end 191 | 192 | describe 'specifying author only' do 193 | subject { User.with_any_role(:author).sort_by(&:id) } 194 | 195 | it 'returns only authors' do 196 | subject.must_equal [@admin_author_viewer, @author_viewer, @author_only].sort_by(&:id) 197 | end 198 | 199 | it "doesn't return non authors" do 200 | subject.wont_include @no_role 201 | subject.wont_include @admin_only 202 | subject.wont_include @viewer 203 | end 204 | end 205 | 206 | describe 'specifying viewer only' do 207 | subject { User.with_any_role(:viewer).sort_by(&:id) } 208 | 209 | it 'returns only viewers' do 210 | subject.must_equal [@admin_author_viewer, @author_viewer, @viewer].sort_by(&:id) 211 | end 212 | 213 | it "doesn't return non authors" do 214 | subject.wont_include @no_role 215 | subject.wont_include @admin_only 216 | subject.wont_include @author_only 217 | end 218 | end 219 | 220 | describe 'specifying admin and author' do 221 | subject { User.with_any_role(:admin, :author).sort_by(&:id) } 222 | 223 | it 'returns only admins and authors' do 224 | subject.must_equal [@admin_author_viewer, @author_viewer, @admin_only, @author_only].sort_by(&:id) 225 | end 226 | 227 | it "doesn't return non admins or authors" do 228 | subject.wont_include @no_role 229 | subject.wont_include @viewer 230 | end 231 | end 232 | 233 | describe 'specifying admin and viewer' do 234 | subject { User.with_any_role(:admin, :viewer).sort_by(&:id) } 235 | 236 | it 'returns only admins and viewers' do 237 | subject.must_equal [@admin_author_viewer, @author_viewer, @admin_only, @viewer].sort_by(&:id) 238 | end 239 | 240 | it "doesn't return non admins or viewers" do 241 | subject.wont_include @no_role 242 | subject.wont_include @author_only 243 | end 244 | end 245 | 246 | describe 'specifying author and viewer' do 247 | subject { User.with_any_role(:author, :viewer).sort_by(&:id) } 248 | 249 | it 'returns only authors and viewers' do 250 | subject.must_equal [@admin_author_viewer, @author_viewer, @author_only, @viewer].sort_by(&:id) 251 | end 252 | 253 | it "doesn't return non authors or viewers" do 254 | subject.wont_include @no_role 255 | subject.wont_include @admin_only 256 | end 257 | end 258 | 259 | describe 'specifying admin, author and viewer' do 260 | subject { User.with_any_role(:admin, :author, :viewer).sort_by(&:id) } 261 | 262 | it 'returns only admins, authors and viewers' do 263 | subject.must_equal [@admin_author_viewer, @author_viewer, @admin_only, @author_only, @viewer].sort_by(&:id) 264 | end 265 | 266 | it "doesn't return non admins, authors or viewers" do 267 | subject.wont_include @no_role 268 | end 269 | end 270 | end 271 | 272 | describe 'with_all_roles' do 273 | describe 'specifying admin only' do 274 | subject { User.with_all_roles(:admin).sort_by(&:id) } 275 | 276 | it 'returns only admins' do 277 | subject.must_equal [@admin_author_viewer, @admin_only].sort_by(&:id) 278 | end 279 | 280 | it "doesn't return non admins" do 281 | subject.wont_include @no_role 282 | subject.wont_include @author_viewer 283 | subject.wont_include @author_only 284 | subject.wont_include @viewer 285 | end 286 | end 287 | 288 | describe 'specifying author only' do 289 | subject { User.with_all_roles(:author).sort_by(&:id) } 290 | 291 | it 'returns only authors' do 292 | subject.must_equal [@admin_author_viewer, @author_viewer, @author_only].sort_by(&:id) 293 | end 294 | 295 | it "doesn't return non authors" do 296 | subject.wont_include @no_role 297 | subject.wont_include @admin_only 298 | subject.wont_include @viewer 299 | end 300 | end 301 | 302 | describe 'specifying viewer only' do 303 | subject { User.with_all_roles(:viewer).sort_by(&:id) } 304 | 305 | it 'returns only viewers' do 306 | subject.must_equal [@admin_author_viewer, @author_viewer, @viewer].sort_by(&:id) 307 | end 308 | 309 | it "doesn't return non authors" do 310 | subject.wont_include @no_role 311 | subject.wont_include @admin_only 312 | subject.wont_include @author_only 313 | end 314 | end 315 | 316 | describe 'specifying admin and author' do 317 | subject { User.with_all_roles(:admin, :author).sort_by(&:id) } 318 | 319 | it 'returns only admins and authors' do 320 | subject.must_equal [@admin_author_viewer].sort_by(&:id) 321 | end 322 | 323 | it "doesn't return non admin and authors" do 324 | subject.wont_include @no_role 325 | subject.wont_include @author_viewer 326 | subject.wont_include @author_only 327 | subject.wont_include @admin_only 328 | subject.wont_include @viewer 329 | end 330 | end 331 | 332 | describe 'specifying admin and viewer' do 333 | subject { User.with_all_roles(:admin, :viewer).sort_by(&:id) } 334 | 335 | it 'returns only admins and viewers' do 336 | subject.must_equal [@admin_author_viewer].sort_by(&:id) 337 | end 338 | 339 | it "doesn't return non admins or viewers" do 340 | subject.wont_include @no_role 341 | subject.wont_include @author_viewer 342 | subject.wont_include @author_only 343 | subject.wont_include @admin_only 344 | subject.wont_include @viewer 345 | end 346 | end 347 | 348 | describe 'specifying author and viewer' do 349 | subject { User.with_all_roles(:author, :viewer).sort_by(&:id) } 350 | 351 | it 'returns only authors and viewers' do 352 | subject.must_equal [@admin_author_viewer, @author_viewer].sort_by(&:id) 353 | end 354 | 355 | it "doesn't return non authors or viewers" do 356 | subject.wont_include @no_role 357 | subject.wont_include @admin_only 358 | subject.wont_include @author_only 359 | subject.wont_include @viewer 360 | end 361 | end 362 | 363 | describe 'specifying admin, author and viewer' do 364 | subject { User.with_all_roles(:admin, :author, :viewer).sort_by(&:id) } 365 | 366 | it 'returns only admins, authors and viewers' do 367 | subject.must_equal [@admin_author_viewer].sort_by(&:id) 368 | end 369 | 370 | it "doesn't return non admins, authors or viewers" do 371 | subject.wont_include @no_role 372 | subject.wont_include @author_viewer 373 | subject.wont_include @author_only 374 | subject.wont_include @admin_only 375 | subject.wont_include @viewer 376 | end 377 | end 378 | end 379 | 380 | describe 'with_only_roles' do 381 | describe 'specifying one role' do 382 | subject { User.with_only_roles(:admin).sort_by(&:id) } 383 | 384 | it 'returns users with just that role' do 385 | subject.must_equal [@admin_only].sort_by(&:id) 386 | end 387 | 388 | it "doesn't return any other users" do 389 | subject.wont_include @no_role 390 | subject.wont_include @admin_author_viewer 391 | subject.wont_include @author_viewer 392 | subject.wont_include @author_only 393 | subject.wont_include @viewer 394 | end 395 | end 396 | 397 | describe 'specifying multiple roles' do 398 | subject { User.with_only_roles(:author, :viewer).sort_by(&:id) } 399 | 400 | it 'returns only users with no more or less roles' do 401 | subject.must_equal [@author_viewer].sort_by(&:id) 402 | end 403 | 404 | it "doesn't return any other users" do 405 | subject.wont_include @no_role 406 | subject.wont_include @admin_author_viewer 407 | subject.wont_include @admin_only 408 | subject.wont_include @author_only 409 | subject.wont_include @viewer 410 | end 411 | end 412 | end 413 | end 414 | end 415 | end 416 | -------------------------------------------------------------------------------- /test/canard/adapters/mongoid_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'test_helper' 4 | require 'bson' 5 | 6 | describe Canard::Adapters::Mongoid do 7 | describe 'acts_as_user' do 8 | describe 'with a role_mask' do 9 | describe 'and :roles => [] specified' do 10 | it 'sets the valid_roles for the class' do 11 | MongoidUser.valid_roles.must_equal %i[viewer author admin] 12 | end 13 | end 14 | end 15 | end 16 | 17 | describe 'scopes' do 18 | describe 'on an Mongoid model with roles' do 19 | before do 20 | @no_role = MongoidUser.create 21 | @admin_author_viewer = MongoidUser.create(roles: %i[admin author viewer]) 22 | @author_viewer = MongoidUser.create(roles: %i[author viewer]) 23 | @viewer = MongoidUser.create(roles: [:viewer]) 24 | @admin_only = MongoidUser.create(roles: [:admin]) 25 | @author_only = MongoidUser.create(roles: [:author]) 26 | end 27 | 28 | after do 29 | MongoidUser.delete_all 30 | end 31 | 32 | subject { MongoidUser } 33 | 34 | it 'adds a scope to return instances with each role' do 35 | subject.must_respond_to :admins 36 | subject.must_respond_to :authors 37 | subject.must_respond_to :viewers 38 | end 39 | 40 | it 'adds a scope to return instances without each role' do 41 | subject.must_respond_to :non_admins 42 | subject.must_respond_to :non_authors 43 | subject.must_respond_to :non_viewers 44 | end 45 | 46 | describe 'finding instances with a role' do 47 | describe 'admins scope' do 48 | subject { MongoidUser.admins.sort_by(&:id) } 49 | 50 | it 'returns only admins' do 51 | subject.must_equal [@admin_author_viewer, @admin_only].sort_by(&:id) 52 | end 53 | 54 | it "doesn't return non admins" do 55 | subject.wont_include @no_role 56 | subject.wont_include @author_viewer 57 | subject.wont_include @author_only 58 | subject.wont_include @viewer 59 | end 60 | end 61 | 62 | describe 'authors scope' do 63 | subject { MongoidUser.authors.sort_by(&:id) } 64 | 65 | it 'returns only authors' do 66 | subject.must_equal [@admin_author_viewer, @author_viewer, @author_only].sort_by(&:id) 67 | end 68 | 69 | it "doesn't return non authors" do 70 | subject.wont_include @no_role 71 | subject.wont_include @admin_only 72 | subject.wont_include @viewer 73 | end 74 | end 75 | 76 | describe 'viewers scope' do 77 | subject { MongoidUser.viewers.sort_by(&:id) } 78 | 79 | it 'returns only viewers' do 80 | subject.must_equal [@admin_author_viewer, @author_viewer, @viewer].sort_by(&:id) 81 | end 82 | 83 | it "doesn't return non authors" do 84 | subject.wont_include @no_role 85 | subject.wont_include @admin_only 86 | subject.wont_include @author_only 87 | end 88 | end 89 | end 90 | 91 | describe 'finding instances without a role' do 92 | describe 'non_admins scope' do 93 | subject { MongoidUser.non_admins.sort_by(&:id) } 94 | 95 | it 'returns only non_admins' do 96 | subject.must_equal [@no_role, @author_viewer, @viewer, @author_only].sort_by(&:id) 97 | end 98 | 99 | it "doesn't return admins" do 100 | subject.wont_include @admin_author_viewer 101 | subject.wont_include @admin_only 102 | end 103 | end 104 | 105 | describe 'non_authors scope' do 106 | subject { MongoidUser.non_authors.sort_by(&:id) } 107 | 108 | it 'returns only non_authors' do 109 | subject.must_equal [@no_role, @viewer, @admin_only].sort_by(&:id) 110 | end 111 | 112 | it "doesn't return authors" do 113 | subject.wont_include @admin_author_viewer 114 | subject.wont_include @author_viewer 115 | subject.wont_include @author_only 116 | end 117 | end 118 | 119 | describe 'non_viewers scope' do 120 | subject { MongoidUser.non_viewers.sort_by(&:id) } 121 | 122 | it 'returns only non_viewers' do 123 | subject.must_equal [@no_role, @admin_only, @author_only].sort_by(&:id) 124 | end 125 | 126 | it "doesn't return viewers" do 127 | subject.wont_include @admin_author_viewer 128 | subject.wont_include @author_viewer 129 | subject.wont_include @viewer 130 | end 131 | end 132 | end 133 | 134 | describe 'with_any_role' do 135 | describe 'specifying admin only' do 136 | subject { MongoidUser.with_any_role(:admin).sort_by(&:id) } 137 | 138 | it 'returns only admins' do 139 | subject.must_equal [@admin_author_viewer, @admin_only].sort_by(&:id) 140 | end 141 | 142 | it "doesn't return non admins" do 143 | subject.wont_include @no_role 144 | subject.wont_include @author_viewer 145 | subject.wont_include @author_only 146 | subject.wont_include @viewer 147 | end 148 | end 149 | 150 | describe 'specifying author only' do 151 | subject { MongoidUser.with_any_role(:author).sort_by(&:id) } 152 | 153 | it 'returns only authors' do 154 | subject.must_equal [@admin_author_viewer, @author_viewer, @author_only].sort_by(&:id) 155 | end 156 | 157 | it "doesn't return non authors" do 158 | subject.wont_include @no_role 159 | subject.wont_include @admin_only 160 | subject.wont_include @viewer 161 | end 162 | end 163 | 164 | describe 'specifying viewer only' do 165 | subject { MongoidUser.with_any_role(:viewer).sort_by(&:id) } 166 | 167 | it 'returns only viewers' do 168 | subject.must_equal [@admin_author_viewer, @author_viewer, @viewer].sort_by(&:id) 169 | end 170 | 171 | it "doesn't return non authors" do 172 | subject.wont_include @no_role 173 | subject.wont_include @admin_only 174 | subject.wont_include @author_only 175 | end 176 | end 177 | 178 | describe 'specifying admin and author' do 179 | subject { MongoidUser.with_any_role(:admin, :author).sort_by(&:id) } 180 | 181 | it 'returns only admins and authors' do 182 | subject.must_equal [@admin_author_viewer, @author_viewer, @admin_only, @author_only].sort_by(&:id) 183 | end 184 | 185 | it "doesn't return non admins or authors" do 186 | subject.wont_include @no_role 187 | subject.wont_include @viewer 188 | end 189 | end 190 | 191 | describe 'specifying admin and viewer' do 192 | subject { MongoidUser.with_any_role(:admin, :viewer).sort_by(&:id) } 193 | 194 | it 'returns only admins and viewers' do 195 | subject.must_equal [@admin_author_viewer, @author_viewer, @admin_only, @viewer].sort_by(&:id) 196 | end 197 | 198 | it "doesn't return non admins or viewers" do 199 | subject.wont_include @no_role 200 | subject.wont_include @author_only 201 | end 202 | end 203 | 204 | describe 'specifying author and viewer' do 205 | subject { MongoidUser.with_any_role(:author, :viewer).sort_by(&:id) } 206 | 207 | it 'returns only authors and viewers' do 208 | subject.must_equal [@admin_author_viewer, @author_viewer, @author_only, @viewer].sort_by(&:id) 209 | end 210 | 211 | it "doesn't return non authors or viewers" do 212 | subject.wont_include @no_role 213 | subject.wont_include @admin_only 214 | end 215 | end 216 | 217 | describe 'specifying admin, author and viewer' do 218 | subject { MongoidUser.with_any_role(:admin, :author, :viewer).sort_by(&:id) } 219 | 220 | it 'returns only admins, authors and viewers' do 221 | subject.must_equal [@admin_author_viewer, @author_viewer, @admin_only, @author_only, @viewer].sort_by(&:id) 222 | end 223 | 224 | it "doesn't return non admins, authors or viewers" do 225 | subject.wont_include @no_role 226 | end 227 | end 228 | end 229 | 230 | describe 'with_all_roles' do 231 | describe 'specifying admin only' do 232 | subject { MongoidUser.with_all_roles(:admin).sort_by(&:id) } 233 | 234 | it 'returns only admins' do 235 | subject.must_equal [@admin_author_viewer, @admin_only].sort_by(&:id) 236 | end 237 | 238 | it "doesn't return non admins" do 239 | subject.wont_include @no_role 240 | subject.wont_include @author_viewer 241 | subject.wont_include @author_only 242 | subject.wont_include @viewer 243 | end 244 | end 245 | 246 | describe 'specifying author only' do 247 | subject { MongoidUser.with_all_roles(:author).sort_by(&:id) } 248 | 249 | it 'returns only authors' do 250 | subject.must_equal [@admin_author_viewer, @author_viewer, @author_only].sort_by(&:id) 251 | end 252 | 253 | it "doesn't return non authors" do 254 | subject.wont_include @no_role 255 | subject.wont_include @admin_only 256 | subject.wont_include @viewer 257 | end 258 | end 259 | 260 | describe 'specifying viewer only' do 261 | subject { MongoidUser.with_all_roles(:viewer).sort_by(&:id) } 262 | 263 | it 'returns only viewers' do 264 | subject.must_equal [@admin_author_viewer, @author_viewer, @viewer].sort_by(&:id) 265 | end 266 | 267 | it "doesn't return non authors" do 268 | subject.wont_include @no_role 269 | subject.wont_include @admin_only 270 | subject.wont_include @author_only 271 | end 272 | end 273 | 274 | describe 'specifying admin and author' do 275 | subject { MongoidUser.with_all_roles(:admin, :author).sort_by(&:id) } 276 | 277 | it 'returns only admins and authors' do 278 | subject.must_equal [@admin_author_viewer].sort_by(&:id) 279 | end 280 | 281 | it "doesn't return non admin and authors" do 282 | subject.wont_include @no_role 283 | subject.wont_include @author_viewer 284 | subject.wont_include @author_only 285 | subject.wont_include @admin_only 286 | subject.wont_include @viewer 287 | end 288 | end 289 | 290 | describe 'specifying admin and viewer' do 291 | subject { MongoidUser.with_all_roles(:admin, :viewer).sort_by(&:id) } 292 | 293 | it 'returns only admins and viewers' do 294 | subject.must_equal [@admin_author_viewer].sort_by(&:id) 295 | end 296 | 297 | it "doesn't return non admins or viewers" do 298 | subject.wont_include @no_role 299 | subject.wont_include @author_viewer 300 | subject.wont_include @author_only 301 | subject.wont_include @admin_only 302 | subject.wont_include @viewer 303 | end 304 | end 305 | 306 | describe 'specifying author and viewer' do 307 | subject { MongoidUser.with_all_roles(:author, :viewer).sort_by(&:id) } 308 | 309 | it 'returns only authors and viewers' do 310 | subject.must_equal [@admin_author_viewer, @author_viewer].sort_by(&:id) 311 | end 312 | 313 | it "doesn't return non authors or viewers" do 314 | subject.wont_include @no_role 315 | subject.wont_include @admin_only 316 | subject.wont_include @author_only 317 | subject.wont_include @viewer 318 | end 319 | end 320 | 321 | describe 'specifying admin, author and viewer' do 322 | subject { MongoidUser.with_all_roles(:admin, :author, :viewer).sort_by(&:id) } 323 | 324 | it 'returns only admins, authors and viewers' do 325 | subject.must_equal [@admin_author_viewer].sort_by(&:id) 326 | end 327 | 328 | it "doesn't return non admins, authors or viewers" do 329 | subject.wont_include @no_role 330 | subject.wont_include @author_viewer 331 | subject.wont_include @author_only 332 | subject.wont_include @admin_only 333 | subject.wont_include @viewer 334 | end 335 | end 336 | end 337 | 338 | describe 'with_only_roles' do 339 | describe 'specifying one role' do 340 | subject { MongoidUser.with_only_roles(:admin).sort_by(&:id) } 341 | 342 | it 'returns users with just that role' do 343 | subject.must_equal [@admin_only].sort_by(&:id) 344 | end 345 | 346 | it "doesn't return any other users" do 347 | subject.wont_include @no_role 348 | subject.wont_include @admin_author_viewer 349 | subject.wont_include @author_viewer 350 | subject.wont_include @author_only 351 | subject.wont_include @viewer 352 | end 353 | end 354 | 355 | describe 'specifying multiple roles' do 356 | subject { MongoidUser.with_only_roles(:author, :viewer).sort_by(&:id) } 357 | 358 | it 'returns only users with no more or less roles' do 359 | subject.must_equal [@author_viewer].sort_by(&:id) 360 | end 361 | 362 | it "doesn't return any other users" do 363 | subject.wont_include @no_role 364 | subject.wont_include @admin_author_viewer 365 | subject.wont_include @admin_only 366 | subject.wont_include @author_only 367 | subject.wont_include @viewer 368 | end 369 | end 370 | end 371 | end 372 | end 373 | end 374 | -------------------------------------------------------------------------------- /test/canard/canard_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'test_helper' 4 | require 'canard' 5 | 6 | describe Canard do 7 | describe 'ability_definitions' do 8 | it 'should be an accessor' do 9 | Canard.must_respond_to(:ability_definitions) 10 | end 11 | 12 | it 'should be a hash' do 13 | Canard.ability_definitions.must_be_instance_of Hash 14 | end 15 | end 16 | 17 | describe 'ability_key' do 18 | it 'returns a snake case version of the string' do 19 | class_name = 'CamelCaseString' 20 | key = :camel_case_string 21 | 22 | Canard.ability_key(class_name).must_equal key 23 | end 24 | 25 | it 'prepends namespaces to the class name' do 26 | class_name = 'Namespace::CamelCaseString' 27 | key = :namespace_camel_case_string 28 | 29 | Canard.ability_key(class_name).must_equal key 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /test/canard/find_abilities_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'test_helper' 4 | 5 | describe Canard do 6 | describe 'find_abilities' do 7 | before do 8 | Canard::Abilities.definition_paths = [File.expand_path('../dummy/app/abilities', __dir__)] 9 | end 10 | 11 | it 'loads the abilities into ability_definitions' do 12 | Canard.find_abilities 13 | 14 | Canard.ability_definitions.keys.must_include :admin 15 | end 16 | 17 | it 'finds abilities in the default path' do 18 | Canard.find_abilities 19 | 20 | Canard.ability_definitions.keys.must_include :author 21 | Canard.ability_definitions.keys.wont_include :administrator 22 | end 23 | 24 | it 'finds abilities in additional paths' do 25 | Canard::Abilities.definition_paths << File.expand_path('../abilities', __dir__) 26 | Canard.find_abilities 27 | 28 | Canard.ability_definitions.keys.must_include :author 29 | Canard.ability_definitions.keys.must_include :administrator 30 | end 31 | 32 | it 'reloads existing abilities' do 33 | Canard.find_abilities 34 | Canard::Abilities.send(:instance_variable_set, '@definitions', {}) 35 | Canard.find_abilities 36 | 37 | Canard.ability_definitions.keys.must_include :author 38 | Canard.ability_definitions.keys.must_include :admin 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /test/canard/user_model_test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'test_helper' 4 | 5 | describe Canard::UserModel do 6 | describe 'acts_as_user' do 7 | describe 'integrating RoleModel' do 8 | before do 9 | PlainRubyUser.acts_as_user 10 | end 11 | 12 | it 'adds role_model to the class' do 13 | PlainRubyUser.included_modules.must_include RoleModel 14 | PlainRubyUser.new.must_respond_to :roles 15 | end 16 | end 17 | 18 | describe 'with a roles_mask' do 19 | describe 'and :roles => [] specified' do 20 | before do 21 | PlainRubyUser.acts_as_user roles: %i[viewer author admin] 22 | end 23 | 24 | it 'sets the valid_roles for the class' do 25 | PlainRubyUser.valid_roles.must_equal %i[viewer author admin] 26 | end 27 | end 28 | 29 | describe 'and no :roles => [] specified' do 30 | before do 31 | PlainRubyUser.acts_as_user 32 | end 33 | 34 | it 'sets no roles' do 35 | PlainRubyUser.valid_roles.must_equal [] 36 | end 37 | end 38 | end 39 | 40 | describe 'with no roles_mask' do 41 | before do 42 | PlainRubyNonUser.acts_as_user roles: %i[viewer author admin] 43 | end 44 | 45 | it 'sets no roles' do 46 | PlainRubyNonUser.valid_roles.must_equal [] 47 | end 48 | end 49 | 50 | describe 'setting the role_mask' do 51 | before do 52 | PlainRubyNonUser.send :attr_accessor, :my_roles 53 | PlainRubyNonUser.acts_as_user roles: %i[viewer author], roles_mask: :my_roles 54 | end 55 | 56 | it 'sets the valid_roles for the class' do 57 | PlainRubyNonUser.valid_roles.must_equal %i[viewer author] 58 | end 59 | end 60 | end 61 | 62 | describe 'scopes' do 63 | before do 64 | PlainRubyUser.acts_as_user roles: %i[viewer author admin] 65 | end 66 | 67 | describe 'on a plain Ruby class' do 68 | subject { PlainRubyUser } 69 | 70 | it 'creates no scope methods' do 71 | subject.wont_respond_to :admins 72 | subject.wont_respond_to :authors 73 | subject.wont_respond_to :viewers 74 | subject.wont_respond_to :non_admins 75 | subject.wont_respond_to :non_authors 76 | subject.wont_respond_to :non_viewers 77 | subject.wont_respond_to :with_any_role 78 | subject.wont_respond_to :with_all_roles 79 | end 80 | end 81 | end 82 | end 83 | 84 | describe Canard::UserModel::InstanceMethods do 85 | before do 86 | Canard::Abilities.default_path = File.expand_path('../dummy/app/abilities', __dir__) 87 | # reload abilities because the reloader will have removed them after the railtie ran 88 | Canard.find_abilities 89 | end 90 | 91 | describe 'ability' do 92 | before do 93 | PlainRubyUser.acts_as_user roles: %i[admin author] 94 | end 95 | 96 | subject { PlainRubyUser.new(:author).ability } 97 | 98 | it 'returns an ability for this instance' do 99 | subject.must_be_instance_of Ability 100 | end 101 | 102 | it 'has the users abilities' do 103 | subject.can?(:new, Post).must_equal true 104 | subject.can?(:create, Post).must_equal true 105 | subject.can?(:edit, Post).must_equal true 106 | subject.can?(:update, Post).must_equal true 107 | subject.can?(:show, Post).must_equal true 108 | subject.can?(:index, Post).must_equal true 109 | end 110 | 111 | it 'has no other abilities' do 112 | subject.cannot?(:destroy, Post).must_equal true 113 | end 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /test/dummy/Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | # frozen_string_literal: true 3 | 4 | # Add your own tasks in files placed in lib/tasks ending in .rake, 5 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 6 | 7 | require File.expand_path('config/application', __dir__) 8 | 9 | Dummy::Application.load_tasks 10 | -------------------------------------------------------------------------------- /test/dummy/app/abilities/admins.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Canard::Abilities.for(:admin) do 4 | can :manage, [Post, User] 5 | 6 | cannot :destroy, User do |u| 7 | (user == u) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/dummy/app/abilities/authors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Canard::Abilities.for(:author) do 4 | can %i[create update read], Post 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/app/abilities/editors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Canard::Abilities.for(:editor) do 4 | includes_abilities_of :author 5 | 6 | cannot :create, Post 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/abilities/guests.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Canard::Abilities.for(:guest) do 4 | can :read, Post 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/app/abilities/users.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Canard::Abilities.for(:user) do 4 | can %i[edit update], Member, user_id: user.id 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class ApplicationController < ActionController::Base 4 | protect_from_forgery 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/app/models/activity.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Activity 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/models/member.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Member < ActiveRecord::Base 4 | belongs_to :user 5 | 6 | attr_accessible :user, :user_id 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/models/mongoid_user.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class MongoidUser 4 | include Mongoid::Document 5 | 6 | acts_as_user roles: %i[viewer author admin] 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/models/plain_ruby_non_user.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class PlainRubyNonUser 4 | extend Canard::UserModel 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/app/models/plain_ruby_user.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class PlainRubyUser 4 | extend Canard::UserModel 5 | 6 | attr_accessor :roles_mask 7 | 8 | def initialize(*roles) 9 | self.roles = roles 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/dummy/app/models/post.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Post < ActiveRecord::Base 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/app/models/user.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class User < ActiveRecord::Base 4 | acts_as_user roles: %i[viewer author admin editor] 5 | 6 | attr_accessible :roles 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/models/user_without_role.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class UserWithoutRole < ActiveRecord::Base 4 | acts_as_user 5 | 6 | attr_accessible :roles 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/models/user_without_role_mask.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class UserWithoutRoleMask < ActiveRecord::Base 4 | attr_accessible :roles 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file is used by Rack-based servers to start the application. 4 | 5 | require ::File.expand_path('../config/environment', __FILE__) 6 | run Dummy::Application 7 | -------------------------------------------------------------------------------- /test/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.expand_path('boot', __dir__) 4 | 5 | # Pick the frameworks you want: 6 | require 'active_record/railtie' 7 | require 'action_controller/railtie' 8 | require 'active_resource/railtie' 9 | require 'rails/test_unit/railtie' 10 | 11 | Bundler.require if defined?(Bundler) 12 | require 'canard' 13 | 14 | module Dummy 15 | class Application < Rails::Application 16 | # Configure the default encoding used in templates for Ruby 1.9. 17 | config.encoding = 'utf-8' 18 | 19 | # Configure sensitive parameters which will be filtered from the log file. 20 | config.filter_parameters += [:password] 21 | 22 | # Use SQL instead of Active Record's schema dumper when creating the database. 23 | # This is necessary if your schema can't be completely dumped by the schema dumper, 24 | # like if you have constraints or database-specific column types 25 | # config.active_record.schema_format = :sql 26 | 27 | # Enforce whitelist mode for mass assignment. 28 | # This will create an empty whitelist of attributes available for mass-assignment for all models 29 | # in your app. As such, your models will need to explicitly whitelist or blacklist accessible 30 | # parameters by using an attr_accessible or attr_protected declaration. 31 | config.active_record.whitelist_attributes = true 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /test/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rubygems' 4 | gemfile = File.expand_path('../../../Gemfile', __dir__) 5 | 6 | if File.exist?(gemfile) 7 | ENV['BUNDLE_GEMFILE'] = gemfile 8 | require 'bundler' 9 | Bundler.setup 10 | end 11 | 12 | $LOAD_PATH.unshift File.expand_path('../../../lib', __dir__) 13 | -------------------------------------------------------------------------------- /test/dummy/config/database.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: sqlite3 3 | database: db/development.sqlite3 4 | test: 5 | adapter: sqlite3 6 | database: db/test.sqlite3 7 | -------------------------------------------------------------------------------- /test/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Load the rails application 4 | require File.expand_path('application', __dir__) 5 | 6 | # Initialize the rails application 7 | Dummy::Application.initialize! 8 | -------------------------------------------------------------------------------- /test/dummy/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Dummy::Application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb 5 | 6 | # In the development environment your application's code is reloaded on 7 | # every request. This slows down response time but is perfect for development 8 | # since you don't have to restart the web server when you make code changes. 9 | config.cache_classes = false 10 | 11 | # Log error messages when you accidentally call methods on nil. 12 | config.whiny_nils = true 13 | 14 | # Show full error reports and disable caching 15 | config.consider_all_requests_local = true 16 | config.action_controller.perform_caching = false 17 | 18 | # Print deprecation notices to the Rails logger 19 | config.active_support.deprecation = :log 20 | 21 | # Only use best-standards-support built into browsers 22 | config.action_dispatch.best_standards_support = :builtin 23 | 24 | # Raise exception on mass assignment protection for Active Record models 25 | config.active_record.mass_assignment_sanitizer = :strict 26 | 27 | # Log the query plan for queries taking more than this (works 28 | # with SQLite, MySQL, and PostgreSQL) 29 | config.active_record.auto_explain_threshold_in_seconds = 0.5 30 | 31 | # Do not compress assets 32 | config.assets.compress = false 33 | 34 | # Expands the lines which load the assets 35 | config.assets.debug = true 36 | end 37 | -------------------------------------------------------------------------------- /test/dummy/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Dummy::Application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb 5 | 6 | # The test environment is used exclusively to run your application's 7 | # test suite. You never need to work with it otherwise. Remember that 8 | # your test database is "scratch space" for the test suite and is wiped 9 | # and recreated between test runs. Don't rely on the data there! 10 | config.cache_classes = true 11 | 12 | # Configure static asset server for tests with Cache-Control for performance 13 | config.serve_static_assets = true 14 | config.static_cache_control = 'public, max-age=3600' 15 | 16 | # Log error messages when you accidentally call methods on nil 17 | config.whiny_nils = true 18 | 19 | # Show full error reports and disable caching 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Raise exception on mass assignment protection for Active Record models 30 | config.active_record.mass_assignment_sanitizer = :strict 31 | 32 | # Print deprecation notices to the stderr 33 | config.active_support.deprecation = :stderr 34 | end 35 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Your secret key for verifying the integrity of signed cookies. 6 | # If you change this key, all old signed cookies will become invalid! 7 | # Make sure the secret is at least 30 characters and all random, 8 | # no regular words or you'll be exposed to dictionary attacks. 9 | Dummy::Application.config.secret_token = 'c2af159f67aa6955b415217bf5aa58779dec6fccc201c7bf38f9a8ad25893d88596778df8bb7106153666f41c3161cfb7f89b639a2c15a084abb6c2c57f74c55' 10 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | Dummy::Application.config.session_store :cookie_store, key: '_dummy_session' 6 | 7 | # Use the database for sessions instead of the cookie-based default, 8 | # which shouldn't be used to store highly confidential information 9 | # (create the session table with "rails generate session_migration") 10 | # Dummy::Application.config.session_store :active_record_store 11 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | # 5 | # This file contains settings for ActionController::ParamsWrapper which 6 | # is enabled by default. 7 | 8 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 9 | ActiveSupport.on_load(:action_controller) do 10 | wrap_parameters format: [:json] 11 | end 12 | 13 | # Disable root element in JSON by default. 14 | ActiveSupport.on_load(:active_record) do 15 | self.include_root_in_json = false 16 | end 17 | -------------------------------------------------------------------------------- /test/dummy/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | hello: "Hello world" 6 | -------------------------------------------------------------------------------- /test/dummy/config/mongoid3.yml: -------------------------------------------------------------------------------- 1 | development: 2 | sessions: 3 | default: 4 | database: canard_development 5 | hosts: 6 | - localhost:27017 7 | 8 | test: 9 | sessions: 10 | default: 11 | database: canard_test 12 | hosts: 13 | - localhost:27017 14 | options: 15 | connect_timeout: 0.01 16 | wait_queue_timeout: 0.01 17 | socket_timeout: 0.01 18 | -------------------------------------------------------------------------------- /test/dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Dummy::Application.routes.draw do 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20120430083231_initialize_db.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class InitializeDb < ActiveRecord::Migration 4 | def change 5 | create_table :users, force: true do |t| 6 | t.integer :roles_mask 7 | end 8 | create_table :user_without_roles, force: true do |t| 9 | t.integer :roles_mask 10 | end 11 | create_table :user_without_role_masks, force: true do |t| 12 | t.integer :my_roles_mask 13 | end 14 | 15 | create_table :members, force: true do |t| 16 | t.references :user 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/dummy/db/schema.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file is auto-generated from the current state of the database. Instead 4 | # of editing this file, please use the migrations feature of Active Record to 5 | # incrementally modify your database, and then regenerate this schema definition. 6 | # 7 | # Note that this schema.rb definition is the authoritative source for your 8 | # database schema. If you need to create the application database on another 9 | # system, you should be using db:schema:load, not running all the migrations 10 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 11 | # you'll amass, the slower it'll run and the greater likelihood for issues). 12 | # 13 | # It's strongly recommended to check this file into your version control system. 14 | 15 | ActiveRecord::Schema.define(version: 20_120_430_083_231) do 16 | create_table 'members', force: true do |t| 17 | t.integer 'user_id' 18 | end 19 | 20 | create_table 'user_without_role_masks', force: true do |t| 21 | t.integer 'my_roles_mask' 22 | end 23 | 24 | create_table 'user_without_roles', force: true do |t| 25 | t.integer 'roles_mask' 26 | end 27 | 28 | create_table 'users', force: true do |t| 29 | t.integer 'roles_mask' 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/dummy/log/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/james2m/canard/6040110ed79bbb8051d0c487c77ba2160e404516/test/dummy/log/.gitkeep -------------------------------------------------------------------------------- /test/dummy/script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 5 | 6 | APP_PATH = File.expand_path('../config/application', __dir__) 7 | require File.expand_path('../config/boot', __dir__) 8 | require 'rails/commands' 9 | -------------------------------------------------------------------------------- /test/support/reloadable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module MiniTest 4 | class Unit 5 | class TestCase 6 | def teardown 7 | Object.send(:remove_const, 'Canard') if Object.const_defined?('Canard') 8 | GC.start 9 | end 10 | 11 | def setup 12 | ['canard/abilities.rb', 13 | 'canard/user_model.rb', 14 | 'canard/find_abilities.rb'].each do |file| 15 | file_path = File.join(File.expand_path('../../lib', __dir__), file) 16 | load file_path 17 | end 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rubygems' 4 | require 'minitest/autorun' 5 | require 'active_record' 6 | require 'mongoid' 7 | 8 | # Configure Rails Environment 9 | environment = ENV['RAILS_ENV'] = 'test' 10 | rails_root = File.expand_path('dummy', __dir__) 11 | database_yml = File.expand_path('config/database.yml', rails_root) 12 | 13 | ActiveRecord::Base.configurations = YAML.load_file(database_yml) 14 | 15 | config = ActiveRecord::Base.configurations[environment] 16 | db = File.expand_path(config['database'], rails_root) 17 | 18 | # Drop the test database and migrate up 19 | FileUtils.rm(db) if File.exist?(db) 20 | ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: db) 21 | ActiveRecord::Base.connection 22 | ActiveRecord::Migration.verbose = false 23 | ActiveRecord::Migrator.up File.expand_path('db/migrate', rails_root) 24 | 25 | # Load mongoid config 26 | Mongoid.load!(File.expand_path('config/mongoid3.yml', rails_root), :test) 27 | Mongoid.logger.level = :info 28 | 29 | # Load dummy rails app 30 | require File.expand_path('config/environment.rb', rails_root) 31 | 32 | Rails.backtrace_cleaner.remove_silencers! 33 | 34 | # Load support files (reloadable reloads canard) 35 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } 36 | --------------------------------------------------------------------------------