indulgence 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,20 @@
1
+ Copyright 2013 Rob Nichols
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.
@@ -0,0 +1,315 @@
1
+ = Indulgence
2
+
3
+ Yet another permissions gem.
4
+
5
+ In creating Indulgence I wanted a role based permissions tool that did two main
6
+ things:
7
+
8
+ * Determine what permission a user had to do something to an object
9
+ * Filtered a search of objects based on the those permissions
10
+
11
+ It was apparent to me that if 'something' was one of the CRUD actions, it would
12
+ cover all the use cases I could think of. So permissions were sub-divided into
13
+ the 'abilities': create, read, update, and delete.
14
+
15
+ The other requirement was that the permission for an object could be defined
16
+ succinctly within a single file.
17
+
18
+ == Defining indulgent permissions
19
+
20
+ Indulgence is added to a class via acts_as_indulgent:
21
+
22
+ class Thing < ActiveRecord::Base
23
+ acts_as_indulgent
24
+ end
25
+
26
+ Used in this way, permissions need to be defined in an Indulgence::Permission
27
+ object called ThingPermission, with an instance method :default
28
+
29
+ class ThingPermission < Indulgence::Permission
30
+
31
+ def default
32
+ {
33
+ create: none,
34
+ read: all,
35
+ update: none,
36
+ delete: none
37
+ }
38
+ end
39
+
40
+ end
41
+
42
+ This needs to be available to the Thing class. For example, in a rails app, by
43
+ placing it in app/permissions/thing_permission.rb
44
+
45
+ == Users and Roles
46
+
47
+ Indulgence assumes that permissions will be tested against an entity object
48
+ (e.g. User), that has a role object associated with it, and that each role can
49
+ be uniquely identified by a name method.
50
+
51
+ The default behaviour assumes that the entity object has a :role method that
52
+ returns the role object, and that the role object has a :name method. So
53
+ typically, these objects could look like this:
54
+
55
+ class User < ActiveRecord::Base
56
+ belongs_to :role
57
+ end
58
+
59
+ class Role < ActiveRecord::Base
60
+ has_many :users
61
+ validates :name, :uniqueness => true
62
+ end
63
+
64
+ role = Role.create(:name => 'pleb')
65
+ user = User.create(
66
+ :first_name => 'Joe',
67
+ :last_name => 'Blogs',
68
+ :role_id => role.id
69
+ )
70
+
71
+ == indulge?
72
+
73
+ Simple true/false permission can be determined using the :indulge? method:
74
+
75
+ thing = Thing.first
76
+
77
+ thing.indulge?(user, :create) == false
78
+ thing.indulge?(user, :read) == true
79
+ thing.indulge?(user, :update) == false
80
+ thing.indulge?(user, :delete) == false
81
+
82
+ == indulgence
83
+
84
+ The :indulgence method is used as a where filter:
85
+
86
+ Thing.indulgence(user, :create) --> raises ActiveRecord::RecordNotFound
87
+ Thing.indulgence(user, :read) == Thing.all
88
+ Thing.indulgence(user, :update) --> raises ActiveRecord::RecordNotFound
89
+ Thing.indulgence(user, :delete) --> raises ActiveRecord::RecordNotFound
90
+
91
+ So to find all the blue things that the user has permission to read:
92
+
93
+ Thing.indulgence(user, :read).where(:colour => 'blue')
94
+
95
+ == Customisation
96
+
97
+ === Adding other roles
98
+
99
+ Up until now, all users get the same permissions irrespective of role. Let's
100
+ give Emperors the right to see and do anything by first creating an emperor
101
+
102
+ emperor = Role.create(:name => 'emperor')
103
+ caesar = User.create(
104
+ :first_name => 'Julius',
105
+ :last_name => 'Ceasar',
106
+ :role_id => emporor.id
107
+ )
108
+
109
+ And then defining what they can do by adding these two methods to ThingPermission:
110
+
111
+ def abilities
112
+ {
113
+ emperor: default.merge(emperor)
114
+ }
115
+ end
116
+
117
+ def emperor
118
+ {
119
+ create: all,
120
+ update: all,
121
+ delete: all
122
+ }
123
+ end
124
+
125
+ This uses a merger of the default abilities so that only the variant abilities
126
+ need to be defined in the emperor method. That is, _read_ is inherited from
127
+ _default_ rather than being defined in _emperor_, as it is already set to *all*.
128
+
129
+ abilities is a hash of hashes. The lowest level, associates action names with
130
+ ability objects. The top level associates role names to the lower level ability
131
+ object hashes. The construction is perhaps clearer in the abilities above was
132
+ written like this:
133
+
134
+ def abilities
135
+ {
136
+ emperor: {
137
+ create: all,
138
+ read: default[:read],
139
+ update: all,
140
+ delete: all
141
+ }
142
+ }
143
+ end
144
+
145
+ With this done:
146
+
147
+ thing.indulge?(caesar, :create) == true
148
+ thing.indulge?(caesar, :read) == true
149
+ thing.indulge?(caesar, :update) == true
150
+ thing.indulge?(caesar, :delete) == true
151
+
152
+ Thing.indulgence(caesar, :create) == Thing.all
153
+ Thing.indulgence(caesar, :read) == Thing.all
154
+ Thing.indulgence(caesar, :update) == Thing.all
155
+ Thing.indulgence(caesar, :delete) == Thing.all
156
+
157
+ === Adding abilities
158
+
159
+ Indulgence has two built in abilities. These are *all* and *none*. These two
160
+ have provided all the functionality described above, but in most real cases
161
+ some more fine tuned ability setting will be needed.
162
+
163
+ Let's create an author role, and give authors the ability to create and update
164
+ their own things.
165
+
166
+ author = Role.create(:name => :author)
167
+
168
+ Next we need to give author's ownership of things. So we add an :author_id
169
+ attribute to Thing, and a matching :author method:
170
+
171
+ class Thing < ActiveRecord::Base
172
+ acts_as_indulgent
173
+ belongs_to :author, :class_name => 'User'
174
+ end
175
+
176
+ Then we need to create an Ability that uses this relationship to determine
177
+ permissions. This can be done by adding this method to ThingPermission:
178
+
179
+ def things_they_wrote
180
+ define_ability(
181
+ :name => :things_they_wrote,
182
+ :truth => lambda {|thing| thing.author_id == entity.id},
183
+ :where_clause => {:author_id => entity.id}
184
+ )
185
+ end
186
+
187
+ This will create an Ability object with the following methods:
188
+
189
+ [name] Allows abilities of the same kind to be matched
190
+ [truth] Used by :indulge?
191
+ [where_clause] Used by :indulgence
192
+
193
+ Note that Indulgence::Permission#entity returns the entity object passed to the
194
+ instance on creation. In this example, that will be the User.
195
+
196
+ ==== The where clause
197
+
198
+ In this example:
199
+
200
+ Thing.indulgence(user, :read) == Thing.where(:author_id => user.id)
201
+
202
+ if the :read ability is matched to *things_they_wrote*
203
+
204
+ ==== truth
205
+
206
+ In *all* :truth is simply _true_, and in *none* :truth is _false_.
207
+
208
+ For more complex abilities :truth should be a lambda that is passed the object
209
+ being tested, and returns _true_ if permission should be given. Otherwise _false_
210
+ should be returned.
211
+
212
+ Once *things_they_wrote* has been defined, we can use it to define a new set
213
+ of abilities:
214
+
215
+ def abilities
216
+ {
217
+ emperor: default.merge(emperor),
218
+ author: default.merge(author)
219
+ }
220
+ end
221
+
222
+ def author
223
+ {
224
+ create: things_they_wrote,
225
+ update: things_they_wrote
226
+ }
227
+ end
228
+
229
+ With that done:
230
+
231
+ cicero = User.create(
232
+ :first_name => 'Marcus',
233
+ :last_name => 'Cicero',
234
+ :role_id => author.id
235
+ )
236
+
237
+ thing.author = cicero
238
+ thing.save
239
+
240
+ thing.indulge?(cicero, :create) == true
241
+ thing.indulge?(cicero, :read) == true
242
+ thing.indulge?(cicero, :update) == true
243
+ thing.indulge?(cicero, :delete) == false
244
+
245
+ Thing.indulgence(cicero, :create) == [thing]
246
+ Thing.indulgence(cicero, :read) == Thing.all
247
+ Thing.indulgence(cicero, :update) == [thing]
248
+ Thing.indulgence(cicero, :delete) --> raises ActiveRecord::RecordNotFound
249
+
250
+ === Alternative Permission Class
251
+
252
+ As stated above, the default behaviour is for a Thing class to expect its
253
+ permissions to be defined in ThingPermission. However, you can define an
254
+ alternative class name:
255
+
256
+ acts_as_indulgent :using => PermissionsForThing
257
+
258
+ === Alternative Entity behaviour
259
+
260
+ Consider this example:
261
+
262
+ class User < ActiveRecord::Base
263
+ belongs_to :position
264
+ end
265
+
266
+ class Position < ActiveRecord::Base
267
+ has_many :users
268
+ validates :title, :uniqueness => true
269
+ end
270
+
271
+ There are two ways of dealing with this.
272
+
273
+ If only ThingPermission is affected, the attributes that stores the _role_method_
274
+ and _role_name_method_ could be overwritten:
275
+
276
+ class ThingPermission < Indulgence::Permission
277
+ def self.role_method
278
+ :position
279
+ end
280
+
281
+ def self.role_name_method
282
+ :title
283
+ end
284
+
285
+ .....
286
+ end
287
+
288
+ Alternatively if all permissions were to be altered the super class Permission
289
+ could be updated (for example in a rails initializer):
290
+
291
+ Indulgence::Permission.role_method = :position
292
+ Indulgence::Permission.role_name_method = :title
293
+
294
+ === Alternative method names
295
+
296
+ The method names _indulgence_ and _indulge?_ may not suit your application. If
297
+ you wish to use alternative names, they can be aliased like this:
298
+
299
+ acts_as_indulgent(
300
+ :truth_method => :permit?,
301
+ :where_method => :permitted
302
+ )
303
+
304
+ With this used to define indulgence in Thing, we can do this:
305
+
306
+ thing.permit?(cicero, :update) == true
307
+ thing.permit?(cicero, :delete) == false
308
+
309
+ Thing.permitted(cicero, :create) == [thing]
310
+ Thing.permitted(cicero, :read) == Thing.all
311
+
312
+ == Examples
313
+
314
+ For some examples, have a look at the tests. In particular, look at the object
315
+ definitions in test/lib.
@@ -0,0 +1,13 @@
1
+
2
+ require 'rubygems'
3
+ require 'rake'
4
+ require 'rake/clean'
5
+ require 'rake/testtask'
6
+
7
+ Rake::TestTask.new do |t|
8
+ t.test_files = FileList['test/**/*_test.rb']
9
+ end
10
+
11
+ require 'standalone_migrations'
12
+ StandaloneMigrations::Tasks.load_tasks
13
+
@@ -0,0 +1,42 @@
1
+ module ActiveRecord
2
+ module Acts
3
+ module Indulgent
4
+ def self.included(base)
5
+ base.send :extend, ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+
10
+ def acts_as_indulgent(args = {})
11
+ include Indulgence::Indulgent::InstanceMethods
12
+ extend Indulgence::Indulgent::ClassMethods
13
+
14
+ alias_method args[:truth_method], :indulge? if args[:truth_method]
15
+ singleton_class.send(:alias_method, args[:where_method], :indulgence) if args[:where_method]
16
+ self.indulgent_permission_class = args[:using]
17
+ end
18
+
19
+ def indulgent_permission_class
20
+ @indulgent_permission_class
21
+ end
22
+
23
+ private
24
+ def default_indulgence_permission_class
25
+ class_name = to_s
26
+ name = class_name + "Permission"
27
+ require name.underscore
28
+ if const_defined? name
29
+ class_eval name
30
+ end
31
+ end
32
+
33
+ def indulgent_permission_class=(klass = nil)
34
+ @indulgent_permission_class = klass || default_indulgence_permission_class
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ ActiveRecord::Base.send(:include, ActiveRecord::Acts::Indulgent)
@@ -0,0 +1,7 @@
1
+ require_relative 'indulgence/permission'
2
+ require_relative 'indulgence/indulgent'
3
+ require_relative 'active_record/acts/indulgent'
4
+
5
+ module Indulgence
6
+
7
+ end
@@ -0,0 +1,19 @@
1
+
2
+ module Indulgence
3
+ class Ability
4
+ attr_reader :name, :truth, :where_clause
5
+
6
+ def initialize(args = {})
7
+ @name = args[:name]
8
+ @truth = args[:truth]
9
+ @where_clause = args[:where_clause]
10
+ end
11
+
12
+ def ==(another_ability)
13
+ [:name, :truth, :where_clause].each do |method|
14
+ return false if send(method) != another_ability.send(method)
15
+ end
16
+ return true
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ module Indulgence
2
+ module Indulgent
3
+
4
+ module ClassMethods
5
+ def indulgence(entity, ability)
6
+ permission = indulgent_permission_class.new(entity, ability)
7
+ raise_not_found if permission.ability == Permission.none or permission.ability.blank?
8
+ where(permission.where)
9
+ end
10
+
11
+ private
12
+ def raise_not_found
13
+ raise ActiveRecord::RecordNotFound.new('Unable to find the item(s) you were looking for')
14
+ end
15
+ end
16
+
17
+ module InstanceMethods
18
+
19
+ def indulge?(entity, ability)
20
+ permission = self.class.indulgent_permission_class.new(entity, ability)
21
+ return permission.indulge? self
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,104 @@
1
+ require_relative 'ability'
2
+
3
+ module Indulgence
4
+ class Permission
5
+ attr_reader :entity, :ability
6
+
7
+ def initialize(entity, ability)
8
+ self
9
+ @entity = entity
10
+ @ability = abilities_for_role[ability]
11
+ end
12
+
13
+ def abilities
14
+ {}
15
+ end
16
+
17
+ def default
18
+ raise 'There must always be a default'
19
+ end
20
+
21
+ def where
22
+ ability.where_clause
23
+ end
24
+
25
+ def indulge?(thing)
26
+ ability.truth.respond_to?(:call) ? ability.truth.call(thing) : ability.truth
27
+ end
28
+
29
+ @@role_method = :role
30
+
31
+ def self.role_method=(name)
32
+ @@role_method = name.to_sym
33
+ end
34
+
35
+ def self.role_method
36
+ @@role_method
37
+ end
38
+
39
+ @@role_name_method = :name
40
+
41
+ def self.role_name_method=(name)
42
+ @@role_name_method = name.to_sym
43
+ end
44
+
45
+ def self.role_name_method
46
+ @@role_name_method
47
+ end
48
+
49
+ def self.none
50
+ @none ||= define_ability(
51
+ :name => :none,
52
+ :truth => false
53
+ )
54
+ end
55
+
56
+ def self.all
57
+ @all ||= define_ability(
58
+ :name => :all,
59
+ :truth => true
60
+ )
61
+ end
62
+
63
+ def self.define_ability(args)
64
+ Ability.new args
65
+ end
66
+
67
+ def define_ability(args)
68
+ self.class.define_ability(args)
69
+ end
70
+
71
+ # Ensure passing an unknown key behaves as one would expect for a hash
72
+ def [](key)
73
+ return {}[key] unless keys.include? key
74
+ super
75
+ end
76
+
77
+ def role_name
78
+ @role_name ||= entity_role_name
79
+ end
80
+
81
+ def abilities_for_role
82
+ abilities[role_name] || default
83
+ end
84
+
85
+ private
86
+ def entity_role_name
87
+ role = entity.send(self.class.role_method)
88
+ name_method = self.class.role_name_method
89
+ if role and abilities.keys.include?(role.send(name_method).to_sym)
90
+ role.send(name_method).to_sym
91
+ else
92
+ :default
93
+ end
94
+ end
95
+
96
+ def all
97
+ self.class.all
98
+ end
99
+
100
+ def none
101
+ self.class.none
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,10 @@
1
+ module Indulgence
2
+ VERSION = "0.0.1"
3
+ end
4
+
5
+ # History
6
+ # =======
7
+ #
8
+ # 0.0.1 Initial build
9
+ # First alpa version
10
+ #
@@ -0,0 +1,17 @@
1
+ test: &test
2
+ adapter: sqlite3
3
+ database: test/db/test.sqlite3.db
4
+ pool: 5
5
+ timeout: 5000
6
+
7
+ develop:
8
+ adapter: sqlite3
9
+ database: test/db/develop.sqlite3.db
10
+ pool: 5
11
+ timeout: 5000
12
+
13
+ production:
14
+ adapter: sqlite3
15
+ database: test/db/production.sqlite3.db
16
+ pool: 5
17
+ timeout: 5000
@@ -0,0 +1,11 @@
1
+ require 'active_record'
2
+ class CreateUsers < ActiveRecord::Migration
3
+ def change
4
+ create_table :users do |t|
5
+ t.string :name
6
+ t.integer :role_id
7
+
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ require 'active_record'
2
+ class CreateRoles < ActiveRecord::Migration
3
+ create_table :roles do |t|
4
+ t.string :name
5
+
6
+ t.timestamps
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ class CreateThings < ActiveRecord::Migration
2
+ create_table :things do |t|
3
+ t.string :name
4
+ t.integer :owner_id
5
+ t.timestamps
6
+ end
7
+ end
@@ -0,0 +1,36 @@
1
+ # encoding: UTF-8
2
+ # This file is auto-generated from the current state of the database. Instead
3
+ # of editing this file, please use the migrations feature of Active Record to
4
+ # incrementally modify your database, and then regenerate this schema definition.
5
+ #
6
+ # Note that this schema.rb definition is the authoritative source for your
7
+ # database schema. If you need to create the application database on another
8
+ # system, you should be using db:schema:load, not running all the migrations
9
+ # from scratch. The latter is a flawed and unsustainable approach (the more migrations
10
+ # you'll amass, the slower it'll run and the greater likelihood for issues).
11
+ #
12
+ # It's strongly recommended to check this file into your version control system.
13
+
14
+ ActiveRecord::Schema.define(:version => 20130408132217) do
15
+
16
+ create_table "roles", :force => true do |t|
17
+ t.string "name"
18
+ t.datetime "created_at", :null => false
19
+ t.datetime "updated_at", :null => false
20
+ end
21
+
22
+ create_table "things", :force => true do |t|
23
+ t.string "name"
24
+ t.integer "owner_id"
25
+ t.datetime "created_at", :null => false
26
+ t.datetime "updated_at", :null => false
27
+ end
28
+
29
+ create_table "users", :force => true do |t|
30
+ t.string "name"
31
+ t.integer "role_id"
32
+ t.datetime "created_at", :null => false
33
+ t.datetime "updated_at", :null => false
34
+ end
35
+
36
+ end
Binary file
@@ -0,0 +1,7 @@
1
+ class Role < ActiveRecord::Base
2
+ has_many :users
3
+
4
+ def title
5
+ self.name
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+
2
+ class Thing < ActiveRecord::Base
3
+
4
+ belongs_to :owner, :class_name => 'User'
5
+
6
+ acts_as_indulgent(
7
+ :truth_method => :permit?,
8
+ :where_method => :permitted
9
+ )
10
+
11
+ end
12
+
13
+ class OtherThing < Thing
14
+
15
+ acts_as_indulgent :using => ThingPermission
16
+
17
+ end
18
+
19
+
@@ -0,0 +1,46 @@
1
+ require 'indulgence'
2
+
3
+ class ThingPermission < Indulgence::Permission
4
+
5
+ def abilities
6
+ {
7
+ god: default.merge(god),
8
+ demigod: default.merge(demigod)
9
+ }
10
+ end
11
+
12
+
13
+ def default
14
+ {
15
+ create: none,
16
+ read: all,
17
+ update: none,
18
+ delete: none
19
+ }
20
+ end
21
+
22
+ def god
23
+ {
24
+ create: all,
25
+ update: all,
26
+ delete: all
27
+ }
28
+ end
29
+
30
+ def demigod
31
+ {
32
+ create: things_they_own,
33
+ update: things_they_own,
34
+ delete: things_they_own
35
+ }
36
+ end
37
+
38
+ def things_they_own
39
+ @things_they_own ||= define_ability(
40
+ :name => :things_they_own,
41
+ :truth => lambda {|thing| thing.owner_id == entity.id},
42
+ :where_clause => {:owner_id => entity.id}
43
+ )
44
+ end
45
+
46
+ end
@@ -0,0 +1,9 @@
1
+ class User < ActiveRecord::Base
2
+
3
+ belongs_to :role
4
+
5
+ has_many :things, :foreign_key => 'owner_id'
6
+
7
+ alias_method :position, :role
8
+
9
+ end
@@ -0,0 +1,9 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+ $:.unshift File.join(File.dirname(__FILE__),'units')
3
+ $:.unshift File.join(File.dirname(__FILE__),'lib')
4
+ $:.unshift File.join(File.dirname(__FILE__),'..','lib','indulgence')
5
+
6
+ require 'test/unit'
7
+
8
+ require 'active_record'
9
+ ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => "test/db/test.sqlite3.db"
@@ -0,0 +1,36 @@
1
+ require_relative '../../test_helper'
2
+ require 'ability'
3
+
4
+ module Indulgence
5
+ class AbilityTest < Test::Unit::TestCase
6
+
7
+ def test_none
8
+ none = Ability.new(
9
+ :name => :none,
10
+ :truth => false
11
+ )
12
+ assert_equal(:none, none.name)
13
+ assert_equal(false, none.truth)
14
+ assert_equal(nil, none.where_clause)
15
+ end
16
+
17
+ def test_all
18
+ all = Ability.new(
19
+ :name => :all,
20
+ :truth => true
21
+ )
22
+ assert_equal(:all, all.name)
23
+ assert_equal(true, all.truth)
24
+ assert_equal(nil, all.where_clause)
25
+ end
26
+
27
+ def test_equality
28
+ attributes = {:name => :same, :true => true}
29
+ ability = Ability.new(attributes)
30
+ other_ability = Ability.new(attributes)
31
+ assert(ability == other_ability, "#{ability} == #{other_ability} should return true")
32
+ assert_equal(ability, other_ability)
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,15 @@
1
+ require_relative '../../test_helper'
2
+ require 'user'
3
+
4
+ module Indulgence
5
+ class PermissionTest < Test::Unit::TestCase
6
+
7
+ def test_creation_fails_as_methods_undefined_in_parent_class
8
+ assert_raise RuntimeError do
9
+ Permission.new(User.create(:name => 'Whisp'), :read)
10
+ end
11
+ end
12
+
13
+
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ require_relative '../test_helper'
2
+ require 'user'
3
+ require 'role'
4
+
5
+ class RoleTest < Test::Unit::TestCase
6
+ def teardown
7
+ User.delete_all
8
+ Role.delete_all
9
+ end
10
+
11
+ def test_users
12
+ role = Role.create(:name => 'god')
13
+ user = User.create(:name => 'Harry', :role_id => role.id)
14
+ assert_equal([user], role.users)
15
+ end
16
+ end
@@ -0,0 +1,78 @@
1
+ require_relative '../test_helper'
2
+ require 'user'
3
+ require 'role'
4
+ require 'thing_permission'
5
+
6
+ class ThingPermissionTest < Test::Unit::TestCase
7
+
8
+ Permission = Indulgence::Permission
9
+
10
+ def setup
11
+ god = Role.create(:name => 'god')
12
+ mortal = Role.create(:name => 'mortal')
13
+ @user = User.create(:name => 'Trevor', :role_id => mortal.id)
14
+ @super_user = User.create(:name => 'Jane', :role_id => god.id)
15
+ @original_role_name = Permission.role_method
16
+ @original_role_name_method = Permission.role_name_method
17
+ end
18
+
19
+ def test_super_user_permissions
20
+ assert_equal Permission.all, ThingPermission.new(@super_user, :create).ability
21
+ assert_equal Permission.all, ThingPermission.new(@super_user, :read).ability
22
+ assert_equal Permission.all, ThingPermission.new(@super_user, :update).ability
23
+ assert_equal Permission.all, ThingPermission.new(@super_user, :delete).ability
24
+ end
25
+
26
+ def test_default_permissions
27
+ assert_equal Permission.none, ThingPermission.new(@user, :create).ability
28
+ assert_equal Permission.all, ThingPermission.new(@user, :read).ability
29
+ assert_equal Permission.none, ThingPermission.new(@user, :update).ability
30
+ assert_equal Permission.none, ThingPermission.new(@user, :delete).ability
31
+ end
32
+
33
+ def test_role_name
34
+ assert_equal @super_user.role.name.to_sym, ThingPermission.new(@super_user, :read).role_name
35
+ end
36
+
37
+ def test_role_name_when_not_defined_in_permissions
38
+ assert_equal :default, ThingPermission.new(@user, :read).role_name
39
+ end
40
+
41
+ def test_setting_unknown_role_method_causes_error
42
+ Permission.role_method = :something_else
43
+ assert_raise NoMethodError do
44
+ ThingPermission.new(@super_user, :read).role_name
45
+ end
46
+ end
47
+
48
+ def test_setting_role_method
49
+ Permission.role_method = :position
50
+ test_super_user_permissions
51
+ end
52
+
53
+ def test_setting_unknown_role_name_method_causes_error
54
+ Permission.role_name_method = :something_else
55
+ assert_raise NoMethodError do
56
+ ThingPermission.new(@super_user, :read).role_name
57
+ end
58
+ end
59
+
60
+ def test_setting_role_name_method
61
+ Permission.role_name_method = :title
62
+ test_super_user_permissions
63
+ end
64
+
65
+ def teardown
66
+ User.delete_all
67
+ Role.delete_all
68
+ Thing.delete_all
69
+ Permission.role_method = @original_role_name
70
+ Permission.role_name_method = @original_role_name_method
71
+ end
72
+
73
+ def make_things
74
+ @users_thing = Thing.create(:one, :owner_id => @user.id)
75
+ @super_users_thing = Thing.create(:two, :owner_id => @super_user.id)
76
+ end
77
+
78
+ end
@@ -0,0 +1,98 @@
1
+ require_relative '../test_helper'
2
+ require 'user'
3
+ require 'thing'
4
+
5
+ class ThingTest < Test::Unit::TestCase
6
+
7
+ def setup
8
+ @god = Role.create(:name => 'god')
9
+ @demigod = Role.create(:name => 'demigod')
10
+ @owner = User.create(:name => 'Mary')
11
+ @thing = Thing.create(:name => 'Stuff', :owner_id => @owner.id)
12
+ end
13
+
14
+
15
+ def test_user
16
+ assert_equal(@owner, @thing.owner)
17
+ assert_equal([@thing], @owner.things)
18
+ end
19
+
20
+ def test_indulge
21
+ make_second_thing
22
+ assert_equal(true, @thing.indulge?(@owner, :read))
23
+ assert_equal(false, @thing.indulge?(@owner, :delete))
24
+ assert_equal(false, @other_thing.indulge?(@owner, :delete))
25
+ end
26
+
27
+ def test_indulge_by_god
28
+ make_second_thing
29
+ @owner.update_attribute(:role_id, @god.id)
30
+ assert_equal(true, @thing.indulge?(@owner, :read))
31
+ assert_equal(true, @thing.indulge?(@owner, :delete))
32
+ assert_equal(true, @other_thing.indulge?(@owner, :delete))
33
+ end
34
+
35
+ def test_indulge_by_demigod
36
+ make_second_thing
37
+ @owner.update_attribute(:role_id, @demigod.id)
38
+ assert_equal(true, @thing.indulge?(@owner, :read))
39
+ assert_equal(true, @thing.indulge?(@owner, :delete))
40
+ assert_equal(false, @other_thing.indulge?(@owner, :delete))
41
+ end
42
+
43
+ def test_indulge_other_thing
44
+ other_thing = OtherThing.create(:name => 'Other Stuff', :owner_id => @owner.id)
45
+ assert_equal(true, other_thing.indulge?(@owner, :read))
46
+ assert_equal(false, other_thing.indulge?(@owner, :delete))
47
+ end
48
+
49
+ def test_indulgence
50
+ make_second_thing
51
+ @owner.update_attribute(:role_id, @demigod.id)
52
+ assert_equal(Thing.order('id'), Thing.indulgence(@owner, :read).order('id'))
53
+ assert_equal(Thing.order('id'), Thing.indulgence(@user, :read).order('id'))
54
+ assert_equal([@thing], Thing.indulgence(@owner, :delete))
55
+ assert_raise ActiveRecord::RecordNotFound do
56
+ Thing.indulgence(@user, :delete)
57
+ end
58
+ end
59
+
60
+ def test_find
61
+ make_second_thing
62
+ @owner.update_attribute(:role_id, @demigod.id)
63
+ assert_equal(@thing, Thing.indulgence(@owner, :delete).find(@thing.id))
64
+ assert_raise ActiveRecord::RecordNotFound do
65
+ assert_equal(@thing, Thing.indulgence(@user, :delete).find(@thing.id))
66
+ end
67
+ end
68
+
69
+ def test_truth_method
70
+ make_second_thing
71
+ assert_equal(true, @thing.permit?(@owner, :read))
72
+ assert_equal(false, @thing.permit?(@owner, :delete))
73
+ assert_equal(false, @other_thing.permit?(@owner, :delete))
74
+ end
75
+
76
+ def test_where_method
77
+ make_second_thing
78
+ @owner.update_attribute(:role_id, @demigod.id)
79
+ assert_equal(Thing.order('id'), Thing.permitted(@owner, :read).order('id'))
80
+ assert_equal(Thing.order('id'), Thing.permitted(@user, :read).order('id'))
81
+ assert_equal([@thing], Thing.permitted(@owner, :delete))
82
+ assert_raise ActiveRecord::RecordNotFound do
83
+ Thing.permitted(@user, :delete)
84
+ end
85
+ end
86
+
87
+ def make_second_thing
88
+ @user = User.create(:name => 'Clive')
89
+ @other_thing = Thing.create(:name => 'Debris', :owner_id => @user.id)
90
+ end
91
+
92
+ def teardown
93
+ Role.delete_all
94
+ User.delete_all
95
+ Thing.delete_all
96
+ end
97
+
98
+ end
@@ -0,0 +1,21 @@
1
+ require_relative '../test_helper'
2
+ require 'user'
3
+ require 'role'
4
+
5
+ class UserTest < Test::Unit::TestCase
6
+ def teardown
7
+ User.delete_all
8
+ Role.delete_all
9
+ end
10
+
11
+ def test_create
12
+ user = User.create(:name => 'Bob')
13
+ assert_equal(user, User.find_by_name('Bob'))
14
+ end
15
+
16
+ def test_role
17
+ role = Role.create(:name => 'god')
18
+ user = User.create(:name => 'Harry', :role_id => role.id)
19
+ assert_equal(role, user.role)
20
+ end
21
+ end
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: indulgence
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Rob Nichols
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-04-10 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: sqlite3
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: Packages permission functionality into a set of permission objects.
47
+ email:
48
+ - rob@undervale.co.uk
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - lib/active_record/acts/indulgent.rb
54
+ - lib/indulgence/version.rb
55
+ - lib/indulgence/indulgent.rb
56
+ - lib/indulgence/ability.rb
57
+ - lib/indulgence/permission.rb
58
+ - lib/indulgence.rb
59
+ - MIT-LICENSE
60
+ - Rakefile
61
+ - README.rdoc
62
+ - test/units/role_test.rb
63
+ - test/units/indulgence/permission_test.rb
64
+ - test/units/indulgence/ability_test.rb
65
+ - test/units/user_test.rb
66
+ - test/units/thing_permission_test.rb
67
+ - test/units/thing_test.rb
68
+ - test/lib/role.rb
69
+ - test/lib/user.rb
70
+ - test/lib/thing.rb
71
+ - test/lib/thing_permission.rb
72
+ - test/db/migrate/20130408103015_create_roles.rb
73
+ - test/db/migrate/20130408085511_create_users.rb
74
+ - test/db/migrate/20130408132217_create_things.rb
75
+ - test/db/config.yml
76
+ - test/db/test.sqlite3.db
77
+ - test/db/schema.rb
78
+ - test/test_helper.rb
79
+ homepage: https://github.com/reggieb/indulgence
80
+ licenses: []
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ requirements: []
98
+ rubyforge_project:
99
+ rubygems_version: 1.8.24
100
+ signing_key:
101
+ specification_version: 3
102
+ summary: Yet another permissions gem
103
+ test_files:
104
+ - test/units/role_test.rb
105
+ - test/units/indulgence/permission_test.rb
106
+ - test/units/indulgence/ability_test.rb
107
+ - test/units/user_test.rb
108
+ - test/units/thing_permission_test.rb
109
+ - test/units/thing_test.rb
110
+ - test/lib/role.rb
111
+ - test/lib/user.rb
112
+ - test/lib/thing.rb
113
+ - test/lib/thing_permission.rb
114
+ - test/db/migrate/20130408103015_create_roles.rb
115
+ - test/db/migrate/20130408085511_create_users.rb
116
+ - test/db/migrate/20130408132217_create_things.rb
117
+ - test/db/config.yml
118
+ - test/db/test.sqlite3.db
119
+ - test/db/schema.rb
120
+ - test/test_helper.rb