indulgence 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -17,7 +17,7 @@ succinctly within a single file.
17
17
 
18
18
  == Defining indulgent permissions
19
19
 
20
- Indulgence is added to a class via acts_as_indulgent:
20
+ Indulgence can be added to a class via acts_as_indulgent:
21
21
 
22
22
  class Thing < ActiveRecord::Base
23
23
  acts_as_indulgent
@@ -59,11 +59,11 @@ So typically, these objects could look like this:
59
59
  validates :name, :uniqueness => true
60
60
  end
61
61
 
62
- role = Role.create(:name => 'pleb')
62
+ pleb = Role.create(:name => 'pleb')
63
63
  user = User.create(
64
64
  :first_name => 'Joe',
65
65
  :last_name => 'Blogs',
66
- :role_id => role.id
66
+ :role => pleb
67
67
  )
68
68
 
69
69
  == Compare single item: indulge?
@@ -101,7 +101,7 @@ give Emperors the right to see and do anything by first creating an emperor
101
101
  caesar = User.create(
102
102
  :first_name => 'Julius',
103
103
  :last_name => 'Ceasar',
104
- :role_id => emperor.id
104
+ :role => emperor
105
105
  )
106
106
 
107
107
  And then defining what they can do by adding these two methods to ThingPermission:
@@ -184,26 +184,28 @@ permissions. This can be done by adding this method to ThingPermission:
184
184
 
185
185
  This will create an Ability object with the following methods:
186
186
 
187
- [name] Allows abilities of the same kind to be matched
187
+ [name] Allows abilities of the same kind to be matched and cached
188
188
  [compare_single] Used by :indulge?
189
189
  [filter_many] Used by :indulgence
190
190
 
191
- ==== Compare single
191
+ Alternatively, you can achieve the same result by defining lambdas for the
192
+ ability methods :compare_single and :filter_many. If you use lambdas, you must
193
+ also define a name.
192
194
 
193
- _compare_single_ should be a lambda that is passed the object being tested and
194
- the entity it is to be tested against. The lambda returns _true_ if permission
195
- should be given. Otherwise _false_ should be returned.
195
+ Alternatively you can define the ability like this:
196
196
 
197
- Alternatively, instead of a lambda, any object can be used that returns _true_
198
- or _false_ when it _call_ method is passed the object and the entity.
199
-
200
- ==== Filter many
197
+ def things_they_wrote
198
+ define_ability(:author)
199
+ end
201
200
 
202
- In this example:
201
+ This will use :author to define attributes of an ability object. :author could
202
+ be an association or an attribute that return either the entity or the entity.id.
203
203
 
204
- Thing.indulgence(user, :read) == Thing.where(:author_id => user.id)
204
+ So this also works:
205
205
 
206
- if the :read ability is matched to *things_they_wrote*
206
+ def things_they_wrote
207
+ define_ability(:author_id)
208
+ end
207
209
 
208
210
  Once *things_they_wrote* has been defined, we can use it to define a new set
209
211
  of abilities:
@@ -227,11 +229,10 @@ With that done:
227
229
  cicero = User.create(
228
230
  :first_name => 'Marcus',
229
231
  :last_name => 'Cicero',
230
- :role_id => author.id
232
+ :role => author
231
233
  )
232
234
 
233
- thing.author = cicero
234
- thing.save
235
+ thing.update_attribute :author, cicero
235
236
 
236
237
  thing.indulge?(cicero, :create) == true
237
238
  thing.indulge?(cicero, :read) == true
@@ -304,6 +305,7 @@ If only ThingPermission is affected, the attributes that stores the _role_method
304
305
  and _role_name_method_ could be overwritten:
305
306
 
306
307
  class ThingPermission < Indulgence::Permission
308
+
307
309
  def self.role_method
308
310
  :position
309
311
  end
@@ -342,4 +344,18 @@ With this used to define indulgence in Thing, we can do this:
342
344
  == Examples
343
345
 
344
346
  For some examples, have a look at the tests. In particular, look at the object
345
- definitions in test/lib.
347
+ definitions in test/lib.
348
+
349
+ == Playing with Indulgence
350
+
351
+ This app has a set of tests associated with it. The test suite includes the
352
+ configuration for a sqlite3 test database, with migrations and fixtures. If you
353
+ wish to play with, and/or modify Indulgence: fork the project and clone a local
354
+ copy. Run bundler to install the necessary gems. Then use this command to
355
+ create the test database:
356
+
357
+ rake db:migrate RAILS_ENV=test
358
+
359
+ If this raises no errors, you should then be able to run the tests with:
360
+
361
+ rake test
@@ -1,17 +1,19 @@
1
1
 
2
2
  module Indulgence
3
3
  class Ability
4
- attr_reader :name, :compare_single, :filter_many
4
+ attr_reader :name, :relationship, :args
5
5
 
6
6
  def initialize(args = {})
7
7
  @name = args[:name]
8
8
  @compare_single = args[:compare_single]
9
9
  @filter_many = args[:filter_many]
10
+ @relationship = args[:relationship]
11
+ @args = args
10
12
  valid?
11
13
  end
12
14
 
13
15
  def ==(another_ability)
14
- [:name, :compare_single, :filter_many].each do |method|
16
+ [:name, :args].each do |method|
15
17
  return false if send(method) != another_ability.send(method)
16
18
  end
17
19
  return true
@@ -19,8 +21,27 @@ module Indulgence
19
21
 
20
22
  def valid?
21
23
  must_be_name
22
- must_respond_to_call :compare_single
23
- must_respond_to_call :filter_many
24
+ unless relationship
25
+ must_respond_to_call @compare_single
26
+ must_respond_to_call @filter_many
27
+ end
28
+ end
29
+
30
+ def compare_single(thing, entity)
31
+ return @compare_single.call thing, entity if @compare_single
32
+
33
+ identifier = thing.send(relationship)
34
+ identifier == entity.id || identifier == entity
35
+ end
36
+
37
+ def filter_many(things, entity)
38
+ return @filter_many.call things, entity if @filter_many
39
+
40
+ if things.reflect_on_association(relationship)
41
+ things.joins(relationship).where(entity.class.table_name => {:id => entity.id})
42
+ else
43
+ things.where(relationship => entity.id)
44
+ end
24
45
  end
25
46
 
26
47
  private
@@ -30,9 +51,9 @@ module Indulgence
30
51
  end
31
52
  end
32
53
 
33
- def must_respond_to_call(method)
34
- unless send(method).respond_to? :call
35
- raise AbilityConfigurationError, "ability.#{method} must respond to call"
54
+ def must_respond_to_call(item)
55
+ unless item.respond_to? :call
56
+ raise AbilityConfigurationError, "item must respond to call: #{item.inspect}"
36
57
  end
37
58
  end
38
59
 
@@ -18,13 +18,11 @@ module Indulgence
18
18
  end
19
19
 
20
20
  def filter_many(things)
21
- check_method_can_be_called(:filter_many)
22
- ability.filter_many.call things, entity
21
+ ability.filter_many things, entity
23
22
  end
24
23
 
25
24
  def compare_single(thing)
26
- check_method_can_be_called(:compare_single)
27
- ability.compare_single.call thing, entity
25
+ ability.compare_single thing, entity
28
26
  end
29
27
 
30
28
  @@role_method = :role
@@ -64,7 +62,7 @@ module Indulgence
64
62
  end
65
63
 
66
64
  def self.define_ability(args)
67
- raise AbilityConfigurationError, "A name is required for each ability" unless args[:name]
65
+ args = generate_ability_args(args)
68
66
  ability_cache[args[:name].to_sym] ||= Ability.new args
69
67
  end
70
68
 
@@ -103,8 +101,19 @@ module Indulgence
103
101
  @ability_cache ||= {}
104
102
  end
105
103
 
106
- def check_method_can_be_called(name)
107
- raise AbilityConfigurationError, "#{name} method must respond to call" unless ability.send(name).respond_to? :call
104
+ def self.generate_ability_args(args)
105
+ unless args.kind_of? Hash
106
+ args = {:relationship => args.to_sym}
107
+ end
108
+ args[:name] = generate_ability_name(args)
109
+ return args
110
+ end
111
+
112
+ def self.generate_ability_name(args)
113
+ return args[:name] if args[:name]
114
+ return args.to_s if args[:relationship]
115
+ raise AbilityConfigurationError, "A name is required for each ability"
108
116
  end
117
+
109
118
  end
110
119
  end
@@ -1,14 +1,19 @@
1
1
  module Indulgence
2
- VERSION = "0.0.3"
2
+ VERSION = "0.0.4"
3
3
  end
4
4
 
5
5
  # History
6
6
  # =======
7
7
  #
8
+ # 0.0.4 Simplifies defining abilities
9
+ #
10
+ # Allows and ability to be defined by passing in the name of the
11
+ # association that it refers to.
12
+ #
8
13
  # 0.0.3 Updated to use latest version of standalone_migrations
9
14
  #
10
- # standalone_migrations main has been updated to use branch this app
11
- # has been using. So config in .standalone_migrations now works without
15
+ # Branch this app has been using has been merged into standalone_migrations
16
+ # main. So config in .standalone_migrations now works without
12
17
  # using a specific clone branch.
13
18
  #
14
19
  # 0.0.2 Rebuild with lessons learnt from first usage in host app
Binary file
@@ -5,7 +5,9 @@ class ThingPermission < Indulgence::Permission
5
5
  def abilities
6
6
  {
7
7
  god: default.merge(god),
8
- demigod: default.merge(demigod)
8
+ demigod: default.merge(demigod),
9
+ thief: default.merge(thief),
10
+ friend: default.merge(friend)
9
11
  }
10
12
  end
11
13
 
@@ -35,12 +37,32 @@ class ThingPermission < Indulgence::Permission
35
37
  }
36
38
  end
37
39
 
40
+ def thief
41
+ {
42
+ update: things_they_stole
43
+ }
44
+ end
45
+
46
+ def friend
47
+ {
48
+ update: things_they_borrow
49
+ }
50
+ end
51
+
38
52
  def things_they_own
39
53
  define_ability(
40
- :name => :things_they_own,
41
- :compare_single => lambda {|thing, user| thing.owner_id == user.id},
42
- :filter_many => lambda {|things, user| things.where(:owner_id => user.id)}
54
+ name: :things_they_own,
55
+ compare_single: lambda {|thing, user| thing.owner_id == user.id},
56
+ filter_many: lambda {|things, user| things.where(:owner_id => user.id)}
43
57
  )
44
58
  end
45
59
 
60
+ def things_they_stole
61
+ define_ability(:owner_id)
62
+ end
63
+
64
+ def things_they_borrow
65
+ define_ability(:owner)
66
+ end
67
+
46
68
  end
@@ -2,44 +2,7 @@ require_relative '../../test_helper'
2
2
  require 'ability'
3
3
  require 'exceptions'
4
4
 
5
- module Indulgence
6
- class AbilityTest < Test::Unit::TestCase
7
-
8
- def setup
9
- @attributes = {
10
- name: :foo,
11
- compare_single: lambda {|thing, entity| true},
12
- filter_many: lambda {|entity| nil}
13
- }
14
- end
15
-
16
- def test_equality
17
- ability = Ability.new(@attributes)
18
- other_ability = Ability.new(@attributes)
19
- assert(ability == other_ability, "#{ability} == #{other_ability} should return true")
20
- assert_equal(ability, other_ability)
21
- end
22
-
23
- def test_name_absence_raises_error
24
- @attributes.delete(:name)
25
- assert_initiation_raises_error
26
- end
27
-
28
- def test_indulge_must_respond_to_call
29
- @attributes[:compare_single] = true
30
- assert_initiation_raises_error
31
- end
32
-
33
- def test_indulgence_must_respond_to_call
34
- @attributes[:filter_many] = true
35
- assert_initiation_raises_error
36
- end
37
-
38
- def assert_initiation_raises_error
39
- assert_raise AbilityConfigurationError do
40
- Ability.new(@attributes)
41
- end
42
- end
43
-
44
- end
45
- end
5
+ require_relative 'ability_tests/with_lambdas'
6
+ require_relative 'ability_tests/method_only'
7
+
8
+
@@ -0,0 +1,38 @@
1
+
2
+ module Indulgence
3
+ module AbilityTests
4
+ class MethodOnly < Test::Unit::TestCase
5
+
6
+ def setup
7
+ @attributes = {
8
+ name: :foo,
9
+ relationship: :author
10
+ }
11
+ end
12
+
13
+ def test_equality
14
+ ability = Ability.new(@attributes)
15
+ other_ability = Ability.new(@attributes)
16
+ assert(ability == other_ability, "#{ability} == #{other_ability} should return true")
17
+ assert_equal(ability, other_ability)
18
+ end
19
+
20
+ def test_name_absence_raises_error
21
+ @attributes.delete :name
22
+ assert_initiation_raises_error
23
+ end
24
+
25
+ def test_lack_of_entity_id_method
26
+ @attributes.delete :relationship
27
+ assert_initiation_raises_error
28
+ end
29
+
30
+ def assert_initiation_raises_error
31
+ assert_raise AbilityConfigurationError do
32
+ Ability.new(@attributes)
33
+ end
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,45 @@
1
+
2
+
3
+ module Indulgence
4
+ module AbilityTests
5
+ class WithLambdas < Test::Unit::TestCase
6
+
7
+ def setup
8
+ @attributes = {
9
+ name: :foo,
10
+ compare_single: lambda {|thing, entity| true},
11
+ filter_many: lambda {|entity| nil}
12
+ }
13
+ end
14
+
15
+ def test_equality
16
+ ability = Ability.new(@attributes)
17
+ other_ability = Ability.new(@attributes)
18
+ assert(ability == other_ability, "#{ability} == #{other_ability} should return true")
19
+ assert_equal(ability, other_ability)
20
+ end
21
+
22
+ def test_name_absence_raises_error
23
+ @attributes.delete(:name)
24
+ assert_initiation_raises_error
25
+ end
26
+
27
+ def test_indulge_must_respond_to_call
28
+ @attributes[:compare_single] = true
29
+ assert_initiation_raises_error
30
+ end
31
+
32
+ def test_indulgence_must_respond_to_call
33
+ @attributes[:filter_many] = true
34
+ assert_initiation_raises_error
35
+ end
36
+
37
+ def assert_initiation_raises_error
38
+ assert_raise AbilityConfigurationError do
39
+ Ability.new(@attributes)
40
+ end
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -7,8 +7,10 @@ class ThingTest < Test::Unit::TestCase
7
7
  def setup
8
8
  @god = Role.create(:name => 'god')
9
9
  @demigod = Role.create(:name => 'demigod')
10
+ @thief = Role.create(:name => 'thief')
11
+ @friend = Role.create(:name => 'friend')
10
12
  @owner = User.create(:name => 'Mary')
11
- @thing = Thing.create(:name => 'Stuff', :owner_id => @owner.id)
13
+ @thing = Thing.create(:name => 'Stuff', :owner => @owner)
12
14
  end
13
15
 
14
16
 
@@ -26,36 +28,51 @@ class ThingTest < Test::Unit::TestCase
26
28
 
27
29
  def test_indulge_by_god
28
30
  make_second_thing
29
- @owner.update_attribute(:role_id, @god.id)
31
+ @owner.update_attribute(:role, @god)
30
32
  assert_equal(true, @thing.indulge?(@owner, :read))
31
- @thing.indulge?(@owner, :delete)
32
33
  assert_equal(true, @thing.indulge?(@owner, :delete))
33
34
  assert_equal(true, @other_thing.indulge?(@owner, :delete))
34
35
  end
35
36
 
36
37
  def test_indulgence_by_god
37
38
  make_second_thing
38
- @owner.update_attribute(:role_id, @god.id)
39
+ @owner.update_attribute(:role, @god)
39
40
  assert_equal(Thing.all, Thing.indulgence(@owner, :delete))
40
41
  end
41
42
 
42
43
  def test_indulge_by_demigod
43
44
  make_second_thing
44
- @owner.update_attribute(:role_id, @demigod.id)
45
+ @owner.update_attribute(:role, @demigod)
45
46
  assert_equal(true, @thing.indulge?(@owner, :read))
46
47
  assert_equal(true, @thing.indulge?(@owner, :delete))
47
48
  assert_equal(false, @other_thing.indulge?(@owner, :delete))
48
49
  end
49
50
 
51
+ def test_indule_via_entity_id
52
+ make_second_thing
53
+ @owner.update_attribute(:role, @thief)
54
+ assert_equal(true, @thing.indulge?(@owner, :read))
55
+ assert_equal(true, @thing.indulge?(@owner, :update))
56
+ assert_equal(false, @thing.indulge?(@owner, :delete))
57
+ end
58
+
59
+ def test_indule_via_entity_association
60
+ make_second_thing
61
+ @owner.update_attribute(:role, @friend)
62
+ assert_equal(true, @thing.indulge?(@owner, :read))
63
+ assert_equal(true, @thing.indulge?(@owner, :update))
64
+ assert_equal(false, @thing.indulge?(@owner, :delete))
65
+ end
66
+
50
67
  def test_indulge_other_thing
51
- other_thing = OtherThing.create(:name => 'Other Stuff', :owner_id => @owner.id)
68
+ other_thing = OtherThing.create(:name => 'Other Stuff', :owner => @owner)
52
69
  assert_equal(true, other_thing.indulge?(@owner, :read))
53
70
  assert_equal(false, other_thing.indulge?(@owner, :delete))
54
71
  end
55
72
 
56
73
  def test_indulgence
57
74
  make_second_thing
58
- @owner.update_attribute(:role_id, @demigod.id)
75
+ @owner.update_attribute(:role, @demigod)
59
76
  assert_equal(Thing.order('id'), Thing.indulgence(@owner, :read).order('id'))
60
77
  assert_equal(Thing.order('id'), Thing.indulgence(@user, :read).order('id'))
61
78
  assert_equal([@thing], Thing.indulgence(@owner, :delete))
@@ -64,6 +81,18 @@ class ThingTest < Test::Unit::TestCase
64
81
  end
65
82
  end
66
83
 
84
+ def test_indulgence_via_entity_id_method
85
+ make_second_thing
86
+ @owner.update_attribute(:role, @thief)
87
+ assert_equal(Thing.where(:owner_id => @owner.id), Thing.indulgence(@owner, :update))
88
+ end
89
+
90
+ def test_indulgence_via_entity_association
91
+ make_second_thing
92
+ @owner.update_attribute(:role, @friend)
93
+ assert_equal(Thing.where(:owner_id => @owner.id).all, Thing.indulgence(@owner, :update).all)
94
+ end
95
+
67
96
  def test_indulgence_with_unspecified_ability
68
97
  assert_raise ActiveRecord::RecordNotFound do
69
98
  Thing.indulgence(@owner, :unspecified)
@@ -72,7 +101,7 @@ class ThingTest < Test::Unit::TestCase
72
101
 
73
102
  def test_find
74
103
  make_second_thing
75
- @owner.update_attribute(:role_id, @demigod.id)
104
+ @owner.update_attribute(:role, @demigod)
76
105
  assert_equal(@thing, Thing.indulgence(@owner, :delete).find(@thing.id))
77
106
  assert_raise ActiveRecord::RecordNotFound do
78
107
  assert_equal(@thing, Thing.indulgence(@user, :delete).find(@thing.id))
@@ -88,7 +117,7 @@ class ThingTest < Test::Unit::TestCase
88
117
 
89
118
  def test_aliased_filter_many_method
90
119
  make_second_thing
91
- @owner.update_attribute(:role_id, @demigod.id)
120
+ @owner.update_attribute(:role, @demigod)
92
121
  assert_equal(Thing.order('id'), Thing.permitted(@owner, :read).order('id'))
93
122
  assert_equal(Thing.order('id'), Thing.permitted(@user, :read).order('id'))
94
123
  assert_equal([@thing], Thing.permitted(@owner, :delete))
@@ -99,7 +128,7 @@ class ThingTest < Test::Unit::TestCase
99
128
 
100
129
  def make_second_thing
101
130
  @user = User.create(:name => 'Clive')
102
- @other_thing = Thing.create(:name => 'Debris', :owner_id => @user.id)
131
+ @other_thing = Thing.create(:name => 'Debris', :owner => @user)
103
132
  end
104
133
 
105
134
  def teardown
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: indulgence
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-15 00:00:00.000000000 Z
12
+ date: 2013-05-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -79,6 +79,8 @@ files:
79
79
  - test/units/role_test.rb
80
80
  - test/units/indulgence/permission_test.rb
81
81
  - test/units/indulgence/ability_test.rb
82
+ - test/units/indulgence/ability_tests/method_only.rb
83
+ - test/units/indulgence/ability_tests/with_lambdas.rb
82
84
  - test/units/user_test.rb
83
85
  - test/units/thing_permission_test.rb
84
86
  - test/units/thing_test.rb
@@ -121,6 +123,8 @@ test_files:
121
123
  - test/units/role_test.rb
122
124
  - test/units/indulgence/permission_test.rb
123
125
  - test/units/indulgence/ability_test.rb
126
+ - test/units/indulgence/ability_tests/method_only.rb
127
+ - test/units/indulgence/ability_tests/with_lambdas.rb
124
128
  - test/units/user_test.rb
125
129
  - test/units/thing_permission_test.rb
126
130
  - test/units/thing_test.rb