indulgence 0.0.3 → 0.0.4

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.
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