├── .github ├── dependabot.yml └── workflows │ ├── activerecord.yml │ └── mongoid.yml ├── .gitignore ├── .hakiri.yml ├── Appraisals ├── CHANGELOG.rdoc ├── CONTRIBUTORS ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── UPGRADE.rdoc ├── gemfiles ├── activerecord_4.gemfile ├── activerecord_5.gemfile ├── activerecord_6.gemfile ├── activerecord_7.gemfile ├── mongoid_5.gemfile ├── mongoid_6.gemfile └── mongoid_7.gemfile ├── lib ├── generators │ ├── active_record │ │ ├── rolify_generator.rb │ │ └── templates │ │ │ ├── README │ │ │ ├── migration.rb │ │ │ └── model.rb │ ├── mongoid │ │ ├── rolify_generator.rb │ │ └── templates │ │ │ └── README-mongoid │ └── rolify │ │ ├── rolify_generator.rb │ │ ├── templates │ │ ├── README │ │ ├── initializer.rb │ │ ├── role-active_record.rb │ │ └── role-mongoid.rb │ │ └── user_generator.rb ├── rolify.rb └── rolify │ ├── adapters │ ├── active_record │ │ ├── resource_adapter.rb │ │ ├── role_adapter.rb │ │ └── scopes.rb │ ├── base.rb │ └── mongoid │ │ ├── resource_adapter.rb │ │ ├── role_adapter.rb │ │ └── scopes.rb │ ├── configure.rb │ ├── dynamic.rb │ ├── finders.rb │ ├── matchers.rb │ ├── railtie.rb │ ├── resource.rb │ ├── role.rb │ ├── utils.rb │ └── version.rb ├── rolify.gemspec └── spec ├── README.rdoc ├── common_helper.rb ├── generators └── rolify │ ├── rolify_activerecord_generator_spec.rb │ └── rolify_mongoid_generator_spec.rb ├── generators_helper.rb ├── rolify ├── config_spec.rb ├── coverage │ ├── .last_run.json │ ├── .resultset.json │ └── .resultset.json.lock ├── custom_spec.rb ├── matchers_spec.rb ├── namespace_spec.rb ├── resource_spec.rb ├── resourcifed_and_rolifed_spec.rb ├── role_spec.rb ├── shared_contexts.rb ├── shared_examples │ ├── shared_examples_for_add_role.rb │ ├── shared_examples_for_callbacks.rb │ ├── shared_examples_for_dynamic.rb │ ├── shared_examples_for_finders.rb │ ├── shared_examples_for_has_all_roles.rb │ ├── shared_examples_for_has_any_role.rb │ ├── shared_examples_for_has_role.rb │ ├── shared_examples_for_only_has_role.rb │ ├── shared_examples_for_remove_role.rb │ ├── shared_examples_for_roles.rb │ └── shared_examples_for_scopes.rb └── utils_spec.rb ├── spec_helper.rb └── support ├── adapters ├── active_record.rb ├── mongoid.rb ├── mongoid_5.yml ├── mongoid_6.yml ├── mongoid_7.yml └── utils │ ├── active_record.rb │ └── mongoid.rb ├── data.rb ├── schema.rb └── stream_helpers.rb /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/activerecord.yml: -------------------------------------------------------------------------------- 1 | name: activerecord 2 | on: [push, pull_request] 3 | jobs: 4 | ubuntu: 5 | runs-on: ubuntu-latest 6 | continue-on-error: true 7 | strategy: 8 | matrix: 9 | gemfile: [activerecord_4, activerecord_5, activerecord_6] 10 | ruby: [2.5.9, 2.6.10, 2.7.7] 11 | include: 12 | - gemfile: activerecord_7 13 | ruby: '3.2' 14 | - gemfile: activerecord_7 15 | ruby: '3.1' 16 | - gemfile: activerecord_7 17 | ruby: '3.0' 18 | - gemfile: activerecord_6 19 | ruby: '3.0' 20 | env: 21 | BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile 22 | ADAPTER: active_record 23 | steps: 24 | - uses: actions/checkout@v3 25 | - run: sudo apt-get install -y libsqlite3-dev 26 | - name: Setup ruby 27 | uses: ruby/setup-ruby@v1 28 | with: 29 | ruby-version: ${{ matrix.ruby }} 30 | bundler-cache: true 31 | - name: Run Tests 32 | run: | 33 | bundle exec rake 34 | -------------------------------------------------------------------------------- /.github/workflows/mongoid.yml: -------------------------------------------------------------------------------- 1 | name: mongoid 2 | on: [push, pull_request] 3 | jobs: 4 | ubuntu: 5 | runs-on: ubuntu-latest 6 | continue-on-error: true 7 | services: 8 | mongodb: 9 | image: mongo:3.4.23 10 | ports: 11 | - 27017:27017 12 | strategy: 13 | matrix: 14 | gemfile: [mongoid_5] 15 | ruby: [2.5.9, 2.6.10, 2.7.7] 16 | env: 17 | BUNDLE_GEMFILE: gemfiles/${{ matrix.gemfile }}.gemfile 18 | ADAPTER: mongoid 19 | steps: 20 | - uses: actions/checkout@v3 21 | - run: sudo apt-get install -y libsqlite3-dev 22 | - name: Setup ruby 23 | uses: ruby/setup-ruby@v1 24 | with: 25 | ruby-version: ${{ matrix.ruby }} 26 | bundler-cache: true 27 | - name: Run Tests 28 | run: | 29 | bundle exec rake 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | .bundle 3 | Gemfile.lock 4 | gemfiles/*.lock 5 | pkg/* 6 | tmp/* 7 | coverage/* 8 | log*/* 9 | .rbx/* 10 | .rspec 11 | *.swp 12 | -------------------------------------------------------------------------------- /.hakiri.yml: -------------------------------------------------------------------------------- 1 | dependency_source: gemspec_file 2 | -------------------------------------------------------------------------------- /Appraisals: -------------------------------------------------------------------------------- 1 | appraise 'mongoid-5' do 2 | gem "mongoid", "~> 5" 3 | gem "mongo", "< 2.17" 4 | gem "bson_ext", "1.5.1" 5 | gem 'bigdecimal', '1.4.2' 6 | end 7 | 8 | appraise 'mongoid-6' do 9 | gem "mongoid", "~> 6" 10 | gem "bson_ext", "1.5.1" 11 | end 12 | 13 | appraise 'mongoid-7' do 14 | gem "mongoid", "~> 7" 15 | gem "bson_ext", "1.5.1" 16 | gem "railties", "5.2.4.1" 17 | end 18 | 19 | appraise 'activerecord-4' do 20 | gem "sqlite3", "~> 1.3.6" 21 | gem "activerecord", "~> 4.2.11", :require => "active_record" 22 | gem 'bigdecimal', '1.4.2' 23 | end 24 | 25 | appraise 'activerecord-5' do 26 | gem "sqlite3", "~> 1.3.6" 27 | gem "activerecord", "~> 5.2.4", :require => "active_record" 28 | 29 | # Ammeter dependencies: 30 | gem "actionpack", "~> 5.2.4" 31 | gem "activemodel", "~> 5.2.4" 32 | gem "railties", "~> 5.2.4" 33 | end 34 | 35 | appraise 'activerecord-6' do 36 | gem "sqlite3", "~> 1.4", :platform => "ruby" 37 | gem "activerecord", ">= 6.0.0", :require => "active_record" 38 | 39 | # Ammeter dependencies: 40 | gem "actionpack", ">= 6.0.0" 41 | gem "activemodel", ">= 6.0.0" 42 | gem "railties", ">= 6.0.0" 43 | end 44 | -------------------------------------------------------------------------------- /CHANGELOG.rdoc: -------------------------------------------------------------------------------- 1 | == 6.0.1 2 | 3 | * Fix with_role with :any resource 4 | 5 | = 6.0.0 (April 2, 2021) 6 | 7 | Breaking changes: 8 | 9 | * Support strict with_role queries. Note this changes behaviour of with_role when running in strict mode to behave as previously documented. #543 describes the behaviour change. 10 | * Dropped support for Ruby 2.3 and 2.4 11 | 12 | Improvements: 13 | 14 | * Updated ActiveRecord adapters implementation of in to utilise a subquery instead of two queries 15 | 16 | = 5.3.0 (June 1, 2020) 17 | 18 | 5.3.0 will be the last version to support Ruby < 2.5 and Rails < 5.2. Support for these will be dropped in the next major release. 19 | 20 | * Fix deprecation warning in Ruby 2.7 21 | * Add Rails 6 support to migration generator 22 | * Significant ActiveRecord performance improvements to add_role and without_role 23 | * Mongoid fix and performance improvement and to roles_name 24 | * Make it safe to call Thing.with_role(:admin, user) with new record 25 | 26 | = 5.2.0 (Dec 14, 2017) 27 | * Fix regression in generator around belongs_to options compatibility 28 | * Update version of database_cleaner 29 | * Update initializer.rb to include the remove_role_if_empty option and description 30 | * Allow inverse_of option on rolify method 31 | * Fix the code-climate-reporter issue that's causing travis to fail 32 | * Remove a separate index for the name column 33 | * Fix migration generator for AR 5.x 34 | * Fixed ambiguous column error 35 | 36 | = 5.1.0 (Mar 19, 2016) 37 | * Rails 5 Support (thanks @lorefnon) 38 | * Fix user handling in generator (thanks @lorefnon) 39 | * Fix quoting issues (thanks @lorefnon) 40 | * Improvements to dynamic module loading (thanks to @DmitryKK) 41 | 42 | = 5.0.0 (Nov 17, 2015) 43 | * Fix migration warning showing when it shouldn't 44 | * Add role lazy-loading 45 | * Add has_cached_role and has_cached_strict_role to project. 46 | See: https://github.com/RolifyCommunity/rolify#cached-roles-to-avoid-n1-issue 47 | * Update specs to pass with all Mongoid version. 48 | 49 | = 4.1.1 (Aug 06, 2015) 50 | * Fix a regression in the dynamic method loader. 51 | 52 | = 4.1.0 (Aug 04, 2015) 53 | * added `without_role` method for resources and classes 54 | * fix `has_role?` with :any and an unsaved user 55 | * rename the `with_role` method on a resource to `find_as` 56 | * add a strict parameter to check only roles that were set without supers. 57 | 58 | = 4.0.0 (Feb 12, 2015) 59 | * Increasing version number to 4.0.0 for semantic versioning since `has_role` was removed in 3.5.0 60 | * Fix spec to pass. 61 | * Fix dynamic shortcuts being set before migration. 62 | 63 | = 3.5.2 (Jan 14, 2015) 64 | * Fixed regression in RolifyGenerator 65 | * Updated dependencies 66 | 67 | = 3.5.0 (Jan 08, 2015) 68 | * Removed `has_role` method alias for `add_role` 69 | * Fixed regression in caching system 70 | * Fix deprecation warning about timestamp on specs 71 | * Copy Kernel#capture helper from Rails 72 | * Add validation on Role.resource_type 73 | 74 | = 3.4.1 (Sep 08, 2014) 75 | * Fixed issue with migrations being generated without a file extension. 76 | * Updated spec tests to work with Travis CI on Ruby up to 2.1.2 and Rails 4.1.x 77 | * Made it possible for inheritance of resources. 78 | * Fixed small bugs throughout project. 79 | 80 | = 3.4 (Jan 28, 2014) 81 | * fixed an initializer bug preventing the rails app to boot 82 | * enhanced documentation regarding with_role method (thanks to @vicomte) 83 | * added select(:id) in SQL subquery used by in method in ActiveRecord adapter (thanks to @ryanaip and @badaboda) 84 | * improved speed when removing roles using LIMIT(1) instead of COUNT (thanks to @yankovski) 85 | * fixed travis builds for rubinius and mongoid 86 | * fixed sanity check running before Rolify.config to ensure roles table exists 87 | * fixed has_any_role? to work with unsaved records (thanks to @mhw) 88 | * fixed specs for Mongoid 89 | 90 | = 3.3 (Jan 26, 2014) 91 | * DEPRECATION NOTICE:Rails 3.1 support dropped: if you use Rails 3.1, please stick to rolify 3.2 92 | * code cleanup in finders methods 93 | * generators rewritten entirely. now using ActiveRecord/Mongoid model generator to create Role model 94 | * added rspec matchers for detailed spec error messages (thanks to @delwyn) 95 | * clean up specs (thanks to @delwyn), removed subject and let declarations in before(:all) block 96 | * roles query needs 1 DB hit instead of 1 up to 3 (thanks to @terraplane) 97 | * remove nil entries from ResourceAdapter#resources_find when using Mongoid adapter (thanks to @daviscabral) 98 | * fixed a bug regarding redundant index for Mongoid Role model (thanks to @rschultheis) 99 | * added support for rolify and resourcify methods on the same model class (specs by @amer) 100 | * added support for namespaced models (thanks to @intrica) 101 | * fixed compatibility issue with squeel when using symbols as role parameters (hint by @f3ndot) 102 | * now raises a warning in the initializer if migration has not been run 103 | * add support for primary key different than 'id' for resource Model (thanks to @rafaeldl) 104 | * Rails 4 (thanks to @adammathys) and ruby 2.0 compliant 105 | * configured travis-ci to run the specs on Rails 3.2/4.0 and Rubies 1.9.3/2.0/rbx/jruby 106 | * added code climate to check for code smell 107 | 108 | = 3.2 (Aug 7, 2012) 109 | * DEPRECATION NOTICE: Ruby 1.8 support dropped ! Mongoid 3.0 only supports MRI 1.9.3, and HEAD, and JRuby 1.6.0+ in 1.9 mode 110 | * removed dynamic_shortcuts arguments from the generator 111 | * to use dynamic shortcuts feature when you're using ActiveRecord, you have to enable it _after_ running rake db:migrate as it relies on the roles table 112 | * support for Mongoid 3.x (thanks to @Leonas) 113 | * new class methods on the User class to find users depending on roles they have 114 | * added scopes to Role class to be able to fetch global, class scoped and instance scoped roles for a specific user 115 | * deletions of n-n relation are unreliable with Mongoid. Removing ids instead (thanks to @nfo) 116 | * has_role? method now supports new instance (i.e. record not saved in the database yet) (thanks to @demental) 117 | * added association callbacks (before|after)_add, (before|after)_remove on rolify method (thanks to @shekibobo) 118 | * added ability to pass an array of roles to Resource.with_role(), aliased by Resource.with_roles() (thanks to @lukes) 119 | * added option to have roles be destroyed by default if parent resource is destroyed (thanks to @treydock) 120 | * added new method only_has_role? to check if user has only a specific role (thanks to @jalcine) 121 | * better edge cases covering in the specs 122 | * fixed a bug regarding the loading order of the railtie when using Mongoid ORM and other gems using initializer files (thanks to @stigi) 123 | * fixed double quote syntax when using MySQL 124 | * fixed a nasty bug regarding class level queries (thanks to @kamrulhassan) 125 | * fixed uninitialized constant error in scopify method 126 | * documentation improvement 127 | 128 | = 3.1 (Apr 6, 2012) 129 | * Mongoid adapter optimization 130 | * adapter code refactoring 131 | * generator now adds the role class name to the rolify method injected in the user class 132 | * fixed a bug on the generator when using a 2 words Camel case for the Role class name 133 | * DEPRECATION NOTICE: has_role and has_no_role have been depecrated. They are replaced by add_role and remove_role 134 | * some internals cleanup (backward compatible) 135 | * stop requiring active_record in rolify.rb to prevent other gems ORM detection issue 136 | * fixed a bug when removing a role to the user using Mongoid adapter 137 | * added indexes to generator for mongoid (thanks to @stigi) 138 | * fixed a bug regarding **with_role** method on resource classes (thanks to @nfo) 139 | 140 | = 3.0 (Apr 2, 2012) 141 | * support for Mongoid 142 | * roles search on resources on instance level (e.g. Forum.first.roles) and class level (e.g. Forum.with_role("admin", user)) 143 | * heavy lifting and redesign of the library, code and specs refactoring 144 | * enhanced drastically specs coverage: 1001 examples ! 145 | 146 | = 2.2.2 (Feb 17, 2012) 147 | * fixed another bug occurring when dynamic shortcuts is enabled 148 | * display now a README file after running the generator to show the next setup steps 149 | 150 | = 2.2.1 (Jan 24, 2012) 151 | * fixed a backward incompatible change introduced in Rails 3.2 release (find_or_create_by_* generated methods) 152 | 153 | = 2.2 (Jan 18, 2012) 154 | * fixed a bug in the initializer file regarding dynamic shortcuts 155 | 156 | = 2.1 (Nov 30, 2011) 157 | * added syntactic sugar: grant and revoke are aliases for has_role and has_no_role 158 | * check if RUBY_ENGINE is defined in the gemspec to be able to use jdbc with JRuby for SQLite 159 | 160 | = 2.0 (Nov 10, 2011) 161 | * improved performance of has_all_roles? method using one single DB query instead of doing one DB lookup per argument 162 | * significant speed-up when requesting with many arguments 163 | * database choice can mitigate the results 164 | * clean up the initializer code 165 | * using a DSL to configure the library 166 | * setting defaults for User and Role classes 167 | * dynamic shortcuts feature is now disabled by default. To turn it on: 168 | * set it to true in the initializer file 169 | * uncomment the extend Rolify::Dynamic line in the User class 170 | * detecting if it's loaded by Rails::Server or Rails::Console 171 | * now also running on Rubinius, JRuby, REE and Ruby 1.8. all specs pass successfully, yeah ! 172 | 173 | = 1.2 (Nov 4, 2011) 174 | * fixed a strange bug, probably rails related (thanks to @scottkf) 175 | * when using rails in development mode, the config.cache_classes = false makes the role class to be loaded at every call and can lead to a AssociationTypeMismatch 176 | * use of role_ids array instead of the roles association 177 | * now running on JRuby (specs are still failing for JRuby though) 178 | 179 | = 1.1 (Oct 14, 2011) 180 | * added a spec to test the rails generator using ammeter gem 181 | * Gemfile cleanup, moved all dependencies in gemspec instead 182 | * edited the dependency to Rails 3.1 and newer, now that Rails 3.1 has been released 183 | * new role scoping capabilities 184 | * instance level : user.has_role "moderator", Forum.first (already supported in previous release). user has the moderator role only on that Forum in particular 185 | * class level : user.has_role "moderator", Forum. User has the moderator role on all instances of Forum 186 | * global level : user.has_role "moderator" (already supported in previous release). User has the moderator role globally (e.q. on all resources) 187 | * new scoped query capabilities 188 | * user.has_role? "moderator", Forum.first (already supported in previous release). asks if the user has the moderator role on Forum.first instance 189 | * user.has_role? "moderator", Forum. asks if the user has the moderator role on all Forum instances 190 | * user.has_role? "moderator" (already supported in previous release). asks if the user has the global moderator role 191 | * user.has_role? "moderator", :any. asks if the user has at least one moderator role no matter the scope is (instance, class or global). 192 | 193 | = 1.0 (Aug 25, 2011) 194 | * added a new parameter to disable dynamic shortcut methods due to potential incompatibility with other gems using method_missing with the same pattern 195 | * add Rolify.dynamic_shortcuts = false in the initializer file or 196 | * use the generator command with a third parameter: 197 | * rails g rolify:role Role User false 198 | * removed the railtie as it created more problems than it solved 199 | * code refactoring to do some speed improvements and code clean up 200 | * added a lot of specs to improve tests coverage 201 | * wrote a tutorial showing how to use rolify with CanCan and Devise 202 | * rolify is now on travis-ci to monitor build status 203 | 204 | = 0.7 (June 20, 2011) 205 | * added a method_missing to catch newly created role outside the current ruby process (i.e. dynamic shortcut methods are not defined within this process) 206 | * dynamic shortcut is created on the fly in the method_missing to avoid extra method_missing for the same dynamic shortcut 207 | * check if the role actually exists in the database before defining the new method 208 | * first call is slower due to method_missing but next calls are fast 209 | * avoid strange bugs when spawning many ruby processes as the dynamic shortcut methods were only defined in the process that used the has_role command 210 | 211 | = 0.6 (June 19, 2011) 212 | * custom User and Role class names support 213 | * can now use other class names for Role and User classes 214 | * fixed generators and templates 215 | * join table is explicitly set to avoid alphabetical order issue 216 | * created a new railtie to load the dynamic shortcuts at startup 217 | 218 | = 0.5.1 (June 07, 2011) 219 | * fixed a nasty typo on a variable name and added a spec to make it never happen again 220 | 221 | = 0.5 (June 07, 2011) 222 | * dynamic shortcuts support 223 | * creates automatically new methods upon new role creation (or at startup for a Rails app) 224 | * has_role "admin" will create a method called is_admin? 225 | * has_role "moderator", Forum.first will create 2 methods: 226 | * is_moderator_of?(resource) 227 | * is_moderator? 228 | 229 | = v0.4 (June 07, 2011) 230 | * removing role support 231 | * has_no_role removes a global role or a role scoped to a resource 232 | * Please note that trying to remove a global role whereas the user a role with the same name on a resource will remove that scoped role 233 | * Trying to remove a role scoped to a resource whereas the user has a global role won't remove it 234 | 235 | = v0.3 (June 06, 2011) 236 | * multiple roles check: 237 | * has_all_roles? returns true if the user has ALL the roles in arguments 238 | * has_any_role? returns true if the user has ANY the roles in arguments 239 | 240 | = v0.2 (June 04, 2011) 241 | * fixed the generator to include the lib 242 | * fixed the migration file with missing polymorphic field 243 | * added some examples in documentation 244 | 245 | = v0.1 (June 04, 2011) 246 | * first release 247 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Florent Monbillard (@EppO) - Original Author 2 | Wellington Cordeiro (@wldcordeiro) 3 | Joel Azemar (@joel) 4 | Alex Klim (@AlexKlim) 5 | Mauro George (@MauroGeorge) 6 | Cheri Allen(@cherimarie) 7 | Gaurab Paul (@lorefnon) 8 | Dmitry Krakosevich (@DmitryKK) 9 | Michael Watts (@mikwat) 10 | Derek Ethier (@ethier) 11 | Michael (@mibamur) 12 | Undo1 (@Undo1) 13 | Hitabis Engineering (@Hitabis) 14 | Sergey Alekseev (@sergey-alekseev) 15 | Sankalp Kulshreshtha (@sankalpk) 16 | Kirill Sevastyanenko (@kirillseva) 17 | Denis Shevchenko (@SimplySorc) 18 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | group :test do 4 | gem 'codeclimate-test-reporter', require: nil 5 | gem 'coveralls', require: false 6 | gem 'database_cleaner', '~> 1.6.2' 7 | gem 'its' 8 | gem 'test-unit' # Implicitly loaded by ammeter 9 | 10 | gem 'byebug' 11 | gem 'pry' 12 | gem 'pry-byebug' 13 | end 14 | 15 | gemspec 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Florent Monbillard 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 | # rolify [![Gem Version](https://badge.fury.io/rb/rolify.svg)](http://badge.fury.io/rb/rolify) [![build status](https://travis-ci.org/RolifyCommunity/rolify.svg)](http://travis-ci.org/RolifyCommunity/rolify) [![Code Climate](https://codeclimate.com/github/RolifyCommunity/rolify.svg)](https://codeclimate.com/github/RolifyCommunity/rolify) [![Coverage Status](https://coveralls.io/repos/RolifyCommunity/rolify/badge.svg?branch=master&service=github)](https://coveralls.io/github/RolifyCommunity/rolify?branch=master) 2 | 3 | Very simple Roles library without any authorization enforcement supporting scope on resource object. 4 | 5 | Let's see an example: 6 | 7 | ```ruby 8 | user.has_role?(:moderator, @forum) 9 | => false # if user is moderator of another Forum 10 | ``` 11 | 12 | This library can be easily integrated with any authentication gem ([devise](https://github.com/plataformatec/devise), [Authlogic](https://github.com/binarylogic/authlogic), [Clearance](https://github.com/thoughtbot/clearance)) and authorization gem* ([CanCanCan](https://github.com/CanCanCommunity/cancancan), [authority](https://github.com/nathanl/authority), [Pundit](https://github.com/elabs/pundit)) 13 | 14 | *: authorization gem that doesn't provide a role class 15 | 16 | ## Requirements 17 | 18 | * Rails >= 4.2 19 | * ActiveRecord >= 4.2 or Mongoid >= 4.0 20 | * supports ruby 2.2+, JRuby 1.6.0+ (in 1.9 mode) and Rubinius 2.0.0dev (in 1.9 mode) 21 | * support of ruby 1.8 has been dropped due to Mongoid >=3.0 that only supports 1.9 new hash syntax 22 | 23 | ## Installation 24 | 25 | Add this to your Gemfile and run the `bundle` command. 26 | 27 | ```ruby 28 | gem "rolify" 29 | ``` 30 | 31 | ## Getting Started 32 | 33 | ### 1. Generate Role Model 34 | 35 | First, use the generator to setup Rolify. Role and User class are the default names. However, you can specify any class name you want. For the User class name, you would probably use the one provided by your authentication solution. 36 | 37 | If you want to use Mongoid instead of ActiveRecord, just add `--orm=mongoid` argument, and skip to step #3. 38 | 39 | ``` 40 | rails g rolify Role User 41 | ``` 42 | 43 | **NB** for versions of Rolify prior to 3.3, use: 44 | 45 | ``` 46 | rails g rolify:role Role User 47 | ``` 48 | 49 | The generator will create your Role model, add a migration file, and update your User class with new class methods. 50 | 51 | ### 2. Run the migration (only required when using ActiveRecord) 52 | 53 | Let's migrate! 54 | 55 | ``` 56 | rake db:migrate 57 | ``` 58 | 59 | ### 3.1 Configure your user model 60 | 61 | This gem adds the `rolify` method to your User class. You can also specify optional callbacks on the User class for when roles are added or removed: 62 | 63 | ```ruby 64 | class User < ActiveRecord::Base 65 | rolify :before_add => :before_add_method 66 | 67 | def before_add_method(role) 68 | # do something before it gets added 69 | end 70 | end 71 | ``` 72 | 73 | The `rolify` method accepts the following callback options: 74 | 75 | - `before_add` 76 | - `after_add` 77 | - `before_remove` 78 | - `after_remove` 79 | 80 | Mongoid callbacks are also supported and works the same way. 81 | 82 | The `rolify` method also accepts the `inverse_of` option if you need to disambiguate the relationship. 83 | 84 | ### 3.2 Configure your resource models 85 | 86 | In the resource models you want to apply roles on, just add ``resourcify`` method. 87 | For example, on this ActiveRecord class: 88 | 89 | ```ruby 90 | class Forum < ActiveRecord::Base 91 | resourcify 92 | end 93 | ``` 94 | 95 | ### 3.3 Assign default role 96 | 97 | ```ruby 98 | class User < ActiveRecord::Base 99 | after_create :assign_default_role 100 | 101 | def assign_default_role 102 | self.add_role(:newuser) if self.roles.blank? 103 | end 104 | end 105 | ``` 106 | 107 | ### 4. Add a role to a user 108 | 109 | To define a global role: 110 | 111 | ```ruby 112 | user = User.find(1) 113 | user.add_role :admin 114 | ``` 115 | 116 | To define a role scoped to a resource instance: 117 | 118 | ```ruby 119 | user = User.find(2) 120 | user.add_role :moderator, Forum.first 121 | ``` 122 | 123 | To define a role scoped to a resource class: 124 | 125 | ```ruby 126 | user = User.find(3) 127 | user.add_role :moderator, Forum 128 | ``` 129 | 130 | Remove role: 131 | ```ruby 132 | user = User.find(3) 133 | user.remove_role :moderator 134 | ``` 135 | 136 | That's it! 137 | 138 | ### 5. Role queries 139 | 140 | To check if a user has a global role: 141 | 142 | ```ruby 143 | user = User.find(1) 144 | user.add_role :admin # sets a global role 145 | user.has_role? :admin 146 | => true 147 | ``` 148 | 149 | To check if a user has a role scoped to a resource instance: 150 | 151 | ```ruby 152 | user = User.find(2) 153 | user.add_role :moderator, Forum.first # sets a role scoped to a resource instance 154 | user.has_role? :moderator, Forum.first 155 | => true 156 | user.has_role? :moderator, Forum.last 157 | => false 158 | ``` 159 | 160 | To check if a user has a role scoped to a resource class: 161 | 162 | ```ruby 163 | user = User.find(3) 164 | user.add_role :moderator, Forum # sets a role scoped to a resource class 165 | user.has_role? :moderator, Forum 166 | => true 167 | user.has_role? :moderator, Forum.first 168 | => true 169 | user.has_role? :moderator, Forum.last 170 | => true 171 | ``` 172 | 173 | A global role overrides resource role request: 174 | 175 | ```ruby 176 | user = User.find(4) 177 | user.add_role :moderator # sets a global role 178 | user.has_role? :moderator, Forum.first 179 | => true 180 | user.has_role? :moderator, Forum.last 181 | => true 182 | ``` 183 | 184 | To check if a user has the exact role scoped to a resource class: 185 | 186 | ```ruby 187 | user = User.find(5) 188 | user.add_role :moderator # sets a global role 189 | user.has_role? :moderator, Forum.first 190 | => true 191 | user.has_strict_role? :moderator, Forum.last 192 | => false 193 | ``` 194 | 195 | ### 6. Resource roles querying 196 | 197 | Starting from rolify 3.0, you can search roles on instance level or class level resources. 198 | 199 | #### Instance level 200 | 201 | ```ruby 202 | forum = Forum.first 203 | forum.roles 204 | # => [ list of roles that are only bound to forum instance ] 205 | forum.applied_roles 206 | # => [ list of roles bound to forum instance and to the Forum class ] 207 | ``` 208 | 209 | #### Class level 210 | 211 | ```ruby 212 | Forum.with_role(:admin) 213 | # => [ list of Forum instances that have role "admin" bound to them ] 214 | Forum.without_role(:admin) 215 | # => [ list of Forum instances that do NOT have role "admin" bound to them ] 216 | Forum.with_role(:admin, current_user) 217 | # => [ list of Forum instances that have role "admin" bound to them and belong to current_user roles ] 218 | Forum.with_roles([:admin, :user], current_user) 219 | # => [ list of Forum instances that have role "admin" or "user" bound to them and belong to current_user roles ] 220 | 221 | User.with_any_role(:user, :admin) 222 | # => [ list of User instances that have role "admin" or "user" bound to them ] 223 | User.with_role(:site_admin, current_site) 224 | # => [ list of User instances that have a scoped role of "site_admin" to a site instance ] 225 | User.with_role(:site_admin, :any) 226 | # => [ list of User instances that have a scoped role of "site_admin" for any site instances ] 227 | User.with_all_roles(:site_admin, :admin) 228 | # => [ list of User instances that have a role of "site_admin" and a role of "admin" bound to it ] 229 | 230 | Forum.find_roles 231 | # => [ list of roles that are bound to any Forum instance or to the Forum class ] 232 | Forum.find_roles(:admin) 233 | # => [ list of roles that are bound to any Forum instance or to the Forum class, with "admin" as a role name ] 234 | Forum.find_roles(:admin, current_user) 235 | # => [ list of roles that are bound to any Forum instance, or to the Forum class with "admin" as a role name, and belongs to current_user ] 236 | ``` 237 | 238 | ### Strict Mode 239 | 240 | ```ruby 241 | class User < ActiveRecord::Base 242 | rolify strict: true 243 | end 244 | 245 | @user = User.first 246 | 247 | @user.add_role(:forum, Forum) 248 | @user.add_role(:forum, Forum.first) 249 | 250 | @user.has_role?(:forum, Forum) #=> true 251 | @user.has_role?(:forum, Forum.first) #=> true 252 | @user.has_role?(:forum, Forum.last) #=> false 253 | ``` 254 | I.e. you get true only on a role that you manually add. 255 | 256 | ### Cached Roles (to avoid N+1 issue) 257 | 258 | ```ruby 259 | @user.add_role :admin, Forum 260 | @user.add_role :member, Forum 261 | 262 | users = User.with_role(:admin, Forum).preload(:roles) 263 | users.each do |user| 264 | user.has_cached_role?(:member, Forum) # no extra queries 265 | end 266 | ``` 267 | 268 | This method should be used with caution. If you don't preload the roles, the `has_cached_role?` might return `false`. In the above example, it would return `false` for `@user.has_cached_role?(:member, Forum)`, because `User.with_role(:admin, Forum)` will load only the `:admin` roles. 269 | 270 | ## Resources 271 | 272 | * [Wiki](https://github.com/RolifyCommunity/rolify/wiki) 273 | * [Usage](https://github.com/RolifyCommunity/rolify/wiki/Usage): all the available commands 274 | * [Tutorials](https://github.com/RolifyCommunity/rolify/wiki#wiki-tutorials): 275 | * [How-To use rolify with Devise and CanCanCan](https://github.com/RolifyCommunity/rolify/wiki/Devise---CanCanCan---rolify-Tutorial) 276 | * [Using rolify with Devise and Authority](https://github.com/RolifyCommunity/rolify/wiki/Using-rolify-with-Devise-and-Authority) 277 | * [Step-by-step tutorial](http://railsapps.github.com/tutorial-rails-bootstrap-devise-cancan.html) provided by [RailsApps](http://railsapps.github.com/) 278 | 279 | ## Upgrade from previous versions 280 | 281 | Please read the [upgrade instructions](UPGRADE.rdoc). 282 | 283 | ## Known issues 284 | 285 | * If you are using Mongoid and/or less-rails gem, please read [this](https://github.com/RolifyCommunity/rolify/wiki/FAQ#when-i-start-rails-using-server-console-whatever-i-get-this-error) 286 | * Moped library (ruby driver for Mongodb used by Mongoid) doesn't support rubinius 2.2 yet (see https://github.com/mongoid/moped/issues/231) 287 | * If you use Rails 4 and Mongoid, use Mongoid ~> 4. rolify is fully tested with Rails 4 and Mongoid 4. 288 | 289 | ## Questions or Problems? 290 | 291 | If you have any issue or feature request with/for rolify, please create an new [issue on GitHub](https://github.com/RolifyCommunity/rolify/issues) **specifying the ruby runtime, rails and rolify versions you're using and the gems listed in your Gemfile**, or fork the project and send a pull request. 292 | 293 | To get the specs running you should call `bundle` and then `rake`. See the spec/README for more information. 294 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | require 'rspec/core/rake_task' 3 | require 'coveralls/rake/task' 4 | require 'appraisal' 5 | 6 | Bundler::GemHelper.install_tasks 7 | 8 | Coveralls::RakeTask.new 9 | 10 | RSpec::Core::RakeTask.new(:generators) do |task| 11 | task.pattern = 'spec/generators/**/*_spec.rb' 12 | end 13 | 14 | RSpec::Core::RakeTask.new(:rolify) do |task| 15 | task.pattern = 'spec/rolify/**/*_spec.rb' 16 | end 17 | 18 | if !ENV["APPRAISAL_INITIALIZED"] && !ENV["CI"] 19 | task :default => :appraisal 20 | else 21 | task :default => [ :spec, 'coveralls:push' ] 22 | end 23 | 24 | desc 'Run all specs' 25 | task 'spec' do 26 | Rake::Task['generators'].invoke 27 | return_code1 = $?.exitstatus 28 | Rake::Task['rolify'].invoke 29 | return_code2 = $?.exitstatus 30 | fail if return_code1 != 0 || return_code2 != 0 31 | end 32 | 33 | desc 'Run specs for all adapters' 34 | task :spec_all do 35 | %w[active_record mongoid].each do |model_adapter| 36 | puts "ADAPTER = #{model_adapter}" 37 | system "ADAPTER=#{model_adapter} rake" 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /UPGRADE.rdoc: -------------------------------------------------------------------------------- 1 | = Upgrade instructions for Rolify library 2 | 3 | == From a previous rolify installation (1.x or 2.x) 4 | 5 | The easiest way is to re-run the generator using rails g Rolify:Role command, and overwrite the initializer file. Both the migration file and the role.rb haven't changed. 6 | In the config/initializers/rolify.rb file, here are the deprecated settings: 7 | * role_cname 8 | * user_cname 9 | By default, these settings were commented out, so you can safely remove them. 10 | If you changed them, you can remove them in the initializer file too. user_cname is no longer used in 3.x, you don't need it anymore. 11 | Add your custom role_cname setting as argument of the rolify method you use in your User class. 12 | For instance, if you use Privilege as Role class, you should add this to your whatever your User class is, let's say Client for the sake of example: 13 | class Client < ActiveRecord::Base 14 | rolify :role_cname => "Privilege" 15 | end 16 | If you use the default Role class name, you don't have to specify the :role_cname argument. 17 | 18 | If you use dynamic methods (user.is_admin? like methods), you should turn it on using use_dynamic_shortcuts method starting from rolify 3.0: 19 | Rolify.configure do |c| 20 | c.use_dynamic_shortcuts 21 | end 22 | The old fashion way still works though, but won't work anymore if the setter method name is changed in a possible future. You've been warned :-) 23 | 24 | == From a rolify installation 3.x 25 | 26 | === Dependencies: 27 | 28 | Starting from 3.3 release, rolify supports Rails 3.2 and newer. 29 | 30 | Mongoid callbacks are supported if Mongoid 3.1 and newer is installed. 31 | 32 | === Rails Generators 33 | 34 | Role model template when using Mongoid has been changed, you should re-run the generator. 35 | 36 | Rails generator has been renamed to: rails g rolify and arguments have been changed: 37 | * Role class name is now mandatory, User class name remains optional and its default is still User 38 | * ORM optional argument is now -o or --orm= 39 | For instance, here is a new rolify generator command example: rails g rolify Role 40 | You can use rails g rolify --help to see all available options. 41 | 42 | === Testing 43 | 44 | Starting from rolify 3.3, to run the specs you should run: rake spec or simply rake. 45 | -------------------------------------------------------------------------------- /gemfiles/activerecord_4.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "sqlite3", "~> 1.3.6" 6 | gem "activerecord", "~> 4.2.11", require: "active_record" 7 | gem "bigdecimal", "1.4.2" 8 | 9 | group :test do 10 | gem "codeclimate-test-reporter", require: nil 11 | gem "coveralls", require: false 12 | gem "database_cleaner", "~> 1.6.2" 13 | gem "its" 14 | gem "test-unit" 15 | gem "byebug" 16 | gem "pry" 17 | gem "pry-byebug" 18 | end 19 | 20 | gemspec path: "../" 21 | -------------------------------------------------------------------------------- /gemfiles/activerecord_5.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "sqlite3", "~> 1.3.6" 6 | gem "activerecord", "~> 5.2.4", require: "active_record" 7 | gem "actionpack", "~> 5.2.4" 8 | gem "activemodel", "~> 5.2.4" 9 | gem "railties", "~> 5.2.4" 10 | 11 | group :test do 12 | gem "codeclimate-test-reporter", require: nil 13 | gem "coveralls", require: false 14 | gem "database_cleaner", "~> 1.6.2" 15 | gem "its" 16 | gem "test-unit" 17 | gem "byebug" 18 | gem "pry" 19 | gem "pry-byebug" 20 | end 21 | 22 | gemspec path: "../" 23 | -------------------------------------------------------------------------------- /gemfiles/activerecord_6.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "sqlite3", "~> 1.4", platform: "ruby" 6 | gem "activerecord", "~> 6.1.0", require: "active_record" 7 | gem "actionpack", "~> 6.1.0" 8 | gem "activemodel", "~> 6.1.0" 9 | gem "railties", "~> 6.1.0" 10 | 11 | group :test do 12 | gem "codeclimate-test-reporter", require: nil 13 | gem "coveralls", require: false 14 | gem "database_cleaner", "~> 1.6.2" 15 | gem "its" 16 | gem "test-unit" 17 | gem "byebug" 18 | gem "pry" 19 | gem "pry-byebug" 20 | end 21 | 22 | gemspec path: "../" 23 | -------------------------------------------------------------------------------- /gemfiles/activerecord_7.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "sqlite3", "~> 1.4", platform: "ruby" 6 | gem "activerecord", "~> 7.0.0", require: "active_record" 7 | gem "actionpack", "~> 7.0.0" 8 | gem "activemodel", "~> 7.0.0" 9 | gem "railties", "~> 7.0.0" 10 | 11 | group :test do 12 | gem "codeclimate-test-reporter", require: nil 13 | gem "coveralls", require: false 14 | gem "database_cleaner", "~> 1.6.2" 15 | gem "its" 16 | gem "test-unit" 17 | gem "byebug" 18 | gem "pry" 19 | gem "pry-byebug" 20 | end 21 | 22 | gemspec path: "../" 23 | -------------------------------------------------------------------------------- /gemfiles/mongoid_5.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "mongoid", "~> 5" 6 | gem "mongo", "< 2.17" 7 | gem "bson_ext", "1.5.1" 8 | gem "bigdecimal", "1.4.2" 9 | 10 | group :test do 11 | gem "codeclimate-test-reporter", require: nil 12 | gem "coveralls", require: false 13 | gem "database_cleaner", "~> 1.6.2" 14 | gem "its" 15 | gem "test-unit" 16 | gem "byebug" 17 | gem "pry" 18 | gem "pry-byebug" 19 | end 20 | 21 | gemspec path: "../" 22 | -------------------------------------------------------------------------------- /gemfiles/mongoid_6.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "mongoid", "~> 6" 6 | gem "bson_ext", "1.5.1" 7 | 8 | group :test do 9 | gem "codeclimate-test-reporter", require: nil 10 | gem "coveralls", require: false 11 | gem "database_cleaner", "~> 1.6.2" 12 | gem "its" 13 | gem "test-unit" 14 | gem "byebug" 15 | gem "pry" 16 | gem "pry-byebug" 17 | end 18 | 19 | gemspec path: "../" 20 | -------------------------------------------------------------------------------- /gemfiles/mongoid_7.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "mongoid", "~> 7" 6 | gem "bson_ext", "1.5.1" 7 | gem "railties", "5.2.4.1" 8 | 9 | group :test do 10 | gem "codeclimate-test-reporter", require: nil 11 | gem "coveralls", require: false 12 | gem "database_cleaner", "~> 1.6.2" 13 | gem "its" 14 | gem "test-unit" 15 | gem "byebug" 16 | gem "pry" 17 | gem "pry-byebug" 18 | end 19 | 20 | gemspec path: "../" 21 | -------------------------------------------------------------------------------- /lib/generators/active_record/rolify_generator.rb: -------------------------------------------------------------------------------- 1 | require 'rails/generators/active_record' 2 | require 'active_support/core_ext' 3 | require 'erb' 4 | 5 | module ActiveRecord 6 | module Generators 7 | class RolifyGenerator < ActiveRecord::Generators::Base 8 | source_root File.expand_path("../templates", __FILE__) 9 | 10 | argument :user_cname, :type => :string, :default => "User", :banner => "User" 11 | 12 | def ensure_user_class_defined 13 | unless user_class_defined? 14 | prompt_missing_user 15 | abort 16 | end 17 | end 18 | 19 | def generate_model 20 | invoke "active_record:model", [ name ], :migration => false 21 | end 22 | 23 | def inject_role_class 24 | if args[1]=="engine" 25 | if args[2]=="devise" 26 | require 'devise' 27 | require "#{ENGINE_ROOT}/config/initializers/devise.rb" 28 | require "#{ENGINE_ROOT}/app/models/#{user_cname.downcase}.rb" 29 | else 30 | require "#{ENGINE_ROOT}/app/models/#{user_cname.downcase}.rb" 31 | end 32 | end 33 | 34 | inject_into_class(model_path, class_name, model_content) 35 | end 36 | 37 | def copy_rolify_migration 38 | migration_template "migration.rb", "db/migrate/rolify_create_#{table_name}.rb", migration_version: migration_version 39 | end 40 | 41 | private 42 | 43 | def join_table 44 | user_class.table_name + "_" + table_name 45 | end 46 | 47 | def user_reference 48 | user_cname.demodulize.underscore 49 | end 50 | 51 | def role_reference 52 | class_name.demodulize.underscore 53 | end 54 | 55 | def model_path 56 | File.join("app", "models", "#{file_path}.rb") 57 | end 58 | 59 | def model_content 60 | ERB.new(File.read(File.join(__dir__, 'templates/model.rb'))).result(binding) 61 | end 62 | 63 | def user_class 64 | user_cname.constantize 65 | end 66 | 67 | def user_class_defined? 68 | user_class 69 | true 70 | rescue NameError => ex 71 | if ex.missing_name == user_cname 72 | false 73 | else 74 | raise ex 75 | end 76 | end 77 | 78 | def prompt_missing_user 79 | puts <= 5 87 | end 88 | 89 | def migration_version 90 | if versioned_migrations? 91 | "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]" 92 | end 93 | end 94 | 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /lib/generators/active_record/templates/README: -------------------------------------------------------------------------------- 1 | Now, you just have to run the migration using rake command: 2 | 3 | rake db:migrate 4 | 5 | and you will be able to add the resourcify method inside all models you want 6 | scoped by a role. 7 | 8 | =============================================================================== 9 | -------------------------------------------------------------------------------- /lib/generators/active_record/templates/migration.rb: -------------------------------------------------------------------------------- 1 | class RolifyCreate<%= table_name.camelize %> < ActiveRecord::Migration<%= migration_version %> 2 | def change 3 | create_table(:<%= table_name %>) do |t| 4 | t.string :name 5 | t.references :resource, :polymorphic => true 6 | 7 | t.timestamps 8 | end 9 | 10 | create_table(:<%= join_table %>, :id => false) do |t| 11 | t.references :<%= user_reference %> 12 | t.references :<%= role_reference %> 13 | end 14 | <% if ActiveRecord::Base.connection.class.to_s.demodulize != 'PostgreSQLAdapter' %><%= "\n " %>add_index(:<%= table_name %>, :name)<% end %> 15 | add_index(:<%= table_name %>, [ :name, :resource_type, :resource_id ]) 16 | add_index(:<%= join_table %>, [ :<%= user_reference %>_id, :<%= role_reference %>_id ]) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/generators/active_record/templates/model.rb: -------------------------------------------------------------------------------- 1 | has_and_belongs_to_many :<%= user_class.table_name %>, :join_table => :<%= join_table %> 2 | <% if Rails::VERSION::MAJOR < 5 %> 3 | belongs_to :resource, 4 | :polymorphic => true 5 | <% else %> 6 | belongs_to :resource, 7 | :polymorphic => true, 8 | :optional => true 9 | <% end %> 10 | 11 | validates :resource_type, 12 | :inclusion => { :in => Rolify.resource_types }, 13 | :allow_nil => true 14 | 15 | scopify 16 | -------------------------------------------------------------------------------- /lib/generators/mongoid/rolify_generator.rb: -------------------------------------------------------------------------------- 1 | require 'rails/generators/mongoid_generator' 2 | require 'active_support/core_ext' 3 | 4 | module Mongoid 5 | module Generators 6 | class RolifyGenerator < Rails::Generators::NamedBase 7 | source_root File.expand_path("../templates", __FILE__) 8 | 9 | argument :user_cname, :type => :string, :default => "User", :banner => "User" 10 | 11 | def generate_model 12 | invoke "mongoid:model", [ name ] 13 | end 14 | 15 | def inject_role_class 16 | inject_into_file(model_path, model_contents, :after => "include Mongoid::Document\n") 17 | end 18 | 19 | def user_reference 20 | user_cname.demodulize.underscore 21 | end 22 | 23 | def role_reference 24 | class_name.demodulize.underscore 25 | end 26 | 27 | def model_path 28 | File.join("app", "models", "#{file_path}.rb") 29 | end 30 | 31 | def model_contents 32 | content = < true 35 | 36 | field :name, :type => String 37 | 38 | index({ 39 | :name => 1, 40 | :resource_type => 1, 41 | :resource_id => 1 42 | }, 43 | { :unique => true}) 44 | 45 | validates :resource_type, 46 | :inclusion => { :in => Rolify.resource_types }, 47 | :allow_nil => true 48 | 49 | scopify 50 | RUBY 51 | content % { :user_cname => user_cname.constantize.collection_name } 52 | end 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/generators/mongoid/templates/README-mongoid: -------------------------------------------------------------------------------- 1 | Now, you are able to add the resourcify method inside all models you want 2 | scoped by a role. 3 | 4 | =============================================================================== 5 | -------------------------------------------------------------------------------- /lib/generators/rolify/rolify_generator.rb: -------------------------------------------------------------------------------- 1 | module Rolify 2 | module Generators 3 | class RolifyGenerator < Rails::Generators::NamedBase 4 | Rails::Generators::ResourceHelpers 5 | 6 | source_root File.expand_path('../templates', __FILE__) 7 | argument :user_cname, :type => :string, :default => "User" 8 | 9 | namespace :rolify 10 | hook_for :orm, :required => true 11 | 12 | desc "Generates a model with the given NAME and a migration file." 13 | 14 | def self.start(args, config) 15 | user_cname = args.size > 1 ? args[1] : "User" 16 | args.insert(1, user_cname) # 0 being the view name 17 | super 18 | end 19 | 20 | def inject_user_class 21 | invoke "rolify:user", [ user_cname, class_name ], :orm => options.orm 22 | end 23 | 24 | def copy_initializer_file 25 | template "initializer.rb", "config/initializers/rolify.rb" 26 | end 27 | 28 | def show_readme 29 | if behavior == :invoke 30 | readme "README" 31 | end 32 | end 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/generators/rolify/templates/README: -------------------------------------------------------------------------------- 1 | =============================================================================== 2 | 3 | An initializer file has been created here: config/initializers/rolify.rb, you 4 | can change rolify settings to match your needs. 5 | Defaults values are commented out. 6 | 7 | A Role class has been created in app/models (with the name you gave as 8 | argument otherwise the default is role.rb), you can add your own business logic 9 | inside. 10 | 11 | Inside your User class (or the name you gave as argument otherwise the default 12 | is user.rb), rolify method has been inserted to provide rolify methods. 13 | 14 | -------------------------------------------------------------------------------- /lib/generators/rolify/templates/initializer.rb: -------------------------------------------------------------------------------- 1 | Rolify.configure<%= "(\"#{class_name.camelize.to_s}\")" if class_name != "Role" %> do |config| 2 | # By default ORM adapter is ActiveRecord. uncomment to use mongoid 3 | <%= "# " if options.orm == :active_record || !options.orm %>config.use_mongoid 4 | 5 | # Dynamic shortcuts for User class (user.is_admin? like methods). Default is: false 6 | # config.use_dynamic_shortcuts 7 | 8 | # Configuration to remove roles from database once the last resource is removed. Default is: true 9 | # config.remove_role_if_empty = false 10 | end 11 | -------------------------------------------------------------------------------- /lib/generators/rolify/templates/role-active_record.rb: -------------------------------------------------------------------------------- 1 | class <%= role_cname.camelize %> < ActiveRecord::Base 2 | <% if need_table_prefix?(role_cname) %> 3 | def self.table_name_prefix 4 | <%= table_prefix(role_cname) %>_ 5 | end 6 | <% end %> 7 | has_and_belongs_to_many :<%= user_cname.tableize %>, :join_table => :<%= "#{table_name(user_cname, true)}_#{table_name(role_cname, true)}" %> 8 | belongs_to :resource, :polymorphic => true 9 | 10 | scopify 11 | end 12 | -------------------------------------------------------------------------------- /lib/generators/rolify/templates/role-mongoid.rb: -------------------------------------------------------------------------------- 1 | class <%= role_cname.camelize %> 2 | include Mongoid::Document 3 | 4 | has_and_belongs_to_many :<%= user_cname.tableize %> 5 | belongs_to :resource, :polymorphic => true 6 | 7 | field :name, :type => String 8 | 9 | index({ 10 | :name => 1, 11 | :resource_type => 1, 12 | :resource_id => 1 13 | }, 14 | { :unique => true}) 15 | 16 | scopify 17 | end 18 | -------------------------------------------------------------------------------- /lib/generators/rolify/user_generator.rb: -------------------------------------------------------------------------------- 1 | require 'rails/generators/migration' 2 | require 'active_support/core_ext' 3 | 4 | module Rolify 5 | module Generators 6 | class UserGenerator < Rails::Generators::NamedBase 7 | argument :role_cname, :type => :string, :default => "Role" 8 | class_option :orm, :type => :string, :default => "active_record" 9 | 10 | desc "Inject rolify method in the User class." 11 | 12 | def inject_user_content 13 | inject_into_file(model_path, :after => inject_rolify_method) do 14 | " rolify#{role_association}\n" 15 | end 16 | end 17 | 18 | def inject_rolify_method 19 | if options.orm == :active_record 20 | /class #{class_name.camelize}\n|class #{class_name.camelize} .*\n|class #{class_name.demodulize.camelize}\n|class #{class_name.demodulize.camelize} .*\n/ 21 | else 22 | /include Mongoid::Document\n|include Mongoid::Document .*\n/ 23 | end 24 | end 25 | 26 | def model_path 27 | File.join("app", "models", "#{file_path}.rb") 28 | end 29 | 30 | def role_association 31 | if role_cname != "Role" 32 | " :role_cname => '#{role_cname.camelize}'" 33 | else 34 | "" 35 | end 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/rolify.rb: -------------------------------------------------------------------------------- 1 | require 'rolify/adapters/base' 2 | require 'rolify/configure' 3 | require 'rolify/dynamic' 4 | require 'rolify/railtie' if defined?(Rails) 5 | require 'rolify/resource' 6 | require 'rolify/role' 7 | 8 | module Rolify 9 | extend Configure 10 | 11 | attr_accessor :role_cname, :adapter, :resource_adapter, :role_join_table_name, :role_table_name, :strict_rolify 12 | @@resource_types = [] 13 | 14 | def rolify(options = {}) 15 | include Role 16 | extend Dynamic if Rolify.dynamic_shortcuts 17 | 18 | options.reverse_merge!({:role_cname => 'Role'}) 19 | self.role_cname = options[:role_cname] 20 | self.role_table_name = self.role_cname.tableize.gsub(/\//, "_") 21 | 22 | default_join_table = "#{self.to_s.tableize.gsub(/\//, "_")}_#{self.role_table_name}" 23 | options.reverse_merge!({:role_join_table_name => default_join_table}) 24 | self.role_join_table_name = options[:role_join_table_name] 25 | 26 | rolify_options = { :class_name => options[:role_cname].camelize } 27 | rolify_options.merge!({ :join_table => self.role_join_table_name }) if Rolify.orm == "active_record" 28 | rolify_options.merge!(options.reject{ |k,v| ![ :before_add, :after_add, :before_remove, :after_remove, :inverse_of ].include? k.to_sym }) 29 | 30 | has_and_belongs_to_many :roles, **rolify_options 31 | 32 | self.adapter = Rolify::Adapter::Base.create("role_adapter", self.role_cname, self.name) 33 | 34 | #use strict roles 35 | self.strict_rolify = true if options[:strict] 36 | end 37 | 38 | def adapter 39 | return self.superclass.adapter unless self.instance_variable_defined? '@adapter' 40 | @adapter 41 | end 42 | 43 | def resourcify(association_name = :roles, options = {}) 44 | include Resource 45 | 46 | options.reverse_merge!({ :role_cname => 'Role', :dependent => :destroy }) 47 | resourcify_options = { :class_name => options[:role_cname].camelize, :as => :resource, :dependent => options[:dependent] } 48 | self.role_cname = options[:role_cname] 49 | self.role_table_name = self.role_cname.tableize.gsub(/\//, "_") 50 | 51 | has_many association_name, **resourcify_options 52 | 53 | self.resource_adapter = Rolify::Adapter::Base.create("resource_adapter", self.role_cname, self.name) 54 | @@resource_types << self.name 55 | end 56 | 57 | def resource_adapter 58 | return self.superclass.resource_adapter unless self.instance_variable_defined? '@resource_adapter' 59 | @resource_adapter 60 | end 61 | 62 | def scopify 63 | require "rolify/adapters/#{Rolify.orm}/scopes.rb" 64 | extend Rolify::Adapter::Scopes 65 | end 66 | 67 | def role_class 68 | return self.superclass.role_class unless self.instance_variable_defined? '@role_cname' 69 | self.role_cname.constantize 70 | end 71 | 72 | def self.resource_types 73 | @@resource_types 74 | end 75 | 76 | end 77 | -------------------------------------------------------------------------------- /lib/rolify/adapters/active_record/resource_adapter.rb: -------------------------------------------------------------------------------- 1 | require 'rolify/adapters/base' 2 | 3 | module Rolify 4 | module Adapter 5 | class ResourceAdapter < ResourceAdapterBase 6 | def find_roles(role_name, relation, user) 7 | roles = user && (user != :any) ? user.roles : self.role_class 8 | roles = roles.where('resource_type IN (?)', self.relation_types_for(relation)) 9 | roles = roles.where(:name => role_name.to_s) if role_name && (role_name != :any) 10 | roles 11 | end 12 | 13 | def resources_find(roles_table, relation, role_name) 14 | klasses = self.relation_types_for(relation) 15 | relations = klasses.inject('') do |str, klass| 16 | str = "#{str}'#{klass.to_s}'" 17 | str << ', ' unless klass == klasses.last 18 | str 19 | end 20 | 21 | resources = relation.joins("INNER JOIN #{quote_table(roles_table)} ON #{quote_table(roles_table)}.resource_type IN (#{relations}) AND 22 | (#{quote_table(roles_table)}.resource_id IS NULL OR #{quote_table(roles_table)}.resource_id = #{quote_table(relation.table_name)}.#{quote_column(relation.primary_key)})") 23 | resources = resources.where("#{quote_table(roles_table)}.name IN (?) AND #{quote_table(roles_table)}.resource_type IN (?)", Array(role_name), klasses) 24 | resources = resources.select("#{quote_table(relation.table_name)}.*") 25 | resources 26 | end 27 | 28 | def in(relation, user, role_names) 29 | roles = user.roles.where(:name => role_names).select("#{quote_table(role_class.table_name)}.#{quote_column(role_class.primary_key)}") 30 | relation.where("#{quote_table(role_class.table_name)}.#{quote_column(role_class.primary_key)} IN (?) AND ((#{quote_table(role_class.table_name)}.resource_id = #{quote_table(relation.table_name)}.#{quote_column(relation.primary_key)}) OR (#{quote_table(role_class.table_name)}.resource_id IS NULL))", roles) 31 | end 32 | 33 | def applied_roles(relation, children) 34 | if children 35 | relation.role_class.where('resource_type IN (?) AND resource_id IS NULL', self.relation_types_for(relation)) 36 | else 37 | relation.role_class.where('resource_type = ? AND resource_id IS NULL', relation.to_s) 38 | end 39 | end 40 | 41 | def all_except(resource, excluded_obj) 42 | prime_key = resource.primary_key.to_sym 43 | resource.where.not(prime_key => excluded_obj.pluck(prime_key)) 44 | end 45 | 46 | private 47 | 48 | def quote_column(column) 49 | ActiveRecord::Base.connection.quote_column_name column 50 | end 51 | 52 | def quote_table(table) 53 | ActiveRecord::Base.connection.quote_table_name table 54 | end 55 | 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/rolify/adapters/active_record/role_adapter.rb: -------------------------------------------------------------------------------- 1 | require 'rolify/adapters/base' 2 | 3 | module Rolify 4 | module Adapter 5 | class RoleAdapter < RoleAdapterBase 6 | def where(relation, *args) 7 | conditions, values = build_conditions(relation, args) 8 | relation.where(conditions, *values) 9 | end 10 | 11 | def where_strict(relation, args) 12 | wrap_conditions = relation.name != role_class.name 13 | 14 | conditions = if args[:resource].is_a?(Class) 15 | {:resource_type => args[:resource].to_s, :resource_id => nil } 16 | elsif args[:resource].present? 17 | {:resource_type => args[:resource].class.name, :resource_id => args[:resource].id} 18 | else 19 | {} 20 | end 21 | 22 | conditions.merge!(:name => args[:name]) 23 | conditions = wrap_conditions ? { role_table => conditions } : conditions 24 | 25 | relation.where(conditions) 26 | end 27 | 28 | def find_cached(relation, args) 29 | resource_id = (args[:resource].nil? || args[:resource].is_a?(Class) || args[:resource] == :any) ? nil : args[:resource].id 30 | resource_type = args[:resource].is_a?(Class) ? args[:resource].to_s : args[:resource].class.name 31 | 32 | return relation.find_all { |role| role.name == args[:name].to_s } if args[:resource] == :any 33 | 34 | relation.find_all do |role| 35 | (role.name == args[:name].to_s && role.resource_type == nil && role.resource_id == nil) || 36 | (role.name == args[:name].to_s && role.resource_type == resource_type && role.resource_id == nil) || 37 | (role.name == args[:name].to_s && role.resource_type == resource_type && role.resource_id == resource_id) 38 | end 39 | end 40 | 41 | def find_cached_strict(relation, args) 42 | resource_id = (args[:resource].nil? || args[:resource].is_a?(Class)) ? nil : args[:resource].id 43 | resource_type = args[:resource].is_a?(Class) ? args[:resource].to_s : args[:resource].class.name 44 | 45 | relation.find_all do |role| 46 | role.resource_id == resource_id && role.resource_type == resource_type && role.name == args[:name].to_s 47 | end 48 | end 49 | 50 | def find_or_create_by(role_name, resource_type = nil, resource_id = nil) 51 | role_class.where(:name => role_name, :resource_type => resource_type, :resource_id => resource_id).first_or_create 52 | end 53 | 54 | def add(relation, role) 55 | relation.roles << role unless relation.roles.include?(role) 56 | end 57 | 58 | def remove(relation, role_name, resource = nil) 59 | cond = { :name => role_name } 60 | cond[:resource_type] = (resource.is_a?(Class) ? resource.to_s : resource.class.name) if resource 61 | cond[:resource_id] = resource.id if resource && !resource.is_a?(Class) 62 | roles = relation.roles.where(cond) 63 | if roles 64 | relation.roles.delete(roles) 65 | roles.each do |role| 66 | role.destroy if role.send(ActiveSupport::Inflector.demodulize(user_class).tableize.to_sym).limit(1).empty? 67 | end if Rolify.remove_role_if_empty 68 | end 69 | roles 70 | end 71 | 72 | def exists?(relation, column) 73 | relation.where("#{column} IS NOT NULL") 74 | end 75 | 76 | def scope(relation, conditions, strict) 77 | query = relation.joins(:roles) 78 | query = strict ? where_strict(query, conditions) : where(query, conditions) 79 | query 80 | end 81 | 82 | def all_except(user, excluded_obj) 83 | user.where.not(user.primary_key => excluded_obj) 84 | end 85 | 86 | private 87 | 88 | def build_conditions(relation, args) 89 | conditions = [] 90 | values = [] 91 | args.each do |arg| 92 | if arg.is_a? Hash 93 | a, v = build_query(arg[:name], arg[:resource]) 94 | elsif arg.is_a?(String) || arg.is_a?(Symbol) 95 | a, v = build_query(arg.to_s) 96 | else 97 | raise ArgumentError, "Invalid argument type: only hash or string or a symbol allowed" 98 | end 99 | conditions << a 100 | values += v 101 | end 102 | conditions = conditions.join(' OR ') 103 | [ conditions, values ] 104 | end 105 | 106 | def build_query(role, resource = nil) 107 | return [ "#{role_table}.name = ?", [ role ] ] if resource == :any 108 | query = "((#{role_table}.name = ?) AND (#{role_table}.resource_type IS NULL) AND (#{role_table}.resource_id IS NULL))" 109 | values = [ role ] 110 | if resource 111 | query.insert(0, "(") 112 | query += " OR ((#{role_table}.name = ?) AND (#{role_table}.resource_type = ?) AND (#{role_table}.resource_id IS NULL))" 113 | values << role << (resource.is_a?(Class) ? resource.to_s : resource.class.name) 114 | if !resource.is_a? Class 115 | query += " OR ((#{role_table}.name = ?) AND (#{role_table}.resource_type = ?) AND (#{role_table}.resource_id = ?))" 116 | values << role << resource.class.name << resource.id 117 | end 118 | query += ")" 119 | end 120 | [ query, values ] 121 | end 122 | end 123 | end 124 | end 125 | -------------------------------------------------------------------------------- /lib/rolify/adapters/active_record/scopes.rb: -------------------------------------------------------------------------------- 1 | module Rolify 2 | module Adapter 3 | module Scopes 4 | def global 5 | where(:resource_type => nil, :resource_id => nil) 6 | end 7 | 8 | def class_scoped(resource_type = nil) 9 | where_conditions = "resource_type IS NOT NULL AND resource_id IS NULL" 10 | where_conditions = [ "resource_type = ? AND resource_id IS NULL", resource_type.name ] if resource_type 11 | where(where_conditions) 12 | end 13 | 14 | def instance_scoped(resource_type = nil) 15 | where_conditions = "resource_type IS NOT NULL AND resource_id IS NOT NULL" 16 | if resource_type 17 | if resource_type.is_a? Class 18 | where_conditions = [ "resource_type = ? AND resource_id IS NOT NULL", resource_type.name ] 19 | else 20 | where_conditions = [ "resource_type = ? AND resource_id = ?", resource_type.class.name, resource_type.id ] 21 | end 22 | end 23 | where(where_conditions) 24 | end 25 | end 26 | end 27 | end -------------------------------------------------------------------------------- /lib/rolify/adapters/base.rb: -------------------------------------------------------------------------------- 1 | module Rolify 2 | module Adapter 3 | class Base 4 | def initialize(role_cname, user_cname) 5 | @role_cname = role_cname 6 | @user_cname = user_cname 7 | end 8 | 9 | def role_class 10 | @role_cname.constantize 11 | end 12 | 13 | def user_class 14 | @user_cname.constantize 15 | end 16 | 17 | def role_table 18 | role_class.table_name 19 | end 20 | 21 | def self.create(adapter, role_cname, user_cname) 22 | load "rolify/adapters/#{Rolify.orm}/#{adapter}.rb" 23 | load "rolify/adapters/#{Rolify.orm}/scopes.rb" 24 | Rolify::Adapter.const_get(adapter.camelize.to_sym).new(role_cname, user_cname) 25 | end 26 | 27 | def relation_types_for(relation) 28 | relation.descendants.map(&:to_s).push(relation.to_s) 29 | end 30 | end 31 | 32 | class RoleAdapterBase < Adapter::Base 33 | def where(relation, args) 34 | raise NotImplementedError.new("You must implement where") 35 | end 36 | 37 | def find_or_create_by(role_name, resource_type = nil, resource_id = nil) 38 | raise NotImplementedError.new("You must implement find_or_create_by") 39 | end 40 | 41 | def add(relation, role_name, resource = nil) 42 | raise NotImplementedError.new("You must implement add") 43 | end 44 | 45 | def remove(relation, role_name, resource = nil) 46 | raise NotImplementedError.new("You must implement delete") 47 | end 48 | 49 | def exists?(relation, column) 50 | raise NotImplementedError.new("You must implement exists?") 51 | end 52 | end 53 | 54 | class ResourceAdapterBase < Adapter::Base 55 | def resources_find(roles_table, relation, role_name) 56 | raise NotImplementedError.new("You must implement resources_find") 57 | end 58 | 59 | def in(resources, roles) 60 | raise NotImplementedError.new("You must implement in") 61 | end 62 | 63 | end 64 | end 65 | end -------------------------------------------------------------------------------- /lib/rolify/adapters/mongoid/resource_adapter.rb: -------------------------------------------------------------------------------- 1 | require 'rolify/adapters/base' 2 | 3 | module Rolify 4 | module Adapter 5 | class ResourceAdapter < ResourceAdapterBase 6 | 7 | def find_roles(role_name, relation, user) 8 | roles = user && (user != :any) ? user.roles : self.role_class 9 | roles = roles.where(:resource_type.in => self.relation_types_for(relation)) 10 | roles = roles.where(:name => role_name.to_s) if role_name && (role_name != :any) 11 | roles 12 | end 13 | 14 | def resources_find(roles_table, relation, role_name) 15 | roles = roles_table.classify.constantize.where(:name.in => Array(role_name), :resource_type.in => self.relation_types_for(relation)) 16 | resources = [] 17 | roles.each do |role| 18 | if role.resource_id.nil? 19 | resources += relation.all 20 | else 21 | resources << role.resource 22 | end 23 | end 24 | resources.compact.uniq 25 | end 26 | 27 | def in(resources, user, role_names) 28 | roles = user.roles.where(:name.in => Array(role_names)) 29 | return [] if resources.empty? || roles.empty? 30 | resources.delete_if { |resource| (resource.applied_roles & roles).empty? } 31 | resources 32 | end 33 | 34 | def applied_roles(relation, children) 35 | if children 36 | relation.role_class.where(:resource_type.in => self.relation_types_for(relation), :resource_id => nil) 37 | else 38 | relation.role_class.where(:resource_type => relation.to_s, :resource_id => nil) 39 | end 40 | end 41 | 42 | def all_except(resource, excluded_obj) 43 | resource.not_in(_id: excluded_obj.to_a) 44 | end 45 | 46 | end 47 | end 48 | end -------------------------------------------------------------------------------- /lib/rolify/adapters/mongoid/role_adapter.rb: -------------------------------------------------------------------------------- 1 | require 'rolify/adapters/base' 2 | 3 | module Rolify 4 | module Adapter 5 | class RoleAdapter < RoleAdapterBase 6 | def where(relation, *args) 7 | conditions = build_conditions(relation, args) 8 | relation.any_of(*conditions) 9 | end 10 | 11 | def where_strict(relation, args) 12 | wrap_conditions = relation.name != role_class.name 13 | 14 | conditions = if args[:resource].is_a?(Class) 15 | {:resource_type => args[:resource].to_s, :resource_id => nil } 16 | elsif args[:resource].present? 17 | {:resource_type => args[:resource].class.name, :resource_id => args[:resource].id} 18 | else 19 | {} 20 | end 21 | 22 | conditions.merge!(:name => args[:name]) 23 | conditions = wrap_conditions ? { role_table => conditions } : conditions 24 | 25 | relation.where(conditions) 26 | end 27 | 28 | def find_cached(relation, args) 29 | resource_id = (args[:resource].nil? || args[:resource].is_a?(Class) || args[:resource] == :any) ? nil : args[:resource].id 30 | resource_type = args[:resource].is_a?(Class) ? args[:resource].to_s : args[:resource].class.name 31 | 32 | return relation.find_all { |role| role.name == args[:name].to_s } if args[:resource] == :any 33 | 34 | relation.find_all do |role| 35 | (role.name == args[:name].to_s && role.resource_type == nil && role.resource_id == nil) || 36 | (role.name == args[:name].to_s && role.resource_type == resource_type && role.resource_id == nil) || 37 | (role.name == args[:name].to_s && role.resource_type == resource_type && role.resource_id == resource_id) 38 | end 39 | end 40 | 41 | def find_cached_strict(relation, args) 42 | resource_id = (args[:resource].nil? || args[:resource].is_a?(Class)) ? nil : args[:resource].id 43 | resource_type = args[:resource].is_a?(Class) ? args[:resource].to_s : args[:resource].class.name 44 | 45 | relation.find_all do |role| 46 | role.resource_id == resource_id && role.resource_type == resource_type && role.name == args[:name].to_s 47 | end 48 | end 49 | 50 | def find_or_create_by(role_name, resource_type = nil, resource_id = nil) 51 | self.role_class.find_or_create_by(:name => role_name, 52 | :resource_type => resource_type, 53 | :resource_id => resource_id) 54 | end 55 | 56 | def add(relation, role) 57 | relation.roles << role 58 | end 59 | 60 | def remove(relation, role_name, resource = nil) 61 | #roles = { :name => role_name } 62 | #roles.merge!({:resource_type => (resource.is_a?(Class) ? resource.to_s : resource.class.name)}) if resource 63 | #roles.merge!({ :resource_id => resource.id }) if resource && !resource.is_a?(Class) 64 | #roles_to_remove = relation.roles.where(roles) 65 | #roles_to_remove.each do |role| 66 | # # Deletion in n-n relations is unreliable. Sometimes it works, sometimes not. 67 | # # So, this does not work all the time: `relation.roles.delete(role)` 68 | # # @see http://stackoverflow.com/questions/9132596/rails3-mongoid-many-to-many-relation-and-delete-operation 69 | # # We instead remove ids from the Role object and the relation object. 70 | # relation.role_ids.delete(role.id) 71 | # role.send((user_class.to_s.underscore + '_ids').to_sym).delete(relation.id) 72 | # 73 | # role.destroy if role.send(user_class.to_s.tableize.to_sym).empty? 74 | #end 75 | cond = { :name => role_name } 76 | cond[:resource_type] = (resource.is_a?(Class) ? resource.to_s : resource.class.name) if resource 77 | cond[:resource_id] = resource.id if resource && !resource.is_a?(Class) 78 | roles = relation.roles.where(cond) 79 | roles.each do |role| 80 | relation.roles.delete(role) 81 | role.send(ActiveSupport::Inflector.demodulize(user_class).tableize.to_sym).delete(relation) 82 | if Rolify.remove_role_if_empty && role.send(ActiveSupport::Inflector.demodulize(user_class).tableize.to_sym).empty? 83 | role.destroy 84 | end 85 | end if roles 86 | roles 87 | end 88 | 89 | def exists?(relation, column) 90 | relation.where(column.to_sym.ne => nil) 91 | end 92 | 93 | def scope(relation, conditions, strict) 94 | query = strict ? where_strict(role_class, conditions) : where(role_class, conditions) 95 | roles = query.map { |role| role.id } 96 | return [] if roles.size.zero? 97 | query = relation.any_in(:role_ids => roles) 98 | query 99 | end 100 | 101 | def all_except(user, excluded_obj) 102 | user.not_in(_id: excluded_obj.to_a) 103 | end 104 | 105 | private 106 | 107 | def build_conditions(relation, args) 108 | conditions = [] 109 | args.each do |arg| 110 | if arg.is_a? Hash 111 | query = build_query(arg[:name], arg[:resource]) 112 | elsif arg.is_a?(String) || arg.is_a?(Symbol) 113 | query = build_query(arg) 114 | else 115 | raise ArgumentError, "Invalid argument type: only hash or string or symbol allowed" 116 | end 117 | conditions += query 118 | end 119 | conditions 120 | end 121 | 122 | def build_query(role, resource = nil) 123 | return [{ :name => role }] if resource == :any 124 | query = [{ :name => role, :resource_type => nil, :resource_id => nil }] 125 | if resource 126 | query << { :name => role, :resource_type => (resource.is_a?(Class) ? resource.to_s : resource.class.name), :resource_id => nil } 127 | if !resource.is_a? Class 128 | query << { :name => role, :resource_type => resource.class.name, :resource_id => resource.id } 129 | end 130 | end 131 | query 132 | end 133 | end 134 | end 135 | end 136 | -------------------------------------------------------------------------------- /lib/rolify/adapters/mongoid/scopes.rb: -------------------------------------------------------------------------------- 1 | module Rolify 2 | module Adapter 3 | module Scopes 4 | def global 5 | where(:resource_type => nil, :resource_id => nil) 6 | end 7 | 8 | def class_scoped(resource_type = nil) 9 | where_conditions = { :resource_type.ne => nil, :resource_id => nil } 10 | where_conditions = { :resource_type => resource_type.name, :resource_id => nil } if resource_type 11 | where(where_conditions) 12 | end 13 | 14 | def instance_scoped(resource_type = nil) 15 | where_conditions = { :resource_type.ne => nil, :resource_id.ne => nil } 16 | if resource_type 17 | if resource_type.is_a? Class 18 | where_conditions = { :resource_type => resource_type.name, :resource_id.ne => nil } 19 | else 20 | where_conditions = { :resource_type => resource_type.class.name, :resource_id => resource_type.id } 21 | end 22 | end 23 | where(where_conditions) 24 | end 25 | end 26 | end 27 | end -------------------------------------------------------------------------------- /lib/rolify/configure.rb: -------------------------------------------------------------------------------- 1 | module Rolify 2 | module Configure 3 | @@dynamic_shortcuts = false 4 | @@orm = "active_record" 5 | @@remove_role_if_empty = true 6 | 7 | def configure(*role_cnames) 8 | return if !sanity_check(role_cnames) 9 | yield self if block_given? 10 | end 11 | 12 | def dynamic_shortcuts 13 | @@dynamic_shortcuts 14 | end 15 | 16 | def dynamic_shortcuts=(is_dynamic) 17 | @@dynamic_shortcuts = is_dynamic 18 | end 19 | 20 | def orm 21 | @@orm 22 | end 23 | 24 | def orm=(orm) 25 | @@orm = orm 26 | end 27 | 28 | def use_mongoid 29 | self.orm = "mongoid" 30 | end 31 | 32 | def use_dynamic_shortcuts 33 | return if !sanity_check([]) 34 | self.dynamic_shortcuts = true 35 | end 36 | 37 | def use_defaults 38 | configure do |config| 39 | config.dynamic_shortcuts = false 40 | config.orm = "active_record" 41 | end 42 | end 43 | 44 | def remove_role_if_empty=(is_remove) 45 | @@remove_role_if_empty = is_remove 46 | end 47 | 48 | def remove_role_if_empty 49 | @@remove_role_if_empty 50 | end 51 | 52 | private 53 | 54 | def sanity_check(role_cnames) 55 | return true if ARGV.reduce(nil) { |acc,arg| arg =~ /assets:/ if acc.nil? } == 0 56 | 57 | role_cnames.each do |role_cname| 58 | role_class = role_cname.constantize 59 | if role_class.superclass.to_s == "ActiveRecord::Base" && role_table_missing?(role_class) 60 | warn "[WARN] table '#{role_cname}' doesn't exist. Did you run the migration? Ignoring rolify config." 61 | return false 62 | end 63 | end 64 | true 65 | end 66 | 67 | def role_table_missing?(role_class) 68 | !role_class.table_exists? 69 | rescue ActiveRecord::NoDatabaseError 70 | true 71 | end 72 | 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/rolify/dynamic.rb: -------------------------------------------------------------------------------- 1 | require "rolify/configure" 2 | 3 | module Rolify 4 | module Dynamic 5 | def define_dynamic_method(role_name, resource) 6 | class_eval do 7 | define_method("is_#{role_name}?".to_sym) do 8 | has_role?("#{role_name}") 9 | end if !method_defined?("is_#{role_name}?".to_sym) && self.adapter.where_strict(self.role_class, name: role_name).exists? 10 | 11 | define_method("is_#{role_name}_of?".to_sym) do |arg| 12 | has_role?("#{role_name}", arg) 13 | end if !method_defined?("is_#{role_name}_of?".to_sym) && resource && self.adapter.where_strict(self.role_class, name: role_name, resource: resource).exists? 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/rolify/finders.rb: -------------------------------------------------------------------------------- 1 | module Rolify 2 | module Finders 3 | def with_role(role_name, resource = nil) 4 | strict = self.strict_rolify && resource && resource != :any 5 | self.adapter.scope( 6 | self, 7 | { :name => role_name, :resource => resource }, 8 | strict 9 | ) 10 | end 11 | 12 | def without_role(role_name, resource = nil) 13 | self.adapter.all_except(self, self.with_role(role_name, resource)) 14 | end 15 | 16 | def with_all_roles(*args) 17 | users = [] 18 | parse_args(args, users) do |users_to_add| 19 | users = users_to_add if users.empty? 20 | users &= users_to_add 21 | return [] if users.empty? 22 | end 23 | users 24 | end 25 | 26 | def with_any_role(*args) 27 | users = [] 28 | parse_args(args, users) do |users_to_add| 29 | users += users_to_add 30 | end 31 | users.uniq 32 | end 33 | end 34 | 35 | private 36 | 37 | def parse_args(args, users, &block) 38 | args.each do |arg| 39 | if arg.is_a? Hash 40 | users_to_add = self.with_role(arg[:name], arg[:resource]) 41 | elsif arg.is_a?(String) || arg.is_a?(Symbol) 42 | users_to_add = self.with_role(arg) 43 | else 44 | raise ArgumentError, "Invalid argument type: only hash or string or symbol allowed" 45 | end 46 | block.call(users_to_add) 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/rolify/matchers.rb: -------------------------------------------------------------------------------- 1 | require 'rspec/expectations' 2 | 3 | RSpec::Matchers.define :have_role do |*args| 4 | match do |resource| 5 | resource.has_role?(*args) 6 | end 7 | 8 | failure_message do |resource| 9 | "expected to have role #{args.map(&:inspect).join(" ")}" 10 | end 11 | 12 | failure_message_when_negated do |resource| 13 | "expected not to have role #{args.map(&:inspect).join(" ")}" 14 | end 15 | end 16 | 17 | RSpec::Matchers.define :be_the_same_role do |*expected| 18 | match do |actual| 19 | if expected.size > 1 20 | if expected[1].is_a? Class 21 | actual[:name] == expected[0] && actual[:resource_type] == expected[1].to_s 22 | else 23 | actual[:name] == expected[0] && 24 | actual[:resource_type] == expected[1].class.name && 25 | actual[:resource_id] == expected[1].id 26 | end 27 | else 28 | actual[:name] == expected[0] 29 | end 30 | end 31 | end -------------------------------------------------------------------------------- /lib/rolify/railtie.rb: -------------------------------------------------------------------------------- 1 | require 'rolify' 2 | require 'rails' 3 | 4 | module Rolify 5 | class Railtie < Rails::Railtie 6 | initializer 'rolify.initialize' do 7 | ActiveSupport.on_load(:active_record) do 8 | ActiveRecord::Base.send :extend, Rolify 9 | end 10 | 11 | config.before_initialize do 12 | ::Mongoid::Document.module_eval do 13 | def self.included(base) 14 | base.extend Rolify 15 | end 16 | end 17 | end if defined?(Mongoid) 18 | end 19 | end 20 | end -------------------------------------------------------------------------------- /lib/rolify/resource.rb: -------------------------------------------------------------------------------- 1 | module Rolify 2 | module Resource 3 | def self.included(base) 4 | base.extend ClassMethods 5 | end 6 | 7 | module ClassMethods 8 | def find_roles(role_name = nil, user = nil) 9 | self.resource_adapter.find_roles(role_name, self, user) 10 | end 11 | 12 | def with_role(role_name, user = nil) 13 | if role_name.is_a? Array 14 | role_name = role_name.map(&:to_s) 15 | else 16 | role_name = role_name.to_s 17 | end 18 | 19 | resources = self.resource_adapter.resources_find(self.role_table_name, self, role_name) #.map(&:id) 20 | user ? self.resource_adapter.in(resources, user, role_name) : resources 21 | end 22 | alias :with_roles :with_role 23 | alias :find_as :with_role 24 | alias :find_multiple_as :with_role 25 | 26 | 27 | def without_role(role_name, user = nil) 28 | self.resource_adapter.all_except(self, self.find_as(role_name, user)) 29 | end 30 | alias :without_roles :without_role 31 | alias :except_as :without_role 32 | alias :except_multiple_as :without_role 33 | 34 | 35 | 36 | def applied_roles(children = true) 37 | self.resource_adapter.applied_roles(self, children) 38 | end 39 | 40 | 41 | 42 | end 43 | 44 | def applied_roles 45 | #self.roles + self.class.role_class.where(:resource_type => self.class.to_s, :resource_id => nil) 46 | self.roles + self.class.applied_roles(true) 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/rolify/role.rb: -------------------------------------------------------------------------------- 1 | require "rolify/finders" 2 | require "rolify/utils" 3 | 4 | module Rolify 5 | module Role 6 | extend Utils 7 | 8 | def self.included(base) 9 | base.extend Finders 10 | end 11 | 12 | def add_role(role_name, resource = nil) 13 | role = self.class.adapter.find_or_create_by(role_name.to_s, 14 | (resource.is_a?(Class) ? resource.to_s : resource.class.name if resource), 15 | (resource.id if resource && !resource.is_a?(Class))) 16 | 17 | if !roles.include?(role) 18 | self.class.define_dynamic_method(role_name, resource) if Rolify.dynamic_shortcuts 19 | self.class.adapter.add(self, role) 20 | end 21 | role 22 | end 23 | alias_method :grant, :add_role 24 | 25 | def has_role?(role_name, resource = nil) 26 | return has_strict_role?(role_name, resource) if self.class.strict_rolify and resource and resource != :any 27 | 28 | if new_record? 29 | role_array = self.roles.detect { |r| 30 | r.name.to_s == role_name.to_s && 31 | (r.resource == resource || 32 | resource.nil? || 33 | (resource == :any && r.resource.present?)) 34 | } 35 | else 36 | role_array = self.class.adapter.where(self.roles, name: role_name, resource: resource) 37 | end 38 | 39 | return false if role_array.nil? 40 | role_array != [] 41 | end 42 | 43 | def has_strict_role?(role_name, resource) 44 | self.class.adapter.where_strict(self.roles, name: role_name, resource: resource).any? 45 | end 46 | 47 | def has_cached_role?(role_name, resource = nil) 48 | return has_strict_cached_role?(role_name, resource) if self.class.strict_rolify and resource and resource != :any 49 | self.class.adapter.find_cached(self.roles, name: role_name, resource: resource).any? 50 | end 51 | 52 | def has_strict_cached_role?(role_name, resource = nil) 53 | self.class.adapter.find_cached_strict(self.roles, name: role_name, resource: resource).any? 54 | end 55 | 56 | def has_all_roles?(*args) 57 | args.each do |arg| 58 | if arg.is_a? Hash 59 | return false if !self.has_role?(arg[:name], arg[:resource]) 60 | elsif arg.is_a?(String) || arg.is_a?(Symbol) 61 | return false if !self.has_role?(arg) 62 | else 63 | raise ArgumentError, "Invalid argument type: only hash or string or symbol allowed" 64 | end 65 | end 66 | true 67 | end 68 | 69 | def has_any_role?(*args) 70 | if new_record? 71 | args.any? { |r| self.has_role?(r) } 72 | else 73 | self.class.adapter.where(self.roles, *args).size > 0 74 | end 75 | end 76 | 77 | def only_has_role?(role_name, resource = nil) 78 | return self.has_role?(role_name,resource) && self.roles.count == 1 79 | end 80 | 81 | def remove_role(role_name, resource = nil) 82 | self.class.adapter.remove(self, role_name.to_s, resource) 83 | end 84 | 85 | alias_method :revoke, :remove_role 86 | deprecate :has_no_role, :remove_role 87 | 88 | def roles_name 89 | self.roles.pluck(:name) 90 | end 91 | 92 | def method_missing(method, *args, &block) 93 | if method.to_s.match(/^is_(\w+)_of[?]$/) || method.to_s.match(/^is_(\w+)[?]$/) 94 | resource = args.first 95 | self.class.define_dynamic_method $1, resource 96 | return has_role?("#{$1}", resource) 97 | end if Rolify.dynamic_shortcuts 98 | super 99 | end 100 | 101 | def respond_to?(method, include_private = false) 102 | if Rolify.dynamic_shortcuts && (method.to_s.match(/^is_(\w+)_of[?]$/) || method.to_s.match(/^is_(\w+)[?]$/)) 103 | query = self.class.role_class.where(:name => $1) 104 | query = self.class.adapter.exists?(query, :resource_type) if method.to_s.match(/^is_(\w+)_of[?]$/) 105 | return true if query.count > 0 106 | false 107 | else 108 | super 109 | end 110 | end 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /lib/rolify/utils.rb: -------------------------------------------------------------------------------- 1 | module Rolify 2 | module Utils 3 | def deprecate(old_method, new_method) 4 | define_method(old_method) do |*args| 5 | warn "[DEPRECATION] #{caller.first}: `#{old_method}` is deprecated. Please use `#{new_method}` instead." 6 | send(new_method, *args) 7 | end 8 | end 9 | end 10 | end -------------------------------------------------------------------------------- /lib/rolify/version.rb: -------------------------------------------------------------------------------- 1 | module Rolify 2 | VERSION = "6.0.1" 3 | end 4 | -------------------------------------------------------------------------------- /rolify.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | $:.push File.expand_path('../lib', __FILE__) 3 | require 'rolify/version' 4 | 5 | Gem::Specification.new do |s| 6 | s.name = 'rolify' 7 | s.summary = %q{Roles library with resource scoping} 8 | s.description = %q{Very simple Roles library without any authorization enforcement supporting scope on resource objects (instance or class). Supports ActiveRecord and Mongoid ORMs.} 9 | s.version = Rolify::VERSION 10 | s.platform = Gem::Platform::RUBY 11 | s.homepage = 'https://github.com/RolifyCommunity/rolify' 12 | 13 | s.license = 'MIT' 14 | 15 | s.authors = [ 16 | 'Florent Monbillard', 17 | 'Wellington Cordeiro' 18 | ] 19 | s.email = [ 20 | 'f.monbillard@gmail.com', 21 | 'wellington@wellingtoncordeiro.com' 22 | ] 23 | 24 | s.files = `git ls-files`.split("\n") 25 | s.test_files = `git ls-files -- spec/*`.split("\n") 26 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 27 | s.require_paths = ['lib'] 28 | 29 | s.required_ruby_version = '>= 2.5' 30 | 31 | s.add_development_dependency 'ammeter', '~> 1.1' # Spec generator 32 | s.add_development_dependency 'appraisal', '~> 2.0' 33 | s.add_development_dependency 'bundler', '~> 2.0' # packaging feature 34 | s.add_development_dependency 'rake', '~> 12.3' # Tasks manager 35 | s.add_development_dependency 'rspec-rails', '~> 3.8' 36 | end 37 | -------------------------------------------------------------------------------- /spec/README.rdoc: -------------------------------------------------------------------------------- 1 | = Rolify Specs 2 | 3 | == Running the specs 4 | 5 | To run the specs first run the +bundle+ command to install the necessary gems and the +rake+ command to run the specs. 6 | 7 | bundle 8 | rake 9 | 10 | == Model Adapters 11 | 12 | Rolify currently supports 2 different ORMs: ActiveRecord and Mongoid. By default it will use Active Record but you can change this by setting the +ADAPTER+ environment variable before running the specs. You can run the +bundle+ command with this as well to ensure you have all the required gems. 13 | 14 | ADAPTER=mongoid bundle 15 | ADAPTER=mongoid rake 16 | 17 | The different model adapters you can specify are: 18 | 19 | * active_record (default) 20 | * mongoid 21 | 22 | You can also run the +spec_all+ rake task to run specs for each adapter. 23 | 24 | rake spec_all -------------------------------------------------------------------------------- /spec/common_helper.rb: -------------------------------------------------------------------------------- 1 | require 'test-unit' 2 | 3 | begin 4 | require 'pry' 5 | rescue LoadError 6 | end 7 | 8 | # `Test::Unit::AutoRunner.need_auto_run=` was introduced to the test-unit 9 | # gem in version 2.4.9. Previous to this version `Test::Unit.run=` was 10 | # used. The implementation of test-unit included with Ruby has neither 11 | # method. 12 | if defined? Test::Unit::AutoRunner 13 | Test::Unit::AutoRunner.need_auto_run = false 14 | elsif defined?(Test::Unit) 15 | Test::Unit.run = false 16 | end 17 | -------------------------------------------------------------------------------- /spec/generators/rolify/rolify_activerecord_generator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'generators_helper' 2 | 3 | # Generators are not automatically loaded by Rails 4 | require 'generators/rolify/rolify_generator' 5 | 6 | describe Rolify::Generators::RolifyGenerator, :if => ENV['ADAPTER'] == 'active_record' do 7 | # Tell the generator where to put its output (what it thinks of as Rails.root) 8 | destination File.expand_path("../../../../tmp", __FILE__) 9 | teardown :cleanup_destination_root 10 | 11 | let(:adapter) { 'SQLite3Adapter' } 12 | before { 13 | prepare_destination 14 | } 15 | 16 | def cleanup_destination_root 17 | FileUtils.rm_rf destination_root 18 | end 19 | 20 | describe 'specifying only Role class name' do 21 | before(:all) { arguments %w(Role) } 22 | 23 | before { 24 | allow(ActiveRecord::Base).to receive_message_chain( 25 | 'connection.class.to_s.demodulize') { adapter } 26 | capture(:stdout) { 27 | generator.create_file "app/models/user.rb" do 28 | <<-RUBY 29 | class User < ActiveRecord::Base 30 | end 31 | RUBY 32 | end 33 | } 34 | require File.join(destination_root, "app/models/user.rb") 35 | if Rails::VERSION::MAJOR >= 7 36 | run_generator %w(--skip-collision-check) 37 | else 38 | run_generator 39 | end 40 | } 41 | 42 | describe 'config/initializers/rolify.rb' do 43 | subject { file('config/initializers/rolify.rb') } 44 | it { should exist } 45 | it { should contain "Rolify.configure do |config|"} 46 | it { should contain "# config.use_dynamic_shortcuts" } 47 | it { should contain "# config.use_mongoid" } 48 | end 49 | 50 | describe 'app/models/role.rb' do 51 | subject { file('app/models/role.rb') } 52 | it { should exist } 53 | it do 54 | if Rails::VERSION::MAJOR < 5 55 | should contain "class Role < ActiveRecord::Base" 56 | else 57 | should contain "class Role < ApplicationRecord" 58 | end 59 | end 60 | it { should contain "has_and_belongs_to_many :users, :join_table => :users_roles" } 61 | it do 62 | if Rails::VERSION::MAJOR < 5 63 | should contain "belongs_to :resource,\n" 64 | " :polymorphic => true" 65 | else 66 | should contain "belongs_to :resource,\n" 67 | " :polymorphic => true,\n" 68 | " :optional => true" 69 | end 70 | end 71 | it { should contain "belongs_to :resource,\n" 72 | " :polymorphic => true,\n" 73 | " :optional => true" 74 | } 75 | it { should contain "validates :resource_type,\n" 76 | " :inclusion => { :in => Rolify.resource_types },\n" 77 | " :allow_nil => true" } 78 | it { should contain "scopify" } 79 | end 80 | 81 | describe 'app/models/user.rb' do 82 | subject { file('app/models/user.rb') } 83 | it { should contain /class User < ActiveRecord::Base\n rolify\n/ } 84 | end 85 | 86 | describe 'migration file' do 87 | subject { migration_file('db/migrate/rolify_create_roles.rb') } 88 | 89 | it { should be_a_migration } 90 | it { should contain "create_table(:roles) do" } 91 | it { should contain "create_table(:users_roles, :id => false) do" } 92 | 93 | context 'mysql2' do 94 | let(:adapter) { 'Mysql2Adapter' } 95 | 96 | it { expect(subject).to contain('add_index(:roles, :name)') } 97 | end 98 | 99 | context 'sqlite3' do 100 | let(:adapter) { 'SQLite3Adapter' } 101 | 102 | it { expect(subject).to contain('add_index(:roles, :name)') } 103 | end 104 | 105 | context 'pg' do 106 | let(:adapter) { 'PostgreSQLAdapter' } 107 | 108 | it { expect(subject).not_to contain('add_index(:roles, :name)') } 109 | end 110 | end 111 | end 112 | 113 | describe 'specifying User and Role class names' do 114 | before(:all) { arguments %w(AdminRole AdminUser) } 115 | 116 | before { 117 | allow(ActiveRecord::Base).to receive_message_chain( 118 | 'connection.class.to_s.demodulize') { adapter } 119 | capture(:stdout) { 120 | generator.create_file "app/models/admin_user.rb" do 121 | "class AdminUser < ActiveRecord::Base\nend" 122 | end 123 | } 124 | require File.join(destination_root, "app/models/admin_user.rb") 125 | run_generator 126 | } 127 | 128 | describe 'config/initializers/rolify.rb' do 129 | subject { file('config/initializers/rolify.rb') } 130 | 131 | it { should exist } 132 | it { should contain "Rolify.configure(\"AdminRole\") do |config|"} 133 | it { should contain "# config.use_dynamic_shortcuts" } 134 | it { should contain "# config.use_mongoid" } 135 | end 136 | 137 | describe 'app/models/admin_role.rb' do 138 | subject { file('app/models/admin_role.rb') } 139 | 140 | it { should exist } 141 | it do 142 | if Rails::VERSION::MAJOR < 5 143 | should contain "class AdminRole < ActiveRecord::Base" 144 | else 145 | should contain "class AdminRole < ApplicationRecord" 146 | end 147 | end 148 | it { should contain "has_and_belongs_to_many :admin_users, :join_table => :admin_users_admin_roles" } 149 | it { should contain "belongs_to :resource,\n" 150 | " :polymorphic => true,\n" 151 | " :optional => true" 152 | } 153 | end 154 | 155 | describe 'app/models/admin_user.rb' do 156 | subject { file('app/models/admin_user.rb') } 157 | 158 | it { should contain /class AdminUser < ActiveRecord::Base\n rolify :role_cname => 'AdminRole'\n/ } 159 | end 160 | 161 | describe 'migration file' do 162 | subject { migration_file('db/migrate/rolify_create_admin_roles.rb') } 163 | 164 | it { should be_a_migration } 165 | it { should contain "create_table(:admin_roles)" } 166 | it { should contain "create_table(:admin_users_admin_roles, :id => false) do" } 167 | 168 | context 'mysql2' do 169 | let(:adapter) { 'Mysql2Adapter' } 170 | 171 | it { expect(subject).to contain('add_index(:admin_roles, :name)') } 172 | end 173 | 174 | context 'sqlite3' do 175 | let(:adapter) { 'SQLite3Adapter' } 176 | 177 | it { expect(subject).to contain('add_index(:admin_roles, :name)') } 178 | end 179 | 180 | context 'pg' do 181 | let(:adapter) { 'PostgreSQLAdapter' } 182 | 183 | it { expect(subject).not_to contain('add_index(:admin_roles, :name)') } 184 | end 185 | end 186 | end 187 | 188 | describe 'specifying namespaced User and Role class names' do 189 | before(:all) { arguments %w(Admin::Role Admin::User) } 190 | 191 | before { 192 | allow(ActiveRecord::Base).to receive_message_chain( 193 | 'connection.class.to_s.demodulize') { adapter } 194 | capture(:stdout) { 195 | generator.create_file "app/models/admin/user.rb" do 196 | <<-RUBY 197 | module Admin 198 | class User < ActiveRecord::Base 199 | self.table_name_prefix = 'admin_' 200 | end 201 | end 202 | RUBY 203 | end 204 | } 205 | require File.join(destination_root, "app/models/admin/user.rb") 206 | run_generator 207 | } 208 | 209 | describe 'config/initializers/rolify.rb' do 210 | subject { file('config/initializers/rolify.rb') } 211 | 212 | it { should exist } 213 | it { should contain "Rolify.configure(\"Admin::Role\") do |config|"} 214 | it { should contain "# config.use_dynamic_shortcuts" } 215 | it { should contain "# config.use_mongoid" } 216 | end 217 | 218 | describe 'app/models/admin/role.rb' do 219 | subject { file('app/models/admin/role.rb') } 220 | 221 | it { should exist } 222 | it do 223 | if Rails::VERSION::MAJOR < 5 224 | should contain "class Admin::Role < ActiveRecord::Base" 225 | else 226 | should contain "class Admin::Role < ApplicationRecord" 227 | end 228 | end 229 | it { should contain "has_and_belongs_to_many :admin_users, :join_table => :admin_users_admin_roles" } 230 | it { should contain "belongs_to :resource,\n" 231 | " :polymorphic => true,\n" 232 | " :optional => true" 233 | } 234 | end 235 | 236 | describe 'app/models/admin/user.rb' do 237 | subject { file('app/models/admin/user.rb') } 238 | 239 | it { should contain /class User < ActiveRecord::Base\n rolify :role_cname => 'Admin::Role'\n/ } 240 | end 241 | 242 | describe 'migration file' do 243 | subject { migration_file('db/migrate/rolify_create_admin_roles.rb') } 244 | 245 | it { should be_a_migration } 246 | it { should contain "create_table(:admin_roles)" } 247 | it { should contain "create_table(:admin_users_admin_roles, :id => false) do" } 248 | it do 249 | if Rails::VERSION::MAJOR < 5 250 | should contain "< ActiveRecord::Migration" 251 | else 252 | should contain "< ActiveRecord::Migration[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]" 253 | end 254 | end 255 | 256 | context 'mysql2' do 257 | let(:adapter) { 'Mysql2Adapter' } 258 | 259 | it { expect(subject).to contain('add_index(:admin_roles, :name)') } 260 | end 261 | 262 | context 'sqlite3' do 263 | let(:adapter) { 'SQLite3Adapter' } 264 | 265 | it { expect(subject).to contain('add_index(:admin_roles, :name)') } 266 | end 267 | 268 | context 'pg' do 269 | let(:adapter) { 'PostgreSQLAdapter' } 270 | 271 | it { expect(subject).not_to contain('add_index(:admin_roles, :name)') } 272 | end 273 | end 274 | end 275 | end 276 | -------------------------------------------------------------------------------- /spec/generators/rolify/rolify_mongoid_generator_spec.rb: -------------------------------------------------------------------------------- 1 | require 'generators_helper' 2 | 3 | # Generators are not automatically loaded by Rails 4 | require 'generators/rolify/rolify_generator' 5 | 6 | describe Rolify::Generators::RolifyGenerator, :if => ENV['ADAPTER'] == 'mongoid' do 7 | # Tell the generator where to put its output (what it thinks of as Rails.root) 8 | destination File.expand_path("../../../../tmp", __FILE__) 9 | teardown :cleanup_destination_root 10 | 11 | before { 12 | prepare_destination 13 | } 14 | 15 | def cleanup_destination_root 16 | FileUtils.rm_rf destination_root 17 | end 18 | 19 | describe 'specifying ORM adapter' do 20 | before(:all) { arguments [ "Role", "User", "--orm=mongoid" ] } 21 | 22 | before { 23 | capture(:stdout) { 24 | generator.create_file "app/models/user.rb" do 25 | <<-RUBY 26 | class User 27 | include Mongoid::Document 28 | 29 | field :login, :type => String 30 | end 31 | RUBY 32 | end 33 | } 34 | require File.join(destination_root, "app/models/user.rb") 35 | run_generator 36 | } 37 | 38 | describe 'config/initializers/rolify.rb' do 39 | subject { file('config/initializers/rolify.rb') } 40 | it { should exist } 41 | it { should contain "Rolify.configure do |config|"} 42 | it { should_not contain "# config.use_mongoid" } 43 | it { should contain "# config.use_dynamic_shortcuts" } 44 | end 45 | 46 | describe 'app/models/role.rb' do 47 | subject { file('app/models/role.rb') } 48 | it { should exist } 49 | it { should contain "class Role\n" } 50 | it { should contain "has_and_belongs_to_many :users\n" } 51 | it { should contain "belongs_to :resource, :polymorphic => true" } 52 | it { should contain "field :name, :type => String" } 53 | it { should contain " index({\n" 54 | " { :name => 1 },\n" 55 | " { :resource_type => 1 },\n" 56 | " { :resource_id => 1 }\n" 57 | " },\n" 58 | " { unique => true })"} 59 | it { should contain "validates :resource_type,\n" 60 | " :inclusion => { :in => Rolify.resource_types },\n" 61 | " :allow_nil => true" } 62 | end 63 | 64 | describe 'app/models/user.rb' do 65 | subject { file('app/models/user.rb') } 66 | it { should contain /class User\n include Mongoid::Document\n rolify\n/ } 67 | end 68 | end 69 | 70 | describe 'specifying namespaced User and Role class names and ORM adapter' do 71 | before(:all) { arguments %w(Admin::Role Admin::User --orm=mongoid) } 72 | 73 | before { 74 | capture(:stdout) { 75 | generator.create_file "app/models/admin/user.rb" do 76 | <<-RUBY 77 | module Admin 78 | class User 79 | include Mongoid::Document 80 | end 81 | end 82 | RUBY 83 | end 84 | } 85 | require File.join(destination_root, "app/models/admin/user.rb") 86 | run_generator 87 | } 88 | 89 | describe 'config/initializers/rolify.rb' do 90 | subject { file('config/initializers/rolify.rb') } 91 | 92 | it { should exist } 93 | it { should contain "Rolify.configure(\"Admin::Role\") do |config|"} 94 | it { should contain "# config.use_dynamic_shortcuts" } 95 | it { should_not contain "# config.use_mongoid" } 96 | end 97 | 98 | describe 'app/models/admin/role.rb' do 99 | subject { file('app/models/admin/role.rb') } 100 | 101 | it { should exist } 102 | it { should contain "class Admin::Role" } 103 | it { should contain "has_and_belongs_to_many :admin_users" } 104 | it { should contain "belongs_to :resource, :polymorphic => true" } 105 | end 106 | 107 | describe 'app/models/admin/user.rb' do 108 | subject { file('app/models/admin/user.rb') } 109 | 110 | it { should contain /class User\n include Mongoid::Document\n rolify :role_cname => 'Admin::Role'\n/ } 111 | end 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /spec/generators_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require "bundler/setup" 3 | 4 | require 'pry' 5 | 6 | require 'rolify' 7 | require 'rolify/matchers' 8 | require 'rails/all' 9 | require_relative 'support/stream_helpers' 10 | include StreamHelpers 11 | 12 | require 'coveralls' 13 | Coveralls.wear_merged! 14 | 15 | require 'common_helper' 16 | 17 | ENV['ADAPTER'] ||= 'active_record' 18 | 19 | if ENV['ADAPTER'] == 'active_record' 20 | load File.dirname(__FILE__) + '/support/adapters/utils/active_record.rb' 21 | require 'active_record/railtie' 22 | establish_connection 23 | else 24 | load File.dirname(__FILE__) + '/support/adapters/utils/mongoid.rb' 25 | load_mongoid_config 26 | end 27 | 28 | module TestApp 29 | class Application < ::Rails::Application 30 | config.root = File.dirname(__FILE__) 31 | end 32 | end 33 | 34 | require 'ammeter/init' 35 | -------------------------------------------------------------------------------- /spec/rolify/config_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | if ENV['ADAPTER'] == 'active_record' 4 | class ARUser < ActiveRecord::Base 5 | extend Rolify 6 | end 7 | else 8 | class MUser 9 | include Mongoid::Document 10 | extend Rolify 11 | end 12 | end 13 | 14 | describe Rolify do 15 | before do 16 | Rolify.use_defaults 17 | end 18 | 19 | describe :dynamic_shortcuts do 20 | context "using defaults values" do 21 | subject { Rolify.dynamic_shortcuts } 22 | 23 | it { should be_falsey } 24 | end 25 | 26 | context "using custom values" do 27 | before do 28 | Rolify.dynamic_shortcuts = true 29 | end 30 | 31 | subject { Rolify.dynamic_shortcuts } 32 | 33 | it { should be_truthy } 34 | end 35 | end 36 | 37 | describe :orm do 38 | context "using defaults values", :if => ENV['ADAPTER'] == 'active_record' do 39 | subject { Rolify.orm } 40 | 41 | it { should eq("active_record") } 42 | 43 | context "on the User class" do 44 | before do 45 | subject.rolify 46 | end 47 | 48 | subject { ARUser } 49 | 50 | its("adapter.class") { should be(Rolify::Adapter::RoleAdapter) } 51 | end 52 | 53 | context "on the Forum class" do 54 | before do 55 | subject.resourcify 56 | end 57 | 58 | subject { Forum } 59 | 60 | its("resource_adapter.class") { should be(Rolify::Adapter::ResourceAdapter) } 61 | end 62 | end 63 | 64 | context "using custom values", :if => ENV['ADAPTER'] == 'mongoid' do 65 | context "using :orm setter method" do 66 | before do 67 | Rolify.orm = "mongoid" 68 | end 69 | 70 | subject { Rolify.orm } 71 | 72 | it { should eq("mongoid") } 73 | 74 | context "on the User class" do 75 | before do 76 | MUser.rolify 77 | end 78 | 79 | subject { MUser } 80 | 81 | its("adapter.class") { should be(Rolify::Adapter::RoleAdapter) } 82 | end 83 | 84 | context "on the Forum class" do 85 | before do 86 | Forum.resourcify 87 | end 88 | 89 | subject { Forum } 90 | 91 | its("resource_adapter.class") { should be(Rolify::Adapter::ResourceAdapter) } 92 | end 93 | end 94 | 95 | context "using :use_mongoid method" do 96 | before do 97 | Rolify.use_mongoid 98 | end 99 | 100 | subject { Rolify.orm } 101 | 102 | it { should eq("mongoid") } 103 | 104 | context "on the User class" do 105 | before do 106 | MUser.rolify 107 | end 108 | 109 | subject { MUser } 110 | 111 | its("adapter.class") { should be(Rolify::Adapter::RoleAdapter) } 112 | end 113 | 114 | context "on the Forum class" do 115 | before do 116 | Forum.resourcify 117 | end 118 | 119 | subject { Forum } 120 | 121 | its("resource_adapter.class") { should be(Rolify::Adapter::ResourceAdapter) } 122 | end 123 | end 124 | end 125 | 126 | describe :dynamic_shortcuts do 127 | context "using defaults values" do 128 | subject { Rolify.dynamic_shortcuts } 129 | 130 | it { should be_falsey } 131 | end 132 | 133 | context "using custom values" do 134 | context "using :dynamic_shortcuts setter method" do 135 | before do 136 | Rolify.dynamic_shortcuts = true 137 | end 138 | 139 | subject { Rolify.dynamic_shortcuts } 140 | 141 | it { should be_truthy } 142 | end 143 | 144 | context "using :use_dynamic_shortcuts method" do 145 | before do 146 | Rolify.use_dynamic_shortcuts 147 | end 148 | 149 | subject { Rolify.dynamic_shortcuts } 150 | 151 | it { should be_truthy } 152 | end 153 | end 154 | end 155 | end 156 | 157 | describe :configure do 158 | before do 159 | Rolify.configure do |config| 160 | config.dynamic_shortcuts = true 161 | config.orm = "mongoid" 162 | end 163 | end 164 | 165 | subject { Rolify } 166 | 167 | its(:dynamic_shortcuts) { should be_truthy } 168 | its(:orm) { should eq("mongoid") } 169 | 170 | context "on the User class", :if => ENV['ADAPTER'] == 'mongoid' do 171 | before do 172 | MUser.rolify 173 | end 174 | 175 | subject { MUser } 176 | 177 | it { should satisfy { |u| u.include? Rolify::Role }} 178 | it { should satisfy { |u| u.singleton_class.include? Rolify::Dynamic } } 179 | its("adapter.class") { should be(Rolify::Adapter::RoleAdapter) } 180 | end 181 | 182 | context "on the Forum class" do 183 | before do 184 | Forum.resourcify 185 | end 186 | 187 | subject { Forum } 188 | it { should satisfy { |u| u.include? Rolify::Resource }} 189 | its("resource_adapter.class") { should be(Rolify::Adapter::ResourceAdapter) } 190 | end 191 | end 192 | end -------------------------------------------------------------------------------- /spec/rolify/coverage/.last_run.json: -------------------------------------------------------------------------------- 1 | { 2 | "result": { 3 | "covered_percent": 100.0 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /spec/rolify/coverage/.resultset.json: -------------------------------------------------------------------------------- 1 | { 2 | "RSpec": { 3 | "coverage": { 4 | }, 5 | "timestamp": 1427836082 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /spec/rolify/coverage/.resultset.json.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RolifyCommunity/rolify/55261dcc26ae0bbca90a657e9449c65095924465/spec/rolify/coverage/.resultset.json.lock -------------------------------------------------------------------------------- /spec/rolify/custom_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "rolify/shared_examples/shared_examples_for_roles" 3 | require "rolify/shared_examples/shared_examples_for_dynamic" 4 | require "rolify/shared_examples/shared_examples_for_scopes" 5 | require "rolify/shared_examples/shared_examples_for_callbacks" 6 | 7 | describe "Using Rolify with custom User and Role class names" do 8 | def user_class 9 | Customer 10 | end 11 | 12 | def role_class 13 | Privilege 14 | end 15 | 16 | it_behaves_like Rolify::Role 17 | it_behaves_like "Role.scopes" 18 | it_behaves_like Rolify::Dynamic 19 | it_behaves_like "Rolify.callbacks" 20 | end 21 | -------------------------------------------------------------------------------- /spec/rolify/matchers_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'have_role', focus: true do 4 | let(:object) { Object.new } 5 | 6 | it 'delegates to has_role?' do 7 | object.should_receive(:has_role?).with(:read, 'Resource') { true } 8 | object.should have_role(:read, 'Resource') 9 | end 10 | 11 | it 'reports a nice failure message for should' do 12 | object.should_receive(:has_role?) { false } 13 | expect{ 14 | object.should have_role(:read, 'Resource') 15 | }.to raise_error('expected to have role :read "Resource"') 16 | end 17 | 18 | it 'reports a nice failure message for should_not' do 19 | object.should_receive(:has_role?) { true } 20 | expect{ 21 | object.should_not have_role(:read, 'Resource') 22 | }.to raise_error('expected not to have role :read "Resource"') 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/rolify/namespace_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "rolify/shared_examples/shared_examples_for_roles" 3 | require "rolify/shared_examples/shared_examples_for_dynamic" 4 | require "rolify/shared_examples/shared_examples_for_scopes" 5 | require "rolify/shared_examples/shared_examples_for_callbacks" 6 | 7 | describe "Rolify.namespace" do 8 | def user_class 9 | Admin::Moderator 10 | end 11 | 12 | def role_class 13 | Admin::Right 14 | end 15 | 16 | def join_table 17 | "moderators_rights" 18 | end 19 | 20 | it_behaves_like Rolify::Role 21 | it_behaves_like "Role.scopes" 22 | it_behaves_like Rolify::Dynamic 23 | it_behaves_like "Rolify.callbacks" 24 | end 25 | -------------------------------------------------------------------------------- /spec/rolify/resource_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Rolify::Resource do 4 | before(:all) do 5 | reset_defaults 6 | silence_warnings { User.rolify } 7 | Forum.resourcify 8 | Group.resourcify 9 | Team.resourcify 10 | Organization.resourcify 11 | Role.destroy_all 12 | end 13 | 14 | # Users 15 | let(:admin) { User.first } 16 | let(:tourist) { User.last } 17 | let(:captain) { User.where(:login => "god").first } 18 | 19 | # roles 20 | let!(:forum_role) { admin.add_role(:forum, Forum.first) } 21 | let!(:godfather_role) { admin.add_role(:godfather, Forum) } 22 | let!(:group_role) { admin.add_role(:group, Group.last) } 23 | let!(:grouper_role) { admin.add_role(:grouper, Group.first) } 24 | let!(:tourist_role) { tourist.add_role(:forum, Forum.last) } 25 | let!(:sneaky_role) { tourist.add_role(:group, Forum.first) } 26 | let!(:captain_role) { captain.add_role(:captain, Team.first) } 27 | let!(:player_role) { captain.add_role(:player, Team.last) } 28 | let!(:company_role) { admin.add_role(:owner, Company.first) } 29 | 30 | describe ".find_multiple_as" do 31 | subject { Group } 32 | 33 | it { should respond_to(:find_roles).with(1).arguments } 34 | it { should respond_to(:find_roles).with(2).arguments } 35 | 36 | context "with a role name as argument" do 37 | context "on the Forum class" do 38 | subject { Forum } 39 | 40 | it "should include Forum instances with forum role" do 41 | subject.find_as(:forum).should =~ [ Forum.first, Forum.last ] 42 | end 43 | 44 | it "should include Forum instances with godfather role" do 45 | subject.find_as(:godfather).should =~ Forum.all 46 | end 47 | 48 | it "should be able to modify the resource", :if => ENV['ADAPTER'] == 'active_record' do 49 | forum_resource = subject.find_as(:forum).first 50 | forum_resource.name = "modified name" 51 | expect { forum_resource.save }.not_to raise_error 52 | end 53 | end 54 | 55 | context "on the Group class" do 56 | subject { Group } 57 | 58 | it "should include Group instances with group role" do 59 | subject.find_as(:group).should =~ [ Group.last ] 60 | end 61 | end 62 | 63 | context "on a Group instance" do 64 | subject { Group.last } 65 | 66 | it "should ignore nil entries" do 67 | subject.subgroups.find_as(:group).should =~ [ ] 68 | end 69 | end 70 | end 71 | 72 | context "with an array of role names as argument" do 73 | context "on the Group class" do 74 | subject { Group } 75 | 76 | it "should include Group instances with both group and grouper roles" do 77 | subject.find_multiple_as([:group, :grouper]).should =~ [ Group.first, Group.last ] 78 | end 79 | end 80 | end 81 | 82 | context "with a role name and a user as arguments" do 83 | context "on the Forum class" do 84 | subject { Forum } 85 | 86 | it "should get all Forum instances binded to the forum role and the admin user" do 87 | subject.find_as(:forum, admin).should =~ [ Forum.first ] 88 | end 89 | 90 | it "should get all Forum instances binded to the forum role and the tourist user" do 91 | subject.find_as(:forum, tourist).should =~ [ Forum.last ] 92 | end 93 | 94 | it "should get all Forum instances binded to the godfather role and the admin user" do 95 | subject.find_as(:godfather, admin).should =~ Forum.all.to_a 96 | end 97 | 98 | it "should get all Forum instances binded to the godfather role and the tourist user" do 99 | subject.find_as(:godfather, tourist).should be_empty 100 | end 101 | 102 | it "should get Forum instances binded to the group role and the tourist user" do 103 | subject.find_as(:group, tourist).should =~ [ Forum.first ] 104 | end 105 | 106 | it "should not get Forum instances not binded to the group role and the tourist user" do 107 | subject.find_as(:group, tourist).should_not include(Forum.last) 108 | end 109 | end 110 | 111 | context "on the Group class" do 112 | subject { Group } 113 | 114 | it "should get all resources binded to the group role and the admin user" do 115 | subject.find_as(:group, admin).should =~ [ Group.last ] 116 | end 117 | 118 | it "should not get resources not binded to the group role and the admin user" do 119 | subject.find_as(:group, admin).should_not include(Group.first) 120 | end 121 | end 122 | end 123 | 124 | context "with an array of role names and a user as arguments" do 125 | context "on the Forum class" do 126 | subject { Forum } 127 | 128 | it "should get Forum instances binded to the forum and group roles and the tourist user" do 129 | subject.find_multiple_as([:forum, :group], tourist).should =~ [ Forum.first, Forum.last ] 130 | end 131 | 132 | end 133 | 134 | context "on the Group class" do 135 | subject { Group } 136 | 137 | it "should get Group instances binded to the group and grouper roles and the admin user" do 138 | subject.find_multiple_as([:group, :grouper], admin).should =~ [ Group.first, Group.last ] 139 | end 140 | 141 | end 142 | end 143 | 144 | context "with a model not having ID column" do 145 | subject { Team } 146 | 147 | it "should find Team instance using team_code column" do 148 | subject.find_multiple_as([:captain, :player], captain).should =~ [ Team.first, Team.last ] 149 | end 150 | end 151 | 152 | context "with a resource using STI" do 153 | subject { Organization } 154 | it "should find instances of children classes" do 155 | subject.find_multiple_as(:owner, admin).should =~ [ Company.first ] 156 | end 157 | end 158 | end 159 | 160 | 161 | describe ".except_multiple_as" do 162 | subject { Group } 163 | 164 | it { should respond_to(:find_roles).with(1).arguments } 165 | it { should respond_to(:find_roles).with(2).arguments } 166 | 167 | context "with a role name as argument" do 168 | context "on the Forum class" do 169 | subject { Forum } 170 | 171 | it "should not include Forum instances with forum role" do 172 | subject.except_as(:forum).should_not =~ [ Forum.first, Forum.last ] 173 | end 174 | 175 | it "should not include Forum instances with godfather role" do 176 | subject.except_as(:godfather).should be_empty 177 | end 178 | 179 | it "should be able to modify the resource", :if => ENV['ADAPTER'] == 'active_record' do 180 | forum_resource = subject.except_as(:forum).first 181 | forum_resource.name = "modified name" 182 | expect { forum_resource.save }.not_to raise_error 183 | end 184 | end 185 | 186 | context "on the Group class" do 187 | subject { Group } 188 | 189 | it "should not include Group instances with group role" do 190 | subject.except_as(:group).should_not =~ [ Group.last ] 191 | end 192 | end 193 | 194 | end 195 | 196 | context "with an array of role names as argument" do 197 | context "on the Group class" do 198 | subject { Group } 199 | 200 | it "should include Group instances without either the group and grouper roles" do 201 | subject.except_multiple_as([:group, :grouper]).should_not =~ [ Group.first, Group.last ] 202 | end 203 | end 204 | end 205 | 206 | context "with a role name and a user as arguments" do 207 | context "on the Forum class" do 208 | subject { Forum } 209 | 210 | it "should get all Forum instances the admin user does not have the forum role" do 211 | subject.except_as(:forum, admin).should_not =~ [ Forum.first ] 212 | end 213 | 214 | it "should get all Forum instances the tourist user does not have the forum role" do 215 | subject.except_as(:forum, tourist).should_not =~ [ Forum.last ] 216 | end 217 | 218 | it "should get all Forum instances the admin user does not have the godfather role" do 219 | subject.except_as(:godfather, admin).should_not =~ Forum.all 220 | end 221 | 222 | it "should get all Forum instances tourist user does not have the godfather role" do 223 | subject.except_as(:godfather, tourist).should =~ Forum.all 224 | end 225 | 226 | it "should get Forum instances the tourist user does not have the group role" do 227 | subject.except_as(:group, tourist).should_not =~ [ Forum.first ] 228 | end 229 | 230 | it "should get Forum instances the tourist user does not have the group role" do 231 | subject.except_as(:group, tourist).should_not =~ [ Forum.first ] 232 | end 233 | end 234 | 235 | context "on the Group class" do 236 | subject { Group } 237 | 238 | it "should get all resources not bounded to the group role and the admin user" do 239 | subject.except_as(:group, admin).should =~ [ Group.first ] 240 | end 241 | 242 | it "should not get resources bound to the group role and the admin user" do 243 | subject.except_as(:group, admin).should include(Group.first) 244 | end 245 | end 246 | end 247 | 248 | context "with an array of role names and a user as arguments" do 249 | context "on the Forum class" do 250 | subject { Forum } 251 | 252 | it "should get Forum instances not bound to the forum and group roles and the tourist user" do 253 | subject.except_multiple_as([:forum, :group], tourist).should_not =~ [ Forum.first, Forum.last ] 254 | end 255 | 256 | end 257 | 258 | context "on the Group class" do 259 | subject { Group } 260 | 261 | it "should get Group instances binded to the group and grouper roles and the admin user" do 262 | subject.except_multiple_as([:group, :grouper], admin).should =~ [ ] 263 | end 264 | 265 | end 266 | end 267 | 268 | context "with a model not having ID column" do 269 | subject { Team } 270 | 271 | it "should find Team instance not using team_code column" do 272 | subject.except_multiple_as(:captain, captain).should =~ [ Team.last ] 273 | end 274 | end 275 | 276 | context "with a resource using STI" do 277 | subject { Organization } 278 | it "should exclude instances of children classes with matching" do 279 | subject.except_as(:owner, admin).should_not =~ [ Company.first ] 280 | end 281 | end 282 | end 283 | 284 | describe ".find_role" do 285 | 286 | context "without using a role name parameter" do 287 | 288 | context "on the Forum class" do 289 | subject { Forum } 290 | 291 | it "should get all roles binded to a Forum class or instance" do 292 | subject.find_roles.to_a.should =~ [ forum_role, godfather_role, tourist_role, sneaky_role ] 293 | end 294 | 295 | it "should not get roles not binded to a Forum class or instance" do 296 | subject.find_roles.should_not include(group_role) 297 | end 298 | 299 | context "using :any parameter" do 300 | it "should get all roles binded to any Forum class or instance" do 301 | subject.find_roles(:any, :any).to_a.should =~ [ forum_role, godfather_role, tourist_role, sneaky_role ] 302 | end 303 | 304 | it "should not get roles not binded to a Forum class or instance" do 305 | subject.find_roles(:any, :any).should_not include(group_role) 306 | end 307 | end 308 | end 309 | 310 | context "on the Group class" do 311 | subject { Group } 312 | 313 | it "should get all roles binded to a Group class or instance" do 314 | subject.find_roles.to_a.should =~ [ group_role, grouper_role ] 315 | end 316 | 317 | it "should not get roles not binded to a Group class or instance" do 318 | subject.find_roles.should_not include(forum_role, godfather_role, tourist_role, sneaky_role) 319 | end 320 | 321 | context "using :any parameter" do 322 | it "should get all roles binded to Group class or instance" do 323 | subject.find_roles(:any, :any).to_a.should =~ [ group_role, grouper_role ] 324 | end 325 | 326 | it "should not get roles not binded to a Group class or instance" do 327 | subject.find_roles(:any, :any).should_not include(forum_role, godfather_role, tourist_role, sneaky_role) 328 | end 329 | end 330 | end 331 | end 332 | 333 | context "using a role name parameter" do 334 | context "on the Forum class" do 335 | subject { Forum } 336 | 337 | context "without using a user parameter" do 338 | it "should get all roles binded to a Forum class or instance and forum role name" do 339 | subject.find_roles(:forum).should include(forum_role, tourist_role) 340 | end 341 | 342 | it "should not get roles not binded to a Forum class or instance and forum role name" do 343 | subject.find_roles(:forum).should_not include(godfather_role, sneaky_role, group_role) 344 | end 345 | end 346 | 347 | context "using a user parameter" do 348 | it "should get all roles binded to any resource" do 349 | subject.find_roles(:forum, admin).to_a.should =~ [ forum_role ] 350 | end 351 | 352 | it "should not get roles not binded to the admin user and forum role name" do 353 | subject.find_roles(:forum, admin).should_not include(godfather_role, tourist_role, sneaky_role, group_role) 354 | end 355 | end 356 | 357 | context "using :any parameter" do 358 | it "should get all roles binded to any resource with forum role name" do 359 | subject.find_roles(:forum, :any).should include(forum_role, tourist_role) 360 | end 361 | 362 | it "should not get roles not binded to a resource with forum role name" do 363 | subject.find_roles(:forum, :any).should_not include(godfather_role, sneaky_role, group_role) 364 | end 365 | end 366 | end 367 | 368 | context "on the Group class" do 369 | subject { Group } 370 | 371 | context "without using a user parameter" do 372 | it "should get all roles binded to a Group class or instance and group role name" do 373 | subject.find_roles(:group).should include(group_role) 374 | end 375 | 376 | it "should not get roles not binded to a Forum class or instance and forum role name" do 377 | subject.find_roles(:group).should_not include(tourist_role, godfather_role, sneaky_role, forum_role) 378 | end 379 | end 380 | 381 | context "using a user parameter" do 382 | it "should get all roles binded to any resource" do 383 | subject.find_roles(:group, admin).should include(group_role) 384 | end 385 | 386 | it "should not get roles not binded to the admin user and forum role name" do 387 | subject.find_roles(:group, admin).should_not include(godfather_role, tourist_role, sneaky_role, forum_role) 388 | end 389 | end 390 | 391 | context "using :any parameter" do 392 | it "should get all roles binded to any resource with forum role name" do 393 | subject.find_roles(:group, :any).should include(group_role) 394 | end 395 | 396 | it "should not get roles not binded to a resource with forum role name" do 397 | subject.find_roles(:group, :any).should_not include(godfather_role, sneaky_role, forum_role, tourist_role) 398 | end 399 | end 400 | end 401 | end 402 | 403 | context "using :any as role name parameter" do 404 | context "on the Forum class" do 405 | subject { Forum } 406 | 407 | context "without using a user parameter" do 408 | it "should get all roles binded to a Forum class or instance" do 409 | subject.find_roles(:any).should include(forum_role, godfather_role, tourist_role, sneaky_role) 410 | end 411 | 412 | it "should not get roles not binded to a Forum class or instance" do 413 | subject.find_roles(:any).should_not include(group_role) 414 | end 415 | end 416 | 417 | context "using a user parameter" do 418 | it "should get all roles binded to a Forum class or instance and admin user" do 419 | subject.find_roles(:any, admin).should include(forum_role, godfather_role) 420 | end 421 | 422 | it "should not get roles not binded to the admin user and Forum class or instance" do 423 | subject.find_roles(:any, admin).should_not include(tourist_role, sneaky_role, group_role) 424 | end 425 | end 426 | 427 | context "using :any as user parameter" do 428 | it "should get all roles binded to a Forum class or instance" do 429 | subject.find_roles(:any, :any).should include(forum_role, godfather_role, tourist_role, sneaky_role) 430 | end 431 | 432 | it "should not get roles not binded to a Forum class or instance" do 433 | subject.find_roles(:any, :any).should_not include(group_role) 434 | end 435 | end 436 | end 437 | 438 | context "on the Group class" do 439 | subject { Group } 440 | 441 | context "without using a user parameter" do 442 | it "should get all roles binded to a Group class or instance" do 443 | subject.find_roles(:any).should include(group_role) 444 | end 445 | 446 | it "should not get roles not binded to a Group class or instance" do 447 | subject.find_roles(:any).should_not include(forum_role, godfather_role, tourist_role, sneaky_role) 448 | end 449 | end 450 | 451 | context "using a user parameter" do 452 | it "should get all roles binded to a Group class or instance and admin user" do 453 | subject.find_roles(:any, admin).should include(group_role) 454 | end 455 | 456 | it "should not get roles not binded to the admin user and Group class or instance" do 457 | subject.find_roles(:any, admin).should_not include(forum_role, godfather_role, tourist_role, sneaky_role) 458 | end 459 | end 460 | 461 | context "using :any as user parameter" do 462 | it "should get all roles binded to a Group class or instance" do 463 | subject.find_roles(:any, :any).should include(group_role) 464 | end 465 | 466 | it "should not get roles not binded to a Group class or instance" do 467 | subject.find_roles(:any, :any).should_not include(forum_role, godfather_role, tourist_role, sneaky_role) 468 | end 469 | end 470 | end 471 | end 472 | 473 | context "with a resource using STI" do 474 | subject{ Organization } 475 | it "should find instances of children classes" do 476 | subject.find_roles(:owner, admin).should =~ [company_role] 477 | end 478 | end 479 | end 480 | 481 | describe "#roles" do 482 | before(:all) { Role.destroy_all } 483 | subject { Forum.first } 484 | 485 | it { should respond_to :roles } 486 | 487 | context "on a Forum instance" do 488 | its(:roles) { should match_array( [ forum_role, sneaky_role ]) } 489 | its(:roles) { should_not include(group_role, godfather_role, tourist_role) } 490 | end 491 | 492 | context "on a Group instance" do 493 | subject { Group.last } 494 | 495 | its(:roles) { should eq([ group_role ]) } 496 | its(:roles) { should_not include(forum_role, godfather_role, sneaky_role, tourist_role) } 497 | 498 | context "when deleting a Group instance" do 499 | subject do 500 | Group.create(:name => "to delete") 501 | end 502 | 503 | before do 504 | subject.roles.create :name => "group_role1" 505 | subject.roles.create :name => "group_role2" 506 | end 507 | 508 | it "should remove the roles binded to this instance" do 509 | expect { subject.destroy }.to change { Role.count }.by(-2) 510 | end 511 | end 512 | end 513 | end 514 | 515 | describe "#applied_roles" do 516 | context "on a Forum instance" do 517 | subject { Forum.first } 518 | 519 | its(:applied_roles) { should =~ [ forum_role, godfather_role, sneaky_role ] } 520 | its(:applied_roles) { should_not include(group_role, tourist_role) } 521 | end 522 | 523 | context "on a Group instance" do 524 | subject { Group.last } 525 | 526 | its(:applied_roles) { should =~ [ group_role ] } 527 | its(:applied_roles) { should_not include(forum_role, godfather_role, sneaky_role, tourist_role) } 528 | end 529 | end 530 | 531 | 532 | describe '.resource_types' do 533 | 534 | it 'include all models that call resourcify' do 535 | Rolify.resource_types.should include("HumanResource", "Forum", "Group", 536 | "Team", "Organization") 537 | end 538 | end 539 | 540 | 541 | describe "#strict" do 542 | context "strict user" do 543 | before(:all) do 544 | @strict_user = StrictUser.first 545 | @strict_user.role_ids 546 | @strict_user.add_role(:forum, Forum.first) 547 | @strict_user.add_role(:forum, Forum) 548 | end 549 | 550 | it "should return only strict forum" do 551 | @strict_user.has_role?(:forum, Forum.first).should be true 552 | @strict_user.has_cached_role?(:forum, Forum.first).should be true 553 | end 554 | 555 | it "should return false on strict another forum" do 556 | @strict_user.has_role?(:forum, Forum.last).should be false 557 | @strict_user.has_cached_role?(:forum, Forum.last).should be false 558 | end 559 | 560 | it "should return true if user has role on Forum model" do 561 | @strict_user.has_role?(:forum, Forum).should be true 562 | @strict_user.has_cached_role?(:forum, Forum).should be true 563 | end 564 | 565 | it "should return true if user has role any forum name" do 566 | @strict_user.has_role?(:forum, :any).should be true 567 | @strict_user.has_cached_role?(:forum, :any).should be true 568 | end 569 | 570 | it "should return false when deleted role on Forum model" do 571 | @strict_user.remove_role(:forum, Forum) 572 | @strict_user.has_role?(:forum, Forum).should be false 573 | @strict_user.has_cached_role?(:forum, Forum).should be false 574 | end 575 | end 576 | end 577 | end 578 | -------------------------------------------------------------------------------- /spec/rolify/resourcifed_and_rolifed_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe "Resourcify and rolify on the same model" do 4 | 5 | before(:all) do 6 | reset_defaults 7 | Role.delete_all 8 | HumanResource.delete_all 9 | end 10 | 11 | let!(:user) do 12 | user = HumanResource.new login: 'Samer' 13 | user.save 14 | user 15 | end 16 | 17 | it "should add the role to the user" do 18 | expect { user.add_role :admin }.to change { user.roles.count }.by(1) 19 | end 20 | 21 | it "should create a role to the roles collection" do 22 | expect { user.add_role :moderator }.to change { Role.count }.by(1) 23 | end 24 | end -------------------------------------------------------------------------------- /spec/rolify/role_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Rolify do 4 | 5 | context 'cache' do 6 | let(:user) { User.first } 7 | before { user.grant(:zombie) } 8 | specify do 9 | expect(user).to have_role(:zombie) 10 | user.remove_role(:zombie) 11 | expect(user).to_not have_role(:zombie) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/rolify/shared_contexts.rb: -------------------------------------------------------------------------------- 1 | shared_context "global role", :scope => :global do 2 | subject { admin } 3 | 4 | def admin 5 | user_class.first 6 | end 7 | 8 | before(:all) do 9 | load_roles 10 | create_other_roles 11 | end 12 | 13 | def load_roles 14 | role_class.destroy_all 15 | admin.roles = [] 16 | admin.add_role :admin 17 | admin.add_role :staff 18 | admin.add_role :manager, Group 19 | admin.add_role :player, Forum 20 | admin.add_role :moderator, Forum.last 21 | admin.add_role :moderator, Group.last 22 | admin.add_role :anonymous, Forum.first 23 | end 24 | end 25 | 26 | shared_context "class scoped role", :scope => :class do 27 | subject { manager } 28 | 29 | before(:all) do 30 | load_roles 31 | create_other_roles 32 | end 33 | 34 | def manager 35 | user_class.where(:login => "moderator").first 36 | end 37 | 38 | def load_roles 39 | role_class.destroy_all 40 | manager.roles = [] 41 | manager.add_role :manager, Forum 42 | manager.add_role :player, Forum 43 | manager.add_role :warrior 44 | manager.add_role :moderator, Forum.last 45 | manager.add_role :moderator, Group.last 46 | manager.add_role :anonymous, Forum.first 47 | end 48 | end 49 | 50 | shared_context "instance scoped role", :scope => :instance do 51 | subject { moderator } 52 | 53 | before(:all) do 54 | load_roles 55 | create_other_roles 56 | end 57 | 58 | def moderator 59 | user_class.where(:login => "god").first 60 | end 61 | 62 | def load_roles 63 | role_class.destroy_all 64 | moderator.roles = [] 65 | moderator.add_role :moderator, Forum.first 66 | moderator.add_role :anonymous, Forum.last 67 | moderator.add_role :visitor, Forum 68 | moderator.add_role :soldier 69 | end 70 | end 71 | 72 | shared_context "mixed scoped roles", :scope => :mixed do 73 | subject { user_class } 74 | 75 | before(:all) do 76 | role_class.destroy_all 77 | end 78 | 79 | let!(:root) { provision_user(user_class.first, [ :admin, :staff, [ :moderator, Group ], [ :visitor, Forum.last ] ]) } 80 | let!(:modo) { provision_user(user_class.where(:login => "moderator").first, [[ :moderator, Forum ], [ :manager, Group ], [ :visitor, Group.first ]])} 81 | let!(:visitor) { provision_user(user_class.last, [[ :visitor, Forum.last ]]) } 82 | let!(:owner) { provision_user(user_class.first, [[:owner, Company.first]]) } 83 | end 84 | 85 | def create_other_roles 86 | role_class.create :name => "superhero" 87 | role_class.create :name => "admin", :resource_type => "Group" 88 | role_class.create :name => "admin", :resource => Forum.first 89 | role_class.create :name => "VIP", :resource_type => "Forum" 90 | role_class.create :name => "manager", :resource => Forum.last 91 | role_class.create :name => "roomate", :resource => Forum.first 92 | role_class.create :name => "moderator", :resource => Group.first 93 | end -------------------------------------------------------------------------------- /spec/rolify/shared_examples/shared_examples_for_add_role.rb: -------------------------------------------------------------------------------- 1 | shared_examples_for "#add_role_examples" do |param_name, param_method| 2 | context "using #{param_name} as parameter" do 3 | context "with a global role", :scope => :global do 4 | it "should add the role to the user" do 5 | expect { subject.add_role "root".send(param_method) }.to change { subject.roles.count }.by(1) 6 | end 7 | 8 | it "should create a role to the roles table" do 9 | expect { subject.add_role "moderator".send(param_method) }.to change { role_class.count }.by(1) 10 | end 11 | 12 | context "considering a new global role" do 13 | it "creates a new class scoped role" do 14 | expect(subject.add_role "expert".send(param_method)).to be_the_same_role("expert") 15 | end 16 | end 17 | 18 | context "should not create another role" do 19 | it "if the role was already assigned to the user" do 20 | subject.add_role "manager".send(param_method) 21 | expect { subject.add_role "manager".send(param_method) }.not_to change { subject.roles.size } 22 | end 23 | 24 | it "if the role already exists in the db" do 25 | role_class.create :name => "god" 26 | expect { subject.add_role "god".send(param_method) }.not_to change { role_class.count } 27 | end 28 | end 29 | end 30 | 31 | context "with a class scoped role", :scope => :class do 32 | it "should add the role to the user" do 33 | expect { subject.add_role "supervisor".send(param_method), Forum }.to change { subject.roles.count }.by(1) 34 | end 35 | 36 | it "should create a role in the roles table" do 37 | expect { subject.add_role "moderator".send(param_method), Forum }.to change { role_class.count }.by(1) 38 | end 39 | 40 | context "considering a new class scoped role" do 41 | it "creates a new class scoped role" do 42 | expect(subject.add_role "boss".send(param_method), Forum).to be_the_same_role("boss", Forum) 43 | end 44 | end 45 | 46 | context "should not create another role" do 47 | it "if the role was already assigned to the user" do 48 | subject.add_role "warrior".send(param_method), Forum 49 | expect { subject.add_role "warrior".send(param_method), Forum }.not_to change { subject.roles.count } 50 | end 51 | 52 | it "if already existing in the database" do 53 | role_class.create :name => "hacker", :resource_type => "Forum" 54 | expect { subject.add_role "hacker".send(param_method), Forum }.not_to change { role_class.count } 55 | end 56 | end 57 | end 58 | 59 | context "with an instance scoped role", :scope => :instance do 60 | it "should add the role to the user" do 61 | expect { subject.add_role "visitor".send(param_method), Forum.last }.to change { subject.roles.count }.by(1) 62 | end 63 | 64 | it "should create a role in the roles table" do 65 | expect { subject.add_role "member".send(param_method), Forum.last }.to change { role_class.count }.by(1) 66 | end 67 | 68 | it "creates a new instance scoped role" do 69 | expect(subject.add_role "mate".send(param_method), Forum.last).to be_the_same_role("mate", Forum.last) 70 | end 71 | 72 | context "should not create another role" do 73 | it "if the role was already assigned to the user" do 74 | subject.add_role "anonymous".send(param_method), Forum.first 75 | expect { subject.add_role "anonymous".send(param_method), Forum.first }.not_to change { subject.roles.size } 76 | end 77 | 78 | it "if already existing in the database" do 79 | role_class.create :name => "ghost", :resource_type => "Forum", :resource_id => Forum.first.id 80 | expect { subject.add_role "ghost".send(param_method), Forum.first }.not_to change { role_class.count } 81 | end 82 | end 83 | end 84 | end 85 | end -------------------------------------------------------------------------------- /spec/rolify/shared_examples/shared_examples_for_callbacks.rb: -------------------------------------------------------------------------------- 1 | shared_examples_for "Rolify.callbacks" do 2 | before(:all) do 3 | reset_defaults 4 | Rolify.dynamic_shortcuts = false 5 | role_class.destroy_all 6 | end 7 | 8 | after :each do 9 | @user.roles.destroy_all 10 | end 11 | 12 | describe "rolify association callbacks", :if => (Rolify.orm == "active_record") do 13 | describe "before_add" do 14 | it "should receive callback" do 15 | rolify_options = { :role_cname => role_class.to_s, :before_add => :role_callback } 16 | rolify_options[:role_join_table_name] = join_table if defined? join_table 17 | silence_warnings { user_class.rolify rolify_options } 18 | @user = user_class.first 19 | @user.stub(:role_callback) 20 | @user.should_receive(:role_callback) 21 | @user.add_role :admin 22 | end 23 | end 24 | 25 | describe "after_add" do 26 | it "should receive callback" do 27 | rolify_options = { :role_cname => role_class.to_s, :after_add => :role_callback } 28 | rolify_options[:role_join_table_name] = join_table if defined? join_table 29 | silence_warnings { user_class.rolify rolify_options } 30 | @user = user_class.first 31 | @user.stub(:role_callback) 32 | @user.should_receive(:role_callback) 33 | @user.add_role :admin 34 | end 35 | end 36 | 37 | describe "before_remove" do 38 | it "should receive callback" do 39 | rolify_options = { :role_cname => role_class.to_s, :before_remove => :role_callback } 40 | rolify_options[:role_join_table_name] = join_table if defined? join_table 41 | silence_warnings { user_class.rolify rolify_options } 42 | @user = user_class.first 43 | @user.add_role :admin 44 | @user.stub(:role_callback) 45 | 46 | @user.should_receive(:role_callback) 47 | @user.remove_role :admin 48 | end 49 | end 50 | 51 | describe "after_remove" do 52 | it "should receive callback" do 53 | rolify_options = { :role_cname => role_class.to_s, :after_remove => :role_callback } 54 | rolify_options[:role_join_table_name] = join_table if defined? join_table 55 | silence_warnings { user_class.rolify rolify_options } 56 | @user = user_class.first 57 | @user.add_role :admin 58 | @user.stub(:role_callback) 59 | 60 | @user.should_receive(:role_callback) 61 | @user.remove_role :admin 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/rolify/shared_examples/shared_examples_for_dynamic.rb: -------------------------------------------------------------------------------- 1 | shared_examples_for Rolify::Dynamic do 2 | before(:all) do 3 | Rolify.dynamic_shortcuts = true 4 | role_class.destroy_all 5 | rolify_options = { :role_cname => role_class.to_s } 6 | rolify_options[:role_join_table_name] = join_table if defined? join_table 7 | silence_warnings { user_class.rolify rolify_options } 8 | Forum.resourcify :roles, :role_cname => role_class.to_s 9 | Group.resourcify :roles, :role_cname => role_class.to_s 10 | end 11 | 12 | context "using a global role" do 13 | subject do 14 | admin = user_class.first 15 | admin.add_role :admin 16 | admin.add_role :moderator, Forum.first 17 | admin.add_role :solo 18 | admin 19 | end 20 | 21 | it { should respond_to(:is_admin?).with(0).arguments } 22 | it { should respond_to(:is_moderator_of?).with(1).arguments } 23 | it { should_not respond_to(:is_god?) } 24 | 25 | it { subject.is_admin?.should be(true) } 26 | it { subject.is_admin?.should be(true) } 27 | it { subject.is_admin?.should be(true) } 28 | 29 | context "removing the role on the last user having it" do 30 | before do 31 | subject.remove_role :solo 32 | end 33 | 34 | it { should_not respond_to(:is_solo?) } 35 | it { subject.is_solo?.should be(false) } 36 | end 37 | end 38 | 39 | context "using a resource scoped role" do 40 | subject do 41 | moderator = user_class.where(:login => "moderator").first 42 | moderator.add_role :moderator, Forum.first 43 | moderator.add_role :sole_mio, Forum.last 44 | moderator 45 | end 46 | 47 | it { should respond_to(:is_moderator?).with(0).arguments } 48 | it { should respond_to(:is_moderator_of?).with(1).arguments } 49 | it { should_not respond_to(:is_god?) } 50 | it { should_not respond_to(:is_god_of?) } 51 | 52 | it { subject.is_moderator?.should be(false) } 53 | it { subject.is_moderator_of?(Forum).should be(false) } 54 | it { subject.is_moderator_of?(Forum.first).should be(true) } 55 | it { subject.is_moderator_of?(Forum.last).should be(false) } 56 | it { subject.is_moderator_of?(Group).should be(false) } 57 | it { subject.is_moderator_of?(Group.first).should be(false) } 58 | it { subject.is_moderator_of?(Group.last).should be(false) } 59 | 60 | context "removing the role on the last user having it" do 61 | before do 62 | subject.remove_role :sole_mio, Forum.last 63 | end 64 | 65 | it { should_not respond_to(:is_sole_mio?) } 66 | it { subject.is_sole_mio?.should be(false) } 67 | end 68 | end 69 | 70 | context "using a class scoped role" do 71 | subject do 72 | manager = user_class.where(:login => "god").first 73 | manager.add_role :manager, Forum 74 | manager.add_role :only_me, Forum 75 | manager 76 | end 77 | 78 | it { should respond_to(:is_manager?).with(0).arguments } 79 | it { should respond_to(:is_manager_of?).with(1).arguments } 80 | it { should_not respond_to(:is_god?) } 81 | it { should_not respond_to(:is_god_of?) } 82 | 83 | it { subject.is_manager?.should be(false) } 84 | it { subject.is_manager_of?(Forum).should be(true) } 85 | it { subject.is_manager_of?(Forum.first).should be(true) } 86 | it { subject.is_manager_of?(Forum.last).should be(true) } 87 | it { subject.is_manager_of?(Group).should be(false) } 88 | it { subject.is_manager_of?(Group.first).should be(false) } 89 | it { subject.is_manager_of?(Group.last).should be(false) } 90 | 91 | context "removing the role on the last user having it" do 92 | before do 93 | subject.remove_role :only_me, Forum 94 | end 95 | 96 | it { should_not respond_to(:is_only_me?) } 97 | it { subject.is_only_me?.should be(false) } 98 | end 99 | end 100 | 101 | context "if the role doesn't exist in the database" do 102 | 103 | subject { user_class.first } 104 | 105 | context "using a global role" do 106 | before(:all) do 107 | other_guy = user_class.last 108 | other_guy.add_role :superman 109 | end 110 | 111 | it { should respond_to(:is_superman?).with(0).arguments } 112 | it { should_not respond_to(:is_superman_of?) } 113 | it { should_not respond_to(:is_god?) } 114 | 115 | it { subject.is_superman?.should be(false) } 116 | it { subject.is_god?.should be(false) } 117 | end 118 | 119 | context "using a resource scope role" do 120 | before(:all) do 121 | other_guy = user_class.last 122 | other_guy.add_role :batman, Forum.first 123 | end 124 | 125 | it { should respond_to(:is_batman?).with(0).arguments } 126 | it { should respond_to(:is_batman_of?).with(1).arguments } 127 | it { should_not respond_to(:is_god?) } 128 | it { should_not respond_to(:is_god_of?) } 129 | 130 | it { subject.is_batman?.should be(false) } 131 | it { subject.is_batman_of?(Forum).should be(false) } 132 | it { subject.is_batman_of?(Forum.first).should be(false) } 133 | it { subject.is_batman_of?(Forum.last).should be(false) } 134 | it { subject.is_batman_of?(Group).should be(false) } 135 | it { subject.is_batman_of?(Group.first).should be(false) } 136 | it { subject.is_batman_of?(Group.last).should be(false) } 137 | 138 | it { subject.is_god?.should be(false) } 139 | it { subject.is_god_of?(Forum).should be(false) } 140 | it { subject.is_god_of?(Forum.first).should be(false) } 141 | it { subject.is_god_of?(Forum.last).should be(false) } 142 | it { subject.is_god_of?(Group).should be(false) } 143 | it { subject.is_god_of?(Group.first).should be(false) } 144 | it { subject.is_god_of?(Group.last).should be(false) } 145 | end 146 | end 147 | end 148 | -------------------------------------------------------------------------------- /spec/rolify/shared_examples/shared_examples_for_finders.rb: -------------------------------------------------------------------------------- 1 | shared_examples_for :finders do |param_name, param_method| 2 | context "using #{param_name} as parameter" do 3 | describe ".with_role" do 4 | it { should respond_to(:with_role).with(1).argument } 5 | it { should respond_to(:with_role).with(2).arguments } 6 | 7 | context "when resource setting: strict is set to false" do 8 | context "with a global role" do 9 | it { subject.with_role("admin".send(param_method)).should eq([ root ]) } 10 | it { subject.with_role("moderator".send(param_method)).should be_empty } 11 | it { subject.with_role("visitor".send(param_method)).should be_empty } 12 | end 13 | 14 | context "with a class scoped role" do 15 | context "on Forum class" do 16 | it { subject.with_role("admin".send(param_method), Forum).should eq([ root ]) } 17 | it { subject.with_role("moderator".send(param_method), Forum).should eq([ modo ]) } 18 | it { subject.with_role("visitor".send(param_method), Forum).should be_empty } 19 | end 20 | 21 | context "on Group class" do 22 | it { subject.with_role("admin".send(param_method), Group).should eq([ root ]) } 23 | it { subject.with_role("moderator".send(param_method), Group).should eq([ root ]) } 24 | it { subject.with_role("visitor".send(param_method), Group).should be_empty } 25 | end 26 | end 27 | 28 | context "with an instance scoped role" do 29 | context "on Forum.first instance" do 30 | it { subject.with_role("admin".send(param_method), Forum.first).should eq([ root ]) } 31 | it { subject.with_role("moderator".send(param_method), Forum.first).should eq([ modo ]) } 32 | it { subject.with_role("visitor".send(param_method), Forum.first).should be_empty } 33 | end 34 | 35 | context "on Forum.last instance" do 36 | it { subject.with_role("admin".send(param_method), Forum.last).should eq([ root ]) } 37 | it { subject.with_role("moderator".send(param_method), Forum.last).should eq([ modo ]) } 38 | it { subject.with_role("visitor".send(param_method), Forum.last).should include(root, visitor) } # =~ doesn't pass using mongoid, don't know why... 39 | end 40 | 41 | context "on Group.first instance" do 42 | it { subject.with_role("admin".send(param_method), Group.first).should eq([ root ]) } 43 | it { subject.with_role("moderator".send(param_method), Group.first).should eq([ root ]) } 44 | it { subject.with_role("visitor".send(param_method), Group.first).should eq([ modo ]) } 45 | end 46 | 47 | context "on Company.first_instance" do 48 | it { subject.with_role("owner".send(param_method), Company.first).should eq([ owner ]) } 49 | end 50 | end 51 | end 52 | 53 | context "when resource setting: strict is set to true" do 54 | before(:context) do 55 | user_class.strict_rolify = true 56 | end 57 | after(:context) do 58 | user_class.strict_rolify = false 59 | end 60 | 61 | context "with an instance scoped role" do 62 | context "on Forum.first instance" do 63 | it { subject.with_role("admin".send(param_method), Forum.first).should be_empty } 64 | it { subject.with_role("moderator".send(param_method), Forum.first).should be_empty } 65 | end 66 | 67 | context "on any resource" do 68 | it { subject.with_role("admin".send(param_method), :any).should_not be_empty } 69 | it { subject.with_role("moderator".send(param_method), :any).should_not be_empty } 70 | end 71 | end 72 | end 73 | end 74 | 75 | describe ".without_role" do 76 | it { should respond_to(:without_role).with(1).argument } 77 | it { should respond_to(:without_role).with(2).arguments } 78 | 79 | context "with a global role" do 80 | it { subject.without_role("admin".send(param_method)).should_not eq([ root ]) } 81 | it { subject.without_role("moderator".send(param_method)).should_not be_empty } 82 | it { subject.without_role("visitor".send(param_method)).should_not be_empty } 83 | end 84 | 85 | context "with a class scoped role" do 86 | context "on Forum class" do 87 | it { subject.without_role("admin".send(param_method), Forum).should_not eq([ root ]) } 88 | it { subject.without_role("moderator".send(param_method), Forum).should_not eq([ modo ]) } 89 | it { subject.without_role("visitor".send(param_method), Forum).should_not be_empty } 90 | end 91 | 92 | context "on Group class" do 93 | it { subject.without_role("admin".send(param_method), Group).should_not eq([ root ]) } 94 | it { subject.without_role("moderator".send(param_method), Group).should_not eq([ root ]) } 95 | it { subject.without_role("visitor".send(param_method), Group).should_not be_empty } 96 | end 97 | end 98 | 99 | context "with an instance scoped role" do 100 | context "on Forum.first instance" do 101 | it { subject.without_role("admin".send(param_method), Forum.first).should_not eq([ root ]) } 102 | it { subject.without_role("moderator".send(param_method), Forum.first).should_not eq([ modo ]) } 103 | it { subject.without_role("visitor".send(param_method), Forum.first).should_not be_empty } 104 | end 105 | 106 | context "on Forum.last instance" do 107 | it { subject.without_role("admin".send(param_method), Forum.last).should_not eq([ root ]) } 108 | it { subject.without_role("moderator".send(param_method), Forum.last).should_not eq([ modo ]) } 109 | it { subject.without_role("visitor".send(param_method), Forum.last).should_not include(root, visitor) } # =~ doesn't pass using mongoid, don't know why... 110 | end 111 | 112 | context "on Group.first instance" do 113 | it { subject.without_role("admin".send(param_method), Group.first).should_not eq([ root ]) } 114 | it { subject.without_role("moderator".send(param_method), Group.first).should_not eq([ root ]) } 115 | it { subject.without_role("visitor".send(param_method), Group.first).should_not eq([ modo ]) } 116 | end 117 | 118 | context "on Company.first_instance" do 119 | it { subject.without_role("owner".send(param_method), Company.first).should_not eq([ owner ]) } 120 | end 121 | end 122 | end 123 | 124 | 125 | describe ".with_all_roles" do 126 | it { should respond_to(:with_all_roles) } 127 | 128 | it { subject.with_all_roles("admin".send(param_method), :staff).should eq([ root ]) } 129 | it { subject.with_all_roles("admin".send(param_method), :staff, { :name => "moderator".send(param_method), :resource => Group }).should eq([ root ]) } 130 | it { subject.with_all_roles("admin".send(param_method), "moderator".send(param_method)).should be_empty } 131 | it { subject.with_all_roles("admin".send(param_method), :staff, { :name => "moderator".send(param_method), :resource => Forum }).should be_empty } 132 | it { subject.with_all_roles({ :name => "moderator".send(param_method), :resource => Forum }, { :name => :manager, :resource => Group }).should eq([ modo ]) } 133 | it { subject.with_all_roles("moderator".send(param_method), :manager).should be_empty } 134 | it { subject.with_all_roles({ :name => "visitor".send(param_method), :resource => Forum.last }, { :name => "moderator".send(param_method), :resource => Group }).should eq([ root ]) } 135 | it { subject.with_all_roles({ :name => "visitor".send(param_method), :resource => Group.first }, { :name => "moderator".send(param_method), :resource => Forum }).should eq([ modo ]) } 136 | it { subject.with_all_roles({ :name => "visitor".send(param_method), :resource => :any }, { :name => "moderator".send(param_method), :resource => :any }).should =~ [ root, modo ] } 137 | end 138 | 139 | describe ".with_any_role" do 140 | it { should respond_to(:with_any_role) } 141 | 142 | it { subject.with_any_role("admin".send(param_method), :staff).should eq([ root ]) } 143 | it { subject.with_any_role("admin".send(param_method), :staff, { :name => "moderator".send(param_method), :resource => Group }).should eq([ root ]) } 144 | it { subject.with_any_role("admin".send(param_method), "moderator".send(param_method)).should eq([ root ]) } 145 | it { subject.with_any_role("admin".send(param_method), :staff, { :name => "moderator".send(param_method), :resource => Forum }).should =~ [ root, modo ] } 146 | it { subject.with_any_role({ :name => "moderator".send(param_method), :resource => Forum }, { :name => :manager, :resource => Group }).should eq([ modo ]) } 147 | it { subject.with_any_role({ :name => "moderator".send(param_method), :resource => Group }, { :name => :manager, :resource => Group }).should =~ [ root, modo ] } 148 | it { subject.with_any_role("moderator".send(param_method), :manager).should be_empty } 149 | it { subject.with_any_role({ :name => "visitor".send(param_method), :resource => Forum.last }, { :name => "moderator".send(param_method), :resource => Group }).should =~ [ root, visitor ] } 150 | it { subject.with_any_role({ :name => "visitor".send(param_method), :resource => Group.first }, { :name => "moderator".send(param_method), :resource => Forum }).should eq([ modo ]) } 151 | it { subject.with_any_role({ :name => "visitor".send(param_method), :resource => :any }, { :name => "moderator".send(param_method), :resource => :any }).should =~ [ root, modo, visitor ] } 152 | end 153 | end 154 | end 155 | -------------------------------------------------------------------------------- /spec/rolify/shared_examples/shared_examples_for_has_all_roles.rb: -------------------------------------------------------------------------------- 1 | shared_examples_for "#has_all_roles?_examples" do |param_name, param_method| 2 | context "using #{param_name} as parameter" do 3 | context "with a global role", :scope => :global do 4 | context "on global roles only request" do 5 | it { subject.has_all_roles?("staff".send(param_method)).should be_truthy } 6 | it { subject.has_all_roles?("admin".send(param_method), "staff".send(param_method)).should be_truthy } 7 | it { subject.has_all_roles?("admin".send(param_method), "dummy".send(param_method)).should be_falsey } 8 | it { subject.has_all_roles?("dummy".send(param_method), "dumber".send(param_method)).should be_falsey } 9 | end 10 | 11 | context "on mixed scoped roles" do 12 | it { subject.has_all_roles?({ :name => "admin".send(param_method), :resource => Forum }, { :name => "admin".send(param_method), :resource => Group }).should be_truthy } 13 | it { subject.has_all_roles?({ :name => "admin".send(param_method), :resource => :any }, { :name => "admin".send(param_method), :resource => Group }).should be_truthy } 14 | it { subject.has_all_roles?({ :name => "admin".send(param_method), :resource => Forum }, { :name => "staff".send(param_method), :resource => Group.last }).should be_truthy } 15 | it { subject.has_all_roles?({ :name => "admin".send(param_method), :resource => Forum.first }, { :name => "admin".send(param_method), :resource => Forum.last }).should be_truthy } 16 | it { subject.has_all_roles?({ :name => "admin".send(param_method), :resource => Forum.first }, { :name => "dummy".send(param_method), :resource => Forum.last }).should be_falsey } 17 | it { subject.has_all_roles?({ :name => "admin".send(param_method), :resource => Forum.first }, { :name => "dummy".send(param_method), :resource => :any }).should be_falsey} 18 | end 19 | end 20 | 21 | context "with a class scoped role", :scope => :class do 22 | context "on class scoped roles only" do 23 | it { subject.has_all_roles?({ :name => "player".send(param_method), :resource => Forum }).should be_truthy } 24 | it { subject.has_all_roles?({ :name => "manager".send(param_method), :resource => Forum }, { :name => "player".send(param_method), :resource => Forum }).should be_truthy } 25 | it { subject.has_all_roles?({ :name => "manager".send(param_method), :resource => :any }, { :name => "player".send(param_method), :resource => Forum }).should be_truthy } 26 | it { subject.has_all_roles?({ :name => "manager".send(param_method), :resource => :any }, { :name => "player".send(param_method), :resource => :any }).should be_truthy } 27 | it { subject.has_all_roles?({ :name => "manager".send(param_method), :resource => Forum }, { :name => "dummy".send(param_method), :resource => Forum }).should be_falsey } 28 | it { subject.has_all_roles?({ :name => "manager".send(param_method), :resource => Forum }, { :name => "dummy".send(param_method), :resource => :any }).should be_falsey } 29 | it { subject.has_all_roles?({ :name => "dummy".send(param_method), :resource => Forum }, { :name => "dumber".send(param_method), :resource => Group }).should be_falsey } 30 | end 31 | 32 | context "on mixed scoped roles" do 33 | it { subject.has_all_roles?({ :name => "manager".send(param_method), :resource => Forum.first }, { :name => "manager".send(param_method), :resource => Forum.last }).should be_truthy } 34 | it { subject.has_all_roles?({ :name => "manager".send(param_method), :resource => Group }, { :name => "moderator".send(param_method), :resource => Forum.first }).should be_falsey } 35 | it { subject.has_all_roles?({ :name => "manager".send(param_method), :resource => Forum.first }, { :name => "moderator".send(param_method), :resource => Forum }).should be_falsey } 36 | it { subject.has_all_roles?({ :name => "manager".send(param_method), :resource => Forum.last }, { :name => "warrior".send(param_method), :resource => Forum.last }).should be_truthy } 37 | end 38 | end 39 | 40 | context "with a instance scoped role", :scope => :instance do 41 | context "on instance scoped roles only" do 42 | it { subject.has_all_roles?({ :name => "moderator".send(param_method), :resource => :any }, { :name => "anonymous".send(param_method), :resource => Forum.last }).should be_truthy } 43 | it { subject.has_all_roles?({ :name => "moderator".send(param_method), :resource => :any }, { :name => "anonymous".send(param_method), :resource => :any }).should be_truthy } 44 | it { subject.has_all_roles?({ :name => "moderator".send(param_method), :resource => :any }, { :name => "anonymous".send(param_method), :resource => Forum }).should be_falsey } 45 | it { subject.has_all_roles?({ :name => "moderator".send(param_method), :resource => Forum.first }, { :name => "anonymous".send(param_method), :resource => Forum.last }).should be_truthy } 46 | it { subject.has_all_roles?({ :name => "moderator".send(param_method), :resource => Forum.first }, { :name => "moderator".send(param_method), :resource => Forum.last }).should be_falsey } 47 | it { subject.has_all_roles?({ :name => "moderator".send(param_method), :resource => Forum.first }, { :name => "dummy".send(param_method), :resource => Forum.last }).should be_falsey } 48 | it { subject.has_all_roles?({ :name => "dummy".send(param_method), :resource => Forum.first }, { :name => "dumber".send(param_method), :resource => Forum.last }).should be_falsey } 49 | end 50 | 51 | context "on mixed scoped roles" do 52 | it { subject.has_all_roles?({ :name => "visitor".send(param_method), :resource => Forum.last }).should be_truthy } 53 | it { subject.has_all_roles?("soldier".send(param_method), { :name => "moderator".send(param_method), :resource => Forum.first }, { :name => "visitor".send(param_method), :resource => Forum }).should be(true) } 54 | it { subject.has_all_roles?("soldier".send(param_method), { :name => "moderator".send(param_method), :resource => Forum.last }, { :name => "visitor".send(param_method), :resource => Forum }).should be(false) } 55 | it { subject.has_all_roles?("soldier".send(param_method), { :name => "moderator".send(param_method), :resource => :any }, { :name => "visitor".send(param_method), :resource => Forum }).should be(true) } 56 | it { subject.has_all_roles?("soldier".send(param_method), { :name => "moderator".send(param_method), :resource => :any }, { :name => "visitor".send(param_method), :resource => :any }).should be(true) } 57 | it { subject.has_all_roles?("soldier".send(param_method), { :name => "moderator".send(param_method), :resource => Forum.first }, { :name => "visitor".send(param_method), :resource => Group }).should be(false) } 58 | it { subject.has_all_roles?("soldier".send(param_method), { :name => "moderator".send(param_method), :resource => Forum.first }, { :name => "visitor".send(param_method), :resource => Group.first }).should be(false) } 59 | it { subject.has_all_roles?({ :name => "soldier".send(param_method), :resource => Forum }, { :name => "moderator".send(param_method), :resource => Forum.first }, { :name => "visitor".send(param_method), :resource => Forum }).should be(true) } 60 | it { subject.has_all_roles?({ :name => "soldier".send(param_method), :resource => Forum.first }, { :name => "moderator".send(param_method), :resource => Forum.first }, { :name => "visitor".send(param_method), :resource => Forum }).should be(true) } 61 | it { subject.has_all_roles?("soldier".send(param_method), { :name => "moderator".send(param_method), :resource => Forum.first }, { :name => "visitor".send(param_method), :resource => Forum.first }).should be(true) } 62 | it { subject.has_all_roles?("dummy".send(param_method), { :name => "dumber".send(param_method), :resource => Forum.last }, { :name => "dumberer".send(param_method), :resource => Forum }).should be(false) } 63 | it { subject.has_all_roles?("soldier".send(param_method), "dummy".send(param_method), { :name => "dumber".send(param_method), :resource => Forum.last }, { :name => "dumberer".send(param_method), :resource => Forum }).should be(false) } 64 | it { subject.has_all_roles?({ :name => "manager".send(param_method), :resource => Forum.last }, "dummy".send(param_method), { :name => "dumber".send(param_method), :resource => Forum.last }, { :name => "dumberer".send(param_method), :resource => Forum }).should be(false) } 65 | it { subject.has_all_roles?("soldier".send(param_method), { :name => "dumber".send(param_method), :resource => Forum.last }, { :name => "manager".send(param_method), :resource => Forum.last }).should be(false) } 66 | it { subject.has_all_roles?({ :name => "soldier".send(param_method), :resource => Forum.first }, { :name => "moderator".send(param_method), :resource => Forum.first }, { :name => "visitor".send(param_method), :resource => Forum.last }).should be(true) } 67 | it { subject.has_all_roles?({ :name => "soldier".send(param_method), :resource => Forum.first }, { :name => "moderator".send(param_method), :resource => :any }, { :name => "visitor".send(param_method), :resource => Forum.last }).should be(true) } 68 | end 69 | end 70 | end 71 | end -------------------------------------------------------------------------------- /spec/rolify/shared_examples/shared_examples_for_has_any_role.rb: -------------------------------------------------------------------------------- 1 | shared_examples_for "#has_any_role?_examples" do |param_name, param_method| 2 | context "using #{param_name} as parameter" do 3 | context "with a global role", :scope => :global do 4 | before do 5 | subject.add_role "staff".send(param_method) 6 | end 7 | 8 | it { subject.has_any_role?("staff".send(param_method)).should be_truthy } 9 | it { subject.has_any_role?("admin".send(param_method), "staff".send(param_method)).should be_truthy } 10 | it { subject.has_any_role?("admin".send(param_method), "moderator".send(param_method)).should be_truthy } 11 | it { subject.has_any_role?("dummy".send(param_method), "dumber".send(param_method)).should be_falsey } 12 | it { subject.has_any_role?({ :name => "admin".send(param_method), :resource => Forum }, { :name => "admin".send(param_method), :resource => Group }).should be_truthy } 13 | it { subject.has_any_role?({ :name => "admin".send(param_method), :resource => :any }, { :name => "admin".send(param_method), :resource => Group }).should be_truthy } 14 | it { subject.has_any_role?({ :name => "admin".send(param_method), :resource => Forum }, { :name => "staff".send(param_method), :resource => Group.last }).should be_truthy } 15 | it { subject.has_any_role?({ :name => "admin".send(param_method), :resource => Forum.first }, { :name => "admin".send(param_method), :resource => Forum.last }).should be_truthy } 16 | it { subject.has_any_role?({ :name => "admin".send(param_method), :resource => Forum.first }, { :name => "dummy".send(param_method), :resource => Forum.last }).should be_truthy } 17 | it { subject.has_any_role?({ :name => "admin".send(param_method), :resource => Forum.first }, { :name => "dummy".send(param_method), :resource => :any }).should be_truthy } 18 | it { subject.has_any_role?({ :name => "dummy".send(param_method), :resource => Forum.first }, { :name => "dumber".send(param_method), :resource => :any }).should be_falsey } 19 | it { subject.has_any_role?({ :name => "dummy".send(param_method), :resource => :any }, { :name => "dumber".send(param_method), :resource => :any }).should be_falsey } 20 | end 21 | 22 | context "with a class scoped role", :scope => :class do 23 | before do 24 | subject.add_role "player".send(param_method), Forum 25 | subject.add_role "superhero".send(param_method) 26 | end 27 | 28 | it { subject.has_any_role?({ :name => "player".send(param_method), :resource => Forum }).should be_truthy } 29 | it { subject.has_any_role?({ :name => "manager".send(param_method), :resource => Forum }, { :name => "player".send(param_method), :resource => Forum }).should be_truthy } 30 | it { subject.has_any_role?({ :name => "manager".send(param_method), :resource => Forum }, { :name => "player".send(param_method), :resource => :any }).should be_truthy } 31 | it { subject.has_any_role?({ :name => "manager".send(param_method), :resource => Forum }, { :name => "player".send(param_method), :resource => :any }).should be_truthy } 32 | it { subject.has_any_role?({ :name => "manager".send(param_method), :resource => :any }, { :name => "player".send(param_method), :resource => :any }).should be_truthy } 33 | it { subject.has_any_role?({ :name => "manager".send(param_method), :resource => Forum }, { :name => "dummy".send(param_method), :resource => Forum }).should be_truthy } 34 | it { subject.has_any_role?({ :name => "manager".send(param_method), :resource => Forum }, { :name => "dummy".send(param_method), :resource => :any }).should be_truthy } 35 | it { subject.has_any_role?({ :name => "dummy".send(param_method), :resource => Forum }, { :name => "dumber".send(param_method), :resource => Group }).should be_falsey } 36 | it { subject.has_any_role?({ :name => "manager".send(param_method), :resource => Forum.first }, { :name => "manager".send(param_method), :resource => Forum.last }).should be_truthy } 37 | it { subject.has_any_role?({ :name => "manager".send(param_method), :resource => Group }, { :name => "moderator".send(param_method), :resource => Forum.first }).should be_falsey } 38 | it { subject.has_any_role?({ :name => "manager".send(param_method), :resource => Forum.first }, { :name => "moderator".send(param_method), :resource => Forum }).should be_truthy } 39 | it { subject.has_any_role?({ :name => "manager".send(param_method), :resource => Forum.last }, { :name => "warrior".send(param_method), :resource => Forum.last }).should be_truthy } 40 | end 41 | 42 | context "with a instance scoped role", :scope => :instance do 43 | before do 44 | subject.add_role "visitor".send(param_method), Forum.last 45 | subject.add_role "leader", Group 46 | subject.add_role "warrior" 47 | end 48 | 49 | it { subject.has_any_role?({ :name => "visitor", :resource => Forum.last }).should be_truthy } 50 | it { subject.has_any_role?({ :name => "moderator", :resource => Forum.first }, { :name => "visitor", :resource => Forum.last }).should be_truthy } 51 | it { subject.has_any_role?({ :name => "moderator", :resource => :any }, { :name => "visitor", :resource => Forum.last }).should be_truthy } 52 | it { subject.has_any_role?({ :name => "moderator", :resource => :any }, { :name => "visitor", :resource => :any}).should be_truthy } 53 | it { subject.has_any_role?({ :name => "moderator", :resource => Forum }, { :name => "visitor", :resource => :any }).should be_truthy } 54 | it { subject.has_any_role?({ :name => "moderator", :resource => Forum.first }, { :name => "moderator", :resource => Forum.last }).should be_truthy } 55 | it { subject.has_any_role?({ :name => "moderator", :resource => Forum.first }, { :name => "dummy", :resource => Forum.last }).should be_truthy } 56 | it { subject.has_any_role?({ :name => "dummy", :resource => Forum.first }, { :name => "dumber", :resource => Forum.last }).should be_falsey } 57 | it { subject.has_any_role?("warrior", { :name => "moderator", :resource => Forum.first }, { :name => "leader", :resource => Group }).should be(true) } 58 | it { subject.has_any_role?("warrior", { :name => "moderator", :resource => :any }, { :name => "leader", :resource => Forum }).should be(true) } 59 | it { subject.has_any_role?("warrior", { :name => "moderator", :resource => Forum.first }, { :name => "leader", :resource => :any }).should be(true) } 60 | it { subject.has_any_role?("warrior", { :name => "moderator", :resource => :any }, { :name => "leader", :resource => :any }).should be(true) } 61 | it { subject.has_any_role?("warrior", { :name => "moderator", :resource => Forum.last }, { :name => "leader", :resource => Forum }).should be(true) } 62 | it { subject.has_any_role?("warrior", { :name => "moderator", :resource => Forum.last }, { :name => "leader", :resource => Group }).should be(true) } 63 | it { subject.has_any_role?("warrior", { :name => "moderator", :resource => Forum.last }, { :name => "leader", :resource => Group.first }).should be(true) } 64 | it { subject.has_any_role?({ :name => "warrior", :resource => Forum }, { :name => "moderator", :resource => Forum.last }, { :name => "leader", :resource => Forum }).should be(true) } 65 | it { subject.has_any_role?({ :name => "warrior", :resource => Forum.first }, { :name => "moderator", :resource => Forum.last }, { :name => "leader", :resource => Forum }).should be(true) } 66 | it { subject.has_any_role?("warrior", { :name => "moderator", :resource => Forum.last }, { :name => "leader", :resource => Forum.first }).should be(true) } 67 | it { subject.has_any_role?("dummy", { :name => "dumber", :resource => Forum.last }, { :name => "dumberer", :resource => Forum }).should be(false) } 68 | it { subject.has_any_role?({ :name => "leader", :resource => Group.last }, "dummy", { :name => "dumber", :resource => Forum.last }, { :name => "dumberer", :resource => Forum }).should be(true) } 69 | end 70 | end 71 | end -------------------------------------------------------------------------------- /spec/rolify/shared_examples/shared_examples_for_has_role.rb: -------------------------------------------------------------------------------- 1 | shared_examples_for "#has_role?_examples" do |param_name, param_method| 2 | context "using #{param_name} as parameter" do 3 | context "with a global role", :scope => :global do 4 | it { subject.has_role?("admin".send(param_method)).should be_truthy } 5 | 6 | it { subject.has_cached_role?("admin".send(param_method)).should be_truthy } 7 | 8 | context "on resource request" do 9 | it { subject.has_role?("admin".send(param_method), Forum.first).should be_truthy } 10 | it { subject.has_role?("admin".send(param_method), Forum).should be_truthy } 11 | it { subject.has_role?("admin".send(param_method), :any).should be_truthy } 12 | 13 | it { subject.has_cached_role?("admin".send(param_method), Forum.first).should be_truthy } 14 | it { subject.has_cached_role?("admin".send(param_method), Forum).should be_truthy } 15 | it { subject.has_cached_role?("admin".send(param_method), :any).should be_truthy } 16 | end 17 | 18 | context "with another global role" do 19 | before(:all) { role_class.create(:name => "global") } 20 | 21 | it { subject.has_role?("global".send(param_method)).should be_falsey } 22 | it { subject.has_role?("global".send(param_method), :any).should be_falsey } 23 | 24 | it { subject.has_cached_role?("global".send(param_method)).should be_falsey } 25 | it { subject.has_cached_role?("global".send(param_method), :any).should be_falsey } 26 | end 27 | 28 | it "should not get an instance scoped role" do 29 | subject.has_role?("moderator".send(param_method), Group.first).should be_falsey 30 | 31 | subject.has_cached_role?("moderator".send(param_method), Group.first).should be_falsey 32 | end 33 | 34 | it "should not get a class scoped role" do 35 | subject.has_role?("manager".send(param_method), Forum).should be_falsey 36 | 37 | subject.has_cached_role?("manager".send(param_method), Forum).should be_falsey 38 | end 39 | 40 | context "using inexisting role" do 41 | it { subject.has_role?("dummy".send(param_method)).should be_falsey } 42 | it { subject.has_role?("dumber".send(param_method), Forum.first).should be_falsey } 43 | 44 | it { subject.has_cached_role?("dummy".send(param_method)).should be_falsey } 45 | it { subject.has_cached_role?("dumber".send(param_method), Forum.first).should be_falsey } 46 | end 47 | end 48 | 49 | context "with a class scoped role", :scope => :class do 50 | context "on class scoped role request" do 51 | it { subject.has_role?("manager".send(param_method), Forum).should be_truthy } 52 | it { subject.has_role?("manager".send(param_method), Forum.first).should be_truthy } 53 | it { subject.has_role?("manager".send(param_method), :any).should be_truthy } 54 | 55 | it { subject.has_cached_role?("manager".send(param_method), Forum).should be_truthy } 56 | it { subject.has_cached_role?("manager".send(param_method), Forum.first).should be_truthy } 57 | it { subject.has_cached_role?("manager".send(param_method), :any).should be_truthy } 58 | end 59 | 60 | it "should not get a scoped role when asking for a global" do 61 | subject.has_role?("manager".send(param_method)).should be_falsey 62 | 63 | subject.has_cached_role?("manager".send(param_method)).should be_falsey 64 | end 65 | 66 | it "should not get a global role" do 67 | role_class.create(:name => "admin") 68 | subject.has_role?("admin".send(param_method)).should be_falsey 69 | 70 | subject.has_cached_role?("admin".send(param_method)).should be_falsey 71 | end 72 | 73 | context "with another class scoped role" do 74 | context "on the same resource but with a different name" do 75 | before(:all) { role_class.create(:name => "member", :resource_type => "Forum") } 76 | 77 | it { subject.has_role?("member".send(param_method), Forum).should be_falsey } 78 | it { subject.has_role?("member".send(param_method), :any).should be_falsey } 79 | 80 | it { subject.has_cached_role?("member".send(param_method), Forum).should be_falsey } 81 | it { subject.has_cached_role?("member".send(param_method), :any).should be_falsey } 82 | end 83 | 84 | context "on another resource with the same name" do 85 | before(:all) { role_class.create(:name => "manager", :resource_type => "Group") } 86 | 87 | it { subject.has_role?("manager".send(param_method), Group).should be_falsey } 88 | it { subject.has_role?("manager".send(param_method), :any).should be_truthy } 89 | 90 | it { subject.has_cached_role?("manager".send(param_method), Group).should be_falsey } 91 | it { subject.has_cached_role?("manager".send(param_method), :any).should be_truthy } 92 | end 93 | 94 | context "on another resource with another name" do 95 | before(:all) { role_class.create(:name => "defenders", :resource_type => "Group") } 96 | 97 | it { subject.has_role?("defenders".send(param_method), Group).should be_falsey } 98 | it { subject.has_role?("defenders".send(param_method), :any).should be_falsey } 99 | 100 | it { subject.has_cached_role?("defenders".send(param_method), Group).should be_falsey } 101 | it { subject.has_cached_role?("defenders".send(param_method), :any).should be_falsey } 102 | end 103 | end 104 | 105 | context "using inexisting role" do 106 | it { subject.has_role?("dummy".send(param_method), Forum).should be_falsey } 107 | it { subject.has_role?("dumber".send(param_method)).should be_falsey } 108 | 109 | it { subject.has_cached_role?("dummy".send(param_method), Forum).should be_falsey } 110 | it { subject.has_cached_role?("dumber".send(param_method)).should be_falsey } 111 | end 112 | end 113 | 114 | context "with a instance scoped role", :scope => :instance do 115 | context "on instance scoped role request" do 116 | it { subject.has_role?("moderator".send(param_method), Forum.first).should be_truthy } 117 | it { subject.has_role?("moderator".send(param_method), :any).should be_truthy } 118 | it { 119 | m = subject.class.new 120 | m.add_role("moderator", Forum.first) 121 | m.has_role?("moderator".send(param_method), :any).should be_truthy 122 | } 123 | 124 | it { subject.has_cached_role?("moderator".send(param_method), Forum.first).should be_truthy } 125 | it { subject.has_cached_role?("moderator".send(param_method), :any).should be_truthy } 126 | it { 127 | m = subject.class.new 128 | m.add_role("moderator", Forum.first) 129 | m.has_cached_role?("moderator".send(param_method), :any).should be_truthy 130 | } 131 | end 132 | 133 | it "should not get an instance scoped role when asking for a global" do 134 | subject.has_role?("moderator".send(param_method)).should be_falsey 135 | 136 | subject.has_cached_role?("moderator".send(param_method)).should be_falsey 137 | end 138 | 139 | it "should not get an instance scoped role when asking for a class scoped" do 140 | subject.has_role?("moderator".send(param_method), Forum).should be_falsey 141 | 142 | subject.has_cached_role?("moderator".send(param_method), Forum).should be_falsey 143 | end 144 | 145 | it "should not get a global role" do 146 | role_class.create(:name => "admin") 147 | subject.has_role?("admin".send(param_method)).should be_falsey 148 | 149 | subject.has_cached_role?("admin".send(param_method)).should be_falsey 150 | end 151 | 152 | context "with another instance scoped role" do 153 | context "on the same resource but with a different role name" do 154 | before(:all) { role_class.create(:name => "member", :resource => Forum.first) } 155 | 156 | it { subject.has_role?("member".send(param_method), Forum.first).should be_falsey } 157 | it { subject.has_role?("member".send(param_method), :any).should be_falsey } 158 | 159 | it { subject.has_cached_role?("member".send(param_method), Forum.first).should be_falsey } 160 | it { subject.has_cached_role?("member".send(param_method), :any).should be_falsey } 161 | end 162 | 163 | context "on another resource of the same type but with the same role name" do 164 | before(:all) { role_class.create(:name => "moderator", :resource => Forum.last) } 165 | 166 | it { subject.has_role?("moderator".send(param_method), Forum.last).should be_falsey } 167 | it { subject.has_role?("moderator".send(param_method), :any).should be_truthy } 168 | 169 | it { subject.has_cached_role?("moderator".send(param_method), Forum.last).should be_falsey } 170 | it { subject.has_cached_role?("moderator".send(param_method), :any).should be_truthy } 171 | end 172 | 173 | context "on another resource of different type but with the same role name" do 174 | before(:all) { role_class.create(:name => "moderator", :resource => Group.last) } 175 | 176 | it { subject.has_role?("moderator".send(param_method), Group.last).should be_falsey } 177 | it { subject.has_role?("moderator".send(param_method), :any).should be_truthy } 178 | 179 | it { subject.has_cached_role?("moderator".send(param_method), Group.last).should be_falsey } 180 | it { subject.has_cached_role?("moderator".send(param_method), :any).should be_truthy } 181 | end 182 | 183 | context "on another resource of the same type and with another role name" do 184 | before(:all) { role_class.create(:name => "member", :resource => Forum.last) } 185 | 186 | it { subject.has_role?("member".send(param_method), Forum.last).should be_falsey } 187 | it { subject.has_role?("member".send(param_method), :any).should be_falsey } 188 | 189 | it { subject.has_cached_role?("member".send(param_method), Forum.last).should be_falsey } 190 | it { subject.has_cached_role?("member".send(param_method), :any).should be_falsey } 191 | end 192 | 193 | context "on another resource of different type and with another role name" do 194 | before(:all) { role_class.create(:name => "member", :resource => Group.first) } 195 | 196 | it { subject.has_role?("member".send(param_method), Group.first).should be_falsey } 197 | it { subject.has_role?("member".send(param_method), :any).should be_falsey } 198 | 199 | it { subject.has_cached_role?("member".send(param_method), Group.first).should be_falsey } 200 | it { subject.has_cached_role?("member".send(param_method), :any).should be_falsey } 201 | end 202 | end 203 | end 204 | end 205 | end 206 | -------------------------------------------------------------------------------- /spec/rolify/shared_examples/shared_examples_for_only_has_role.rb: -------------------------------------------------------------------------------- 1 | shared_examples_for "#only_has_role?_examples" do |param_name, param_method| 2 | context "using #{param_name} as parameter" do 3 | context "with a global role", :scope => :global do 4 | subject do 5 | user = User.create(:login => "global_user") 6 | user.add_role "global_role".send(param_method) 7 | user 8 | end 9 | 10 | it { subject.only_has_role?("global_role".send(param_method)).should be_truthy } 11 | 12 | context "on resource request" do 13 | it { subject.only_has_role?("global_role".send(param_method), Forum.first).should be_truthy } 14 | it { subject.only_has_role?("global_role".send(param_method), Forum).should be_truthy } 15 | it { subject.only_has_role?("global_role".send(param_method), :any).should be_truthy } 16 | end 17 | 18 | context "with another global role" do 19 | before(:all) { role_class.create(:name => "another_global_role") } 20 | 21 | it { subject.only_has_role?("another_global_role".send(param_method)).should be_falsey } 22 | it { subject.only_has_role?("another_global_role".send(param_method), :any).should be_falsey } 23 | end 24 | 25 | it "should not get an instance scoped role" do 26 | subject.only_has_role?("moderator".send(param_method), Group.first).should be_falsey 27 | end 28 | 29 | it "should not get a class scoped role" do 30 | subject.only_has_role?("manager".send(param_method), Forum).should be_falsey 31 | end 32 | 33 | context "using inexisting role" do 34 | it { subject.only_has_role?("dummy".send(param_method)).should be_falsey } 35 | it { subject.only_has_role?("dumber".send(param_method), Forum.first).should be_falsey } 36 | end 37 | 38 | context "with multiple roles" do 39 | before { subject.add_role "multiple_global_roles".send(param_method) } 40 | 41 | it { subject.only_has_role?("global_role".send(param_method)).should be_falsey } 42 | end 43 | end 44 | 45 | context "with a class scoped role", :scope => :class do 46 | subject do 47 | user = User.create(:login => "class_user") 48 | user.add_role "class_role".send(param_method), Forum 49 | user 50 | end 51 | 52 | context "on class scoped role request" do 53 | it { subject.only_has_role?("class_role".send(param_method), Forum).should be_truthy } 54 | it { subject.only_has_role?("class_role".send(param_method), Forum.first).should be_truthy } 55 | it { subject.only_has_role?("class_role".send(param_method), :any).should be_truthy } 56 | end 57 | 58 | it "should not get a scoped role when asking for a global" do 59 | subject.only_has_role?("class_role".send(param_method)).should be_falsey 60 | end 61 | 62 | it "should not get a global role" do 63 | role_class.create(:name => "global_role") 64 | subject.only_has_role?("global_role".send(param_method)).should be_falsey 65 | end 66 | 67 | context "with another class scoped role" do 68 | context "on the same resource but with a different name" do 69 | before(:all) { role_class.create(:name => "another_class_role", :resource_type => "Forum") } 70 | 71 | it { subject.only_has_role?("another_class_role".send(param_method), Forum).should be_falsey } 72 | it { subject.only_has_role?("another_class_role".send(param_method), :any).should be_falsey } 73 | end 74 | 75 | context "on another resource with the same name" do 76 | before(:all) { role_class.create(:name => "class_role", :resource_type => "Group") } 77 | 78 | it { subject.only_has_role?("class_role".send(param_method), Group).should be_falsey } 79 | it { subject.only_has_role?("class_role".send(param_method), :any).should be_truthy } 80 | end 81 | 82 | context "on another resource with another name" do 83 | before(:all) { role_class.create(:name => "another_class_role", :resource_type => "Group") } 84 | 85 | it { subject.only_has_role?("another_class_role".send(param_method), Group).should be_falsey } 86 | it { subject.only_has_role?("another_class_role".send(param_method), :any).should be_falsey } 87 | end 88 | end 89 | 90 | context "using inexisting role" do 91 | it { subject.only_has_role?("dummy".send(param_method), Forum).should be_falsey } 92 | it { subject.only_has_role?("dumber".send(param_method)).should be_falsey } 93 | end 94 | 95 | context "with multiple roles" do 96 | before { subject.add_role "multiple_class_roles".send(param_method) } 97 | 98 | it { subject.only_has_role?("class_role".send(param_method), Forum).should be_falsey } 99 | it { subject.only_has_role?("class_role".send(param_method), Forum.first).should be_falsey } 100 | it { subject.only_has_role?("class_role".send(param_method), :any).should be_falsey } 101 | end 102 | end 103 | 104 | context "with a instance scoped role", :scope => :instance do 105 | subject do 106 | user = User.create(:login => "instance_user") 107 | user.add_role "instance_role".send(param_method), Forum.first 108 | user 109 | end 110 | 111 | context "on instance scoped role request" do 112 | it { subject.only_has_role?("instance_role".send(param_method), Forum.first).should be_truthy } 113 | it { subject.only_has_role?("instance_role".send(param_method), :any).should be_truthy } 114 | end 115 | 116 | it "should not get an instance scoped role when asking for a global" do 117 | subject.only_has_role?("instance_role".send(param_method)).should be_falsey 118 | end 119 | 120 | it "should not get an instance scoped role when asking for a class scoped" do 121 | subject.only_has_role?("instance_role".send(param_method), Forum).should be_falsey 122 | end 123 | 124 | it "should not get a global role" do 125 | role_class.create(:name => "global_role") 126 | subject.only_has_role?("global_role".send(param_method)).should be_falsey 127 | end 128 | 129 | context "with another instance scoped role" do 130 | context "on the same resource but with a different role name" do 131 | before(:all) { role_class.create(:name => "another_instance_role", :resource => Forum.first) } 132 | 133 | it { subject.only_has_role?("another_instance_role".send(param_method), Forum.first).should be_falsey } 134 | it { subject.only_has_role?("another_instance_role".send(param_method), :any).should be_falsey } 135 | end 136 | 137 | context "on another resource of the same type but with the same role name" do 138 | before(:all) { role_class.create(:name => "moderator", :resource => Forum.last) } 139 | 140 | it { subject.only_has_role?("instance_role".send(param_method), Forum.last).should be_falsey } 141 | it { subject.only_has_role?("instance_role".send(param_method), :any).should be_truthy } 142 | end 143 | 144 | context "on another resource of different type but with the same role name" do 145 | before(:all) { role_class.create(:name => "moderator", :resource => Group.last) } 146 | 147 | it { subject.only_has_role?("instance_role".send(param_method), Group.last).should be_falsey } 148 | it { subject.only_has_role?("instance_role".send(param_method), :any).should be_truthy } 149 | end 150 | 151 | context "on another resource of the same type and with another role name" do 152 | before(:all) { role_class.create(:name => "another_instance_role", :resource => Forum.last) } 153 | 154 | it { subject.only_has_role?("another_instance_role".send(param_method), Forum.last).should be_falsey } 155 | it { subject.only_has_role?("another_instance_role".send(param_method), :any).should be_falsey } 156 | end 157 | 158 | context "on another resource of different type and with another role name" do 159 | before(:all) { role_class.create(:name => "another_instance_role", :resource => Group.first) } 160 | 161 | it { subject.only_has_role?("another_instance_role".send(param_method), Group.first).should be_falsey } 162 | it { subject.only_has_role?("another_instance_role".send(param_method), :any).should be_falsey } 163 | end 164 | end 165 | 166 | context "with multiple roles" do 167 | before { subject.add_role "multiple_instance_roles".send(param_method), Forum.first } 168 | 169 | it { subject.only_has_role?("instance_role".send(param_method), Forum.first).should be_falsey } 170 | it { subject.only_has_role?("instance_role".send(param_method), :any).should be_falsey } 171 | end 172 | end 173 | end 174 | end -------------------------------------------------------------------------------- /spec/rolify/shared_examples/shared_examples_for_remove_role.rb: -------------------------------------------------------------------------------- 1 | shared_examples_for "#remove_role_examples" do |param_name, param_method| 2 | context "using #{param_name} as parameter" do 3 | context "removing a global role", :scope => :global do 4 | context "being a global role of the user" do 5 | it { expect { subject.remove_role("admin".send(param_method)) }.to change { subject.roles.size }.by(-1) } 6 | 7 | it { should_not have_role("admin".send(param_method)) } 8 | end 9 | 10 | context "being a class scoped role to the user" do 11 | it { expect { subject.remove_role("manager".send(param_method)) }.to change { subject.roles.size }.by(-1) } 12 | 13 | it { should_not have_role("manager".send(param_method), Group) } 14 | end 15 | 16 | context "being instance scoped roles to the user" do 17 | it { expect { subject.remove_role("moderator".send(param_method)) }.to change { subject.roles.size }.by(-2) } 18 | 19 | it { should_not have_role("moderator".send(param_method), Forum.last) } 20 | it { should_not have_role("moderator".send(param_method), Group.last) } 21 | end 22 | 23 | context "not being a role of the user" do 24 | it { expect { subject.remove_role("superhero".send(param_method)) }.not_to change { subject.roles.size } } 25 | end 26 | 27 | context "used by another user" do 28 | before do 29 | user = user_class.last 30 | user.add_role "staff".send(param_method) 31 | end 32 | 33 | it { expect { subject.remove_role("staff".send(param_method)) }.not_to change { role_class.count } } 34 | 35 | it { should_not have_role("staff".send(param_method)) } 36 | end 37 | 38 | context "not used by anyone else" do 39 | before do 40 | subject.add_role "nobody".send(param_method) 41 | end 42 | 43 | it { expect { subject.remove_role("nobody".send(param_method)) }.to change { role_class.count }.by(-1) } 44 | end 45 | end 46 | 47 | context "removing a class scoped role", :scope => :class do 48 | context "being a global role of the user" do 49 | it { expect { subject.remove_role("warrior".send(param_method), Forum) }.not_to change{ subject.roles.size } } 50 | end 51 | 52 | context "being a class scoped role to the user" do 53 | it { expect { subject.remove_role("manager".send(param_method), Forum) }.to change{ subject.roles.size }.by(-1) } 54 | 55 | it { should_not have_role("manager", Forum) } 56 | end 57 | 58 | context "being instance scoped role to the user" do 59 | it { expect { subject.remove_role("moderator".send(param_method), Forum) }.to change { subject.roles.size }.by(-1) } 60 | 61 | it { should_not have_role("moderator".send(param_method), Forum.last) } 62 | it { should have_role("moderator".send(param_method), Group.last) } 63 | end 64 | 65 | context "not being a role of the user" do 66 | it { expect { subject.remove_role("manager".send(param_method), Group) }.not_to change { subject.roles.size } } 67 | end 68 | end 69 | 70 | context "removing a instance scoped role", :scope => :instance do 71 | context "being a global role of the user" do 72 | it { expect { subject.remove_role("soldier".send(param_method), Group.first) }.not_to change { subject.roles.size } } 73 | end 74 | 75 | context "being a class scoped role to the user" do 76 | it { expect { subject.remove_role("visitor".send(param_method), Forum.first) }.not_to change { subject.roles.size } } 77 | end 78 | 79 | context "being instance scoped role to the user" do 80 | it { expect { subject.remove_role("moderator".send(param_method), Forum.first) }.to change { subject.roles.size }.by(-1) } 81 | 82 | it { should_not have_role("moderator", Forum.first) } 83 | end 84 | 85 | context "not being a role of the user" do 86 | it { expect { subject.remove_role("anonymous".send(param_method), Forum.first) }.not_to change { subject.roles.size } } 87 | end 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /spec/rolify/shared_examples/shared_examples_for_roles.rb: -------------------------------------------------------------------------------- 1 | require "rolify/shared_contexts" 2 | require "rolify/shared_examples/shared_examples_for_add_role" 3 | require "rolify/shared_examples/shared_examples_for_has_role" 4 | require "rolify/shared_examples/shared_examples_for_only_has_role" 5 | require "rolify/shared_examples/shared_examples_for_has_all_roles" 6 | require "rolify/shared_examples/shared_examples_for_has_any_role" 7 | require "rolify/shared_examples/shared_examples_for_remove_role" 8 | require "rolify/shared_examples/shared_examples_for_finders" 9 | 10 | 11 | shared_examples_for Rolify::Role do 12 | before(:all) do 13 | reset_defaults 14 | Rolify.dynamic_shortcuts = false 15 | rolify_options = { :role_cname => role_class.to_s } 16 | rolify_options[:role_join_table_name] = join_table if defined? join_table 17 | silence_warnings { user_class.rolify rolify_options } 18 | role_class.destroy_all 19 | Forum.resourcify :roles, :role_cname => role_class.to_s 20 | Group.resourcify :roles, :role_cname => role_class.to_s 21 | Organization.resourcify :roles, :role_cname => role_class.to_s 22 | end 23 | 24 | context "in the Instance level" do 25 | before(:all) do 26 | admin = user_class.first 27 | admin.add_role :admin 28 | admin.add_role :moderator, Forum.first 29 | end 30 | 31 | subject { user_class.first } 32 | 33 | [ :grant, :add_role ].each do |method_alias| 34 | it { should respond_to(method_alias.to_sym).with(1).arguments } 35 | it { should respond_to(method_alias.to_sym).with(2).arguments } 36 | end 37 | 38 | it { should respond_to(:has_role?).with(1).arguments } 39 | it { should respond_to(:has_role?).with(2).arguments } 40 | 41 | it { should respond_to(:has_all_roles?) } 42 | it { should respond_to(:has_all_roles?) } 43 | 44 | it { should respond_to(:has_any_role?) } 45 | it { should respond_to(:has_any_role?) } 46 | 47 | [ :has_no_role, :revoke, :remove_role ].each do |method_alias| 48 | it { should respond_to(method_alias.to_sym).with(1).arguments } 49 | it { should respond_to(method_alias.to_sym).with(2).arguments } 50 | end 51 | 52 | it { should_not respond_to(:is_admin?) } 53 | it { should_not respond_to(:is_moderator_of?) } 54 | 55 | describe "#has_role?" do 56 | it_should_behave_like "#has_role?_examples", "String", :to_s 57 | it_should_behave_like "#has_role?_examples", "Symbol", :to_sym 58 | end 59 | 60 | describe "#only_has_role?" do 61 | it_should_behave_like "#only_has_role?_examples", "String", :to_s 62 | it_should_behave_like "#only_has_role?_examples", "Symbol", :to_sym 63 | end 64 | 65 | describe "#has_all_roles?" do 66 | it_should_behave_like "#has_all_roles?_examples", "String", :to_s 67 | it_should_behave_like "#has_all_roles?_examples", "Symbol", :to_sym 68 | end 69 | 70 | describe "#has_any_role?" do 71 | it_should_behave_like "#has_any_role?_examples", "String", :to_s 72 | it_should_behave_like "#has_any_role?_examples", "Symbol", :to_sym 73 | end 74 | 75 | describe "#has_no_role" do 76 | it_should_behave_like "#remove_role_examples", "String", :to_s 77 | it_should_behave_like "#remove_role_examples", "Symbol", :to_sym 78 | end 79 | end 80 | 81 | context "with a new instance" do 82 | let(:user) { user_class.new } 83 | 84 | before do 85 | user.add_role :admin 86 | user.add_role :moderator, Forum.first 87 | end 88 | 89 | subject { user } 90 | 91 | it { should have_role :admin } 92 | # it { should have_role :admin, Forum } 93 | # it { should have_role :admin, :any } 94 | # it { should have_role :moderator, Forum.first } 95 | # it { should have_role :moderator, :any } 96 | # it { should_not have_role :moderator } 97 | # it { should_not have_role :moderator, Forum } 98 | it { subject.has_any_role?(:admin).should be_truthy } 99 | end 100 | 101 | context "on the Class level ", :scope => :mixed do 102 | it_should_behave_like :finders, "String", :to_s 103 | it_should_behave_like :finders, "Symbol", :to_sym 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /spec/rolify/shared_examples/shared_examples_for_scopes.rb: -------------------------------------------------------------------------------- 1 | require "rolify/shared_contexts" 2 | 3 | shared_examples_for "Role.scopes" do 4 | before do 5 | role_class.destroy_all 6 | end 7 | 8 | subject { user_class.first } 9 | 10 | describe ".global" do 11 | let!(:admin_role) { subject.add_role :admin } 12 | let!(:staff_role) { subject.add_role :staff } 13 | 14 | it { subject.roles.global.should == [ admin_role, staff_role ] } 15 | end 16 | 17 | describe ".class_scoped" do 18 | let!(:manager_role) { subject.add_role :manager, Group } 19 | let!(:moderator_role) { subject.add_role :moderator, Forum } 20 | 21 | it { subject.roles.class_scoped.should =~ [ manager_role, moderator_role ] } 22 | it { subject.roles.class_scoped(Group).should =~ [ manager_role ] } 23 | it { subject.roles.class_scoped(Forum).should =~ [ moderator_role ] } 24 | end 25 | 26 | describe ".instance_scoped" do 27 | let!(:visitor_role) { subject.add_role :visitor, Forum.first } 28 | let!(:zombie_role) { subject.add_role :visitor, Forum.last } 29 | let!(:anonymous_role) { subject.add_role :anonymous, Group.last } 30 | 31 | it { subject.roles.instance_scoped.to_a.entries.should =~ [ visitor_role, zombie_role, anonymous_role ] } 32 | it { subject.roles.instance_scoped(Forum).should =~ [ visitor_role, zombie_role ] } 33 | it { subject.roles.instance_scoped(Forum.first).should =~ [ visitor_role ] } 34 | it { subject.roles.instance_scoped(Forum.last).should =~ [ zombie_role ] } 35 | it { subject.roles.instance_scoped(Group.last).should =~ [ anonymous_role ] } 36 | it { subject.roles.instance_scoped(Group.first).should be_empty } 37 | end 38 | end -------------------------------------------------------------------------------- /spec/rolify/utils_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | describe Rolify::Utils do 4 | class Harness 5 | extend Rolify::Utils 6 | define_method(:new_method) { |*_args| true } 7 | deprecate :old_method, :new_method 8 | end 9 | 10 | let(:harness) { Harness.new } 11 | 12 | context '#deprecate' do 13 | it 'calls new method with same arguments' do 14 | expect(harness).to receive(:warn).once 15 | expect(harness).to receive(:new_method).once.with(1, 2, 3) 16 | harness.old_method(1, 2, 3) 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'coveralls' 2 | Coveralls.wear_merged! 3 | 4 | require 'rubygems' 5 | require "bundler/setup" 6 | 7 | require 'rolify' 8 | require 'rolify/matchers' 9 | require 'rails' 10 | begin 11 | require 'its' 12 | rescue LoadError 13 | end 14 | require 'database_cleaner' 15 | 16 | ENV['ADAPTER'] ||= 'active_record' 17 | 18 | load File.dirname(__FILE__) + "/support/adapters/#{ENV['ADAPTER']}.rb" 19 | load File.dirname(__FILE__) + '/support/data.rb' 20 | 21 | 22 | 23 | def reset_defaults 24 | Rolify.use_defaults 25 | Rolify.use_mongoid if ENV['ADAPTER'] == 'mongoid' 26 | end 27 | 28 | def provision_user(user, roles) 29 | roles.each do |role| 30 | if role.is_a? Array 31 | user.add_role *role 32 | else 33 | user.add_role role 34 | end 35 | end 36 | user 37 | end 38 | 39 | def silence_warnings(&block) 40 | warn_level = $VERBOSE 41 | $VERBOSE = nil 42 | result = block.call 43 | $VERBOSE = warn_level 44 | result 45 | end 46 | 47 | RSpec.configure do |config| 48 | config.expect_with(:rspec) { |c| c.syntax = [:should, :expect] } 49 | 50 | config.before(:suite) do 51 | DatabaseCleaner.strategy = :truncation 52 | DatabaseCleaner.start 53 | end 54 | 55 | config.after(:suite) do |example| 56 | DatabaseCleaner.clean 57 | end 58 | 59 | end 60 | -------------------------------------------------------------------------------- /spec/support/adapters/active_record.rb: -------------------------------------------------------------------------------- 1 | load File.dirname(__FILE__) + '/utils/active_record.rb' 2 | 3 | extend_rspec_with_activerecord_specific_matchers 4 | establish_connection 5 | 6 | ActiveRecord::Base.extend Rolify 7 | 8 | load File.dirname(__FILE__) + '/../schema.rb' 9 | 10 | # Standard user and role classes 11 | class User < ActiveRecord::Base 12 | rolify 13 | end 14 | 15 | class Role < ActiveRecord::Base 16 | has_and_belongs_to_many :users, :join_table => :users_roles 17 | has_and_belongs_to_many :strict_users, :join_table => :strict_users_roles 18 | 19 | belongs_to :resource, :polymorphic => true 20 | 21 | extend Rolify::Adapter::Scopes 22 | end 23 | 24 | # Strict user and role classes 25 | class StrictUser < ActiveRecord::Base 26 | rolify strict: true 27 | end 28 | 29 | # Resourcifed and rolifed at the same time 30 | class HumanResource < ActiveRecord::Base 31 | resourcify :resources 32 | rolify 33 | end 34 | 35 | # Custom role and class names 36 | class Customer < ActiveRecord::Base 37 | rolify :role_cname => "Privilege" 38 | end 39 | 40 | class Privilege < ActiveRecord::Base 41 | has_and_belongs_to_many :customers, :join_table => :customers_privileges 42 | belongs_to :resource, :polymorphic => true 43 | 44 | extend Rolify::Adapter::Scopes 45 | end 46 | 47 | # Namespaced models 48 | module Admin 49 | def self.table_name_prefix 50 | 'admin_' 51 | end 52 | 53 | class Moderator < ActiveRecord::Base 54 | rolify :role_cname => "Admin::Right", :role_join_table_name => "moderators_rights" 55 | end 56 | 57 | class Right < ActiveRecord::Base 58 | has_and_belongs_to_many :moderators, :class_name => "Admin::Moderator", :join_table => "moderators_rights" 59 | belongs_to :resource, :polymorphic => true 60 | 61 | extend Rolify::Adapter::Scopes 62 | end 63 | end 64 | 65 | 66 | # Resources classes 67 | class Forum < ActiveRecord::Base 68 | #resourcify done during specs setup to be able to use custom user classes 69 | end 70 | 71 | class Group < ActiveRecord::Base 72 | #resourcify done during specs setup to be able to use custom user classes 73 | 74 | def subgroups 75 | Group.where(:parent_id => id) 76 | end 77 | end 78 | 79 | class Team < ActiveRecord::Base 80 | #resourcify done during specs setup to be able to use custom user classes 81 | self.primary_key = "team_code" 82 | 83 | default_scope { order(:team_code) } 84 | end 85 | 86 | class Organization < ActiveRecord::Base 87 | 88 | end 89 | 90 | class Company < Organization 91 | 92 | end 93 | -------------------------------------------------------------------------------- /spec/support/adapters/mongoid.rb: -------------------------------------------------------------------------------- 1 | load File.dirname(__FILE__) + '/utils/mongoid.rb' 2 | 3 | load_mongoid_config 4 | 5 | begin 6 | Mongo::Logger.logger.level = ::Logger::FATAL 7 | rescue NameError 8 | end 9 | 10 | ::Mongoid::Document.module_eval do 11 | def self.included(base) 12 | base.extend Rolify 13 | end 14 | end 15 | 16 | Rolify.use_mongoid 17 | 18 | # Standard user and role classes 19 | class User 20 | include Mongoid::Document 21 | default_scope -> { order_by id: 'asc' } 22 | 23 | rolify 24 | 25 | field :login, :type => String 26 | end 27 | 28 | # Standard user and role classes 29 | class StrictUser 30 | include Mongoid::Document 31 | default_scope -> { order_by id: 'asc' } 32 | rolify strict: true 33 | 34 | field :login, :type => String 35 | end 36 | 37 | class Role 38 | include Mongoid::Document 39 | has_and_belongs_to_many :users 40 | has_and_belongs_to_many :strict_users 41 | belongs_to :resource, :polymorphic => true 42 | 43 | field :name, :type => String 44 | index( 45 | { 46 | :name => 1, 47 | :resource_type => 1, 48 | :resource_id => 1 49 | }, 50 | { :unique => true } 51 | ) 52 | 53 | scopify 54 | end 55 | 56 | # Resourcifed and rolifed at the same time 57 | class HumanResource 58 | include Mongoid::Document 59 | default_scope -> { order_by id: 'asc' } 60 | resourcify :resources 61 | rolify 62 | 63 | field :login, :type => String 64 | end 65 | 66 | #class Power 67 | # include Mongoid::Document 68 | # has_and_belongs_to_many :human_resources 69 | # belongs_to :resource, :polymorphic => true 70 | # scopify 71 | # 72 | # field :name, :type => String 73 | # index( 74 | # { 75 | # :name => 1, 76 | # :resource_type => 1, 77 | # :resource_id => 1 78 | # }, 79 | # { :unique => true } 80 | # ) 81 | #end 82 | 83 | # Custom role and class names 84 | class Customer 85 | include Mongoid::Document 86 | default_scope -> { order_by id: 'asc' } 87 | rolify :role_cname => "Privilege" 88 | 89 | field :login, :type => String 90 | end 91 | 92 | class Privilege 93 | include Mongoid::Document 94 | default_scope -> { order_by id: 'asc' } 95 | has_and_belongs_to_many :customers 96 | belongs_to :resource, :polymorphic => true 97 | scopify 98 | 99 | field :name, :type => String 100 | index( 101 | { 102 | :name => 1, 103 | :resource_type => 1, 104 | :resource_id => 1 105 | }, 106 | { :unique => true } 107 | ) 108 | end 109 | 110 | # Namespaced models 111 | module Admin 112 | class Moderator 113 | include Mongoid::Document 114 | default_scope -> { order_by id: 'asc' } 115 | rolify :role_cname => "Admin::Right" 116 | 117 | field :login, :type => String 118 | end 119 | 120 | class Right 121 | include Mongoid::Document 122 | default_scope -> { order_by id: 'asc' } 123 | has_and_belongs_to_many :moderators, :class_name => 'Admin::Moderator' 124 | belongs_to :resource, :polymorphic => true 125 | scopify 126 | 127 | field :name, :type => String 128 | index( 129 | { 130 | :name => 1, 131 | :resource_type => 1, 132 | :resource_id => 1 133 | }, 134 | { :unique => true } 135 | ) 136 | end 137 | end 138 | 139 | # Resources classes 140 | class Forum 141 | include Mongoid::Document 142 | default_scope -> { order_by id: 'asc' } 143 | #resourcify done during specs setup to be able to use custom user classes 144 | 145 | field :name, :type => String 146 | end 147 | 148 | class Group 149 | include Mongoid::Document 150 | default_scope -> { order_by id: 'asc' } 151 | #resourcify done during specs setup to be able to use custom user classes 152 | 153 | field :name, :type => String 154 | field :parent_id, :type => Integer 155 | 156 | def subgroups 157 | Group.in(:parent_id => _id) 158 | end 159 | end 160 | 161 | class Team 162 | include Mongoid::Document 163 | default_scope -> { order_by id: 'asc' } 164 | #resourcify done during specs setup to be able to use custom user classes 165 | 166 | field :team_code, :type => Integer 167 | field :name, :type => String 168 | end 169 | 170 | class Organization 171 | include Mongoid::Document 172 | default_scope -> { order_by id: 'asc' } 173 | end 174 | 175 | class Company < Organization 176 | 177 | end 178 | -------------------------------------------------------------------------------- /spec/support/adapters/mongoid_5.yml: -------------------------------------------------------------------------------- 1 | test: 2 | clients: 3 | default: 4 | database: godfather 5 | hosts: 6 | - localhost:27017 7 | -------------------------------------------------------------------------------- /spec/support/adapters/mongoid_6.yml: -------------------------------------------------------------------------------- 1 | test: 2 | clients: 3 | default: 4 | database: godfather 5 | hosts: 6 | - localhost:27017 7 | -------------------------------------------------------------------------------- /spec/support/adapters/mongoid_7.yml: -------------------------------------------------------------------------------- 1 | test: 2 | clients: 3 | default: 4 | database: godfather 5 | hosts: 6 | - localhost:27017 7 | -------------------------------------------------------------------------------- /spec/support/adapters/utils/active_record.rb: -------------------------------------------------------------------------------- 1 | require 'active_record' 2 | 3 | def establish_connection 4 | ActiveRecord::Base.establish_connection( 5 | :adapter => "sqlite3", 6 | :database => ":memory:" 7 | ) 8 | end 9 | 10 | def extend_rspec_with_activerecord_specific_matchers 11 | RSpec::Matchers::BuiltIn::OperatorMatcher.register(ActiveRecord::Relation, '=~', RSpec::Matchers::BuiltIn::ContainExactly) 12 | end 13 | -------------------------------------------------------------------------------- /spec/support/adapters/utils/mongoid.rb: -------------------------------------------------------------------------------- 1 | require 'mongoid' 2 | 3 | def mongoid_major_version 4 | Mongoid::VERSION.split('.').first.to_i 5 | end 6 | 7 | def mongoid_config 8 | "spec/support/adapters/mongoid_#{mongoid_major_version}.yml" 9 | end 10 | 11 | def load_mongoid_config 12 | Mongoid.load!(mongoid_config, :test) 13 | end 14 | -------------------------------------------------------------------------------- /spec/support/data.rb: -------------------------------------------------------------------------------- 1 | # Users 2 | [ User, Customer, Admin::Moderator, StrictUser ].each do |user| 3 | user.destroy_all 4 | 5 | user.create(:login => "admin") 6 | user.create(:login => "moderator") 7 | user.create(:login => "god") 8 | user.create(:login => "zombie") 9 | end 10 | 11 | # Roles 12 | [ Role, Privilege, Admin::Right ].each do |role| 13 | role.destroy_all 14 | end 15 | 16 | # Resources 17 | Forum.create(:name => "forum 1") 18 | Forum.create(:name => "forum 2") 19 | Forum.create(:name => "forum 3") 20 | 21 | Group.create(:name => "group 1") 22 | Group.create(:name => "group 2") 23 | 24 | Team.create(:team_code => "1", :name => "PSG") 25 | Team.create(:team_code => "2", :name => "MU") 26 | 27 | Organization.create 28 | Company.create 29 | -------------------------------------------------------------------------------- /spec/support/schema.rb: -------------------------------------------------------------------------------- 1 | ActiveRecord::Schema.define do 2 | self.verbose = false 3 | 4 | [ :roles, :privileges, :admin_rights ].each do |table| 5 | create_table(table) do |t| 6 | t.string :name 7 | t.references :resource, :polymorphic => true 8 | 9 | t.timestamps null: false 10 | end 11 | end 12 | 13 | [ :users, :human_resources, :customers, :admin_moderators, :strict_users ].each do |table| 14 | create_table(table) do |t| 15 | t.string :login 16 | end 17 | end 18 | 19 | create_table(:users_roles, :id => false) do |t| 20 | t.references :user 21 | t.references :role 22 | end 23 | 24 | create_table(:strict_users_roles, :id => false) do |t| 25 | t.references :strict_user 26 | t.references :role 27 | end 28 | 29 | create_table(:human_resources_roles, :id => false) do |t| 30 | t.references :human_resource 31 | t.references :role 32 | end 33 | 34 | create_table(:customers_privileges, :id => false) do |t| 35 | t.references :customer 36 | t.references :privilege 37 | end 38 | 39 | create_table(:moderators_rights, :id => false) do |t| 40 | t.references :moderator 41 | t.references :right 42 | end 43 | 44 | create_table(:forums) do |t| 45 | t.string :name 46 | end 47 | 48 | create_table(:groups) do |t| 49 | t.integer :parent_id 50 | t.string :name 51 | end 52 | 53 | create_table(:teams, :id => false) do |t| 54 | t.primary_key :team_code 55 | t.string :name 56 | end 57 | 58 | create_table(:organizations) do |t| 59 | t.string :type 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/support/stream_helpers.rb: -------------------------------------------------------------------------------- 1 | module StreamHelpers 2 | def capture(stream) 3 | stream = stream.to_s 4 | captured_stream = Tempfile.new(stream) 5 | stream_io = eval("$#{stream}") 6 | origin_stream = stream_io.dup 7 | stream_io.reopen(captured_stream) 8 | 9 | yield 10 | 11 | stream_io.rewind 12 | return captured_stream.read 13 | ensure 14 | captured_stream.close 15 | captured_stream.unlink 16 | stream_io.reopen(origin_stream) 17 | end 18 | end 19 | --------------------------------------------------------------------------------