mongoid_ability 1.0.0 → 2.0.0

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/Gemfile +7 -0
  4. data/README.md +55 -23
  5. data/lib/cancancan/model_adapters/mongoid_adapter.rb +156 -0
  6. data/lib/cancancan/model_additions.rb +30 -0
  7. data/lib/mongoid_ability.rb +12 -12
  8. data/lib/mongoid_ability/ability.rb +67 -26
  9. data/lib/mongoid_ability/find_lock.rb +71 -0
  10. data/lib/mongoid_ability/lock.rb +25 -16
  11. data/lib/mongoid_ability/locks_decorator.rb +45 -0
  12. data/lib/mongoid_ability/owner.rb +23 -3
  13. data/lib/mongoid_ability/subject.rb +10 -22
  14. data/lib/mongoid_ability/version.rb +1 -1
  15. data/test/cancancan/model_adapters/mongoid_adapter_options_test.rb +102 -0
  16. data/test/cancancan/model_adapters/mongoid_adapter_test.rb +207 -0
  17. data/test/mongoid_ability/ability_basic_benchmark.rb +30 -0
  18. data/test/mongoid_ability/ability_basic_test.rb +44 -13
  19. data/test/mongoid_ability/ability_marshal_test.rb +17 -0
  20. data/test/mongoid_ability/ability_options_test.rb +93 -0
  21. data/test/mongoid_ability/ability_test.rb +87 -106
  22. data/test/mongoid_ability/find_lock_test.rb +67 -0
  23. data/test/mongoid_ability/lock_test.rb +32 -40
  24. data/test/mongoid_ability/owner_locks_test.rb +14 -21
  25. data/test/mongoid_ability/owner_test.rb +4 -14
  26. data/test/mongoid_ability/subject_test.rb +32 -58
  27. data/test/support/test_classes/my_lock.rb +8 -13
  28. data/test/support/test_classes/my_owner.rb +13 -15
  29. data/test/support/test_classes/my_role.rb +9 -11
  30. data/test/support/test_classes/my_subject.rb +16 -9
  31. data/test/test_helper.rb +12 -2
  32. metadata +18 -25
  33. data/lib/mongoid_ability/accessible_query_builder.rb +0 -64
  34. data/lib/mongoid_ability/resolve_default_locks.rb +0 -17
  35. data/lib/mongoid_ability/resolve_inherited_locks.rb +0 -35
  36. data/lib/mongoid_ability/resolve_locks.rb +0 -12
  37. data/lib/mongoid_ability/resolve_owner_locks.rb +0 -35
  38. data/lib/mongoid_ability/resolver.rb +0 -24
  39. data/lib/mongoid_ability/values_for_accessible_query.rb +0 -74
  40. data/test/mongoid_ability/ability_syntactic_sugar_test.rb +0 -32
  41. data/test/mongoid_ability/accessible_query_builder_test.rb +0 -119
  42. data/test/mongoid_ability/can_options_test.rb +0 -17
  43. data/test/mongoid_ability/resolve_default_locks_test.rb +0 -41
  44. data/test/mongoid_ability/resolve_inherited_locks_test.rb +0 -50
  45. data/test/mongoid_ability/resolve_owner_locks_test.rb +0 -56
  46. data/test/mongoid_ability/resolver_test.rb +0 -23
  47. data/test/mongoid_ability/subject_accessible_by_test.rb +0 -147
@@ -25,8 +25,18 @@ DatabaseCleaner.orm = :mongoid
25
25
  DatabaseCleaner.strategy = :truncation
26
26
 
27
27
  class MiniTest::Spec
28
- before(:each) { DatabaseCleaner.start }
29
- after(:each) { DatabaseCleaner.clean }
28
+ before(:each) do
29
+ DatabaseCleaner.start
30
+ end
31
+
32
+ after(:each) do
33
+ [ MySubject,
34
+ MySubject1, MySubject2,
35
+ MySubject11, MySubject21
36
+ ].each(&:reset_default_locks!)
37
+
38
+ DatabaseCleaner.clean
39
+ end
30
40
  end
31
41
 
32
42
  class Object
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mongoid_ability
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tomas Celizna
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-08 00:00:00.000000000 Z
11
+ date: 2018-01-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cancancan
@@ -153,33 +153,28 @@ files:
153
153
  - LICENSE.txt
154
154
  - README.md
155
155
  - Rakefile
156
+ - lib/cancancan/model_adapters/mongoid_adapter.rb
157
+ - lib/cancancan/model_additions.rb
156
158
  - lib/mongoid_ability.rb
157
159
  - lib/mongoid_ability/ability.rb
158
- - lib/mongoid_ability/accessible_query_builder.rb
160
+ - lib/mongoid_ability/find_lock.rb
159
161
  - lib/mongoid_ability/lock.rb
162
+ - lib/mongoid_ability/locks_decorator.rb
160
163
  - lib/mongoid_ability/owner.rb
161
- - lib/mongoid_ability/resolve_default_locks.rb
162
- - lib/mongoid_ability/resolve_inherited_locks.rb
163
- - lib/mongoid_ability/resolve_locks.rb
164
- - lib/mongoid_ability/resolve_owner_locks.rb
165
- - lib/mongoid_ability/resolver.rb
166
164
  - lib/mongoid_ability/subject.rb
167
- - lib/mongoid_ability/values_for_accessible_query.rb
168
165
  - lib/mongoid_ability/version.rb
169
166
  - mongoid_ability.gemspec
167
+ - test/cancancan/model_adapters/mongoid_adapter_options_test.rb
168
+ - test/cancancan/model_adapters/mongoid_adapter_test.rb
169
+ - test/mongoid_ability/ability_basic_benchmark.rb
170
170
  - test/mongoid_ability/ability_basic_test.rb
171
- - test/mongoid_ability/ability_syntactic_sugar_test.rb
171
+ - test/mongoid_ability/ability_marshal_test.rb
172
+ - test/mongoid_ability/ability_options_test.rb
172
173
  - test/mongoid_ability/ability_test.rb
173
- - test/mongoid_ability/accessible_query_builder_test.rb
174
- - test/mongoid_ability/can_options_test.rb
174
+ - test/mongoid_ability/find_lock_test.rb
175
175
  - test/mongoid_ability/lock_test.rb
176
176
  - test/mongoid_ability/owner_locks_test.rb
177
177
  - test/mongoid_ability/owner_test.rb
178
- - test/mongoid_ability/resolve_default_locks_test.rb
179
- - test/mongoid_ability/resolve_inherited_locks_test.rb
180
- - test/mongoid_ability/resolve_owner_locks_test.rb
181
- - test/mongoid_ability/resolver_test.rb
182
- - test/mongoid_ability/subject_accessible_by_test.rb
183
178
  - test/mongoid_ability/subject_test.rb
184
179
  - test/support/expectations.rb
185
180
  - test/support/test_classes/my_lock.rb
@@ -213,19 +208,17 @@ specification_version: 4
213
208
  summary: Custom Ability class that allows CanCanCan authorization library store permissions
214
209
  in MongoDB via the Mongoid gem.
215
210
  test_files:
211
+ - test/cancancan/model_adapters/mongoid_adapter_options_test.rb
212
+ - test/cancancan/model_adapters/mongoid_adapter_test.rb
213
+ - test/mongoid_ability/ability_basic_benchmark.rb
216
214
  - test/mongoid_ability/ability_basic_test.rb
217
- - test/mongoid_ability/ability_syntactic_sugar_test.rb
215
+ - test/mongoid_ability/ability_marshal_test.rb
216
+ - test/mongoid_ability/ability_options_test.rb
218
217
  - test/mongoid_ability/ability_test.rb
219
- - test/mongoid_ability/accessible_query_builder_test.rb
220
- - test/mongoid_ability/can_options_test.rb
218
+ - test/mongoid_ability/find_lock_test.rb
221
219
  - test/mongoid_ability/lock_test.rb
222
220
  - test/mongoid_ability/owner_locks_test.rb
223
221
  - test/mongoid_ability/owner_test.rb
224
- - test/mongoid_ability/resolve_default_locks_test.rb
225
- - test/mongoid_ability/resolve_inherited_locks_test.rb
226
- - test/mongoid_ability/resolve_owner_locks_test.rb
227
- - test/mongoid_ability/resolver_test.rb
228
- - test/mongoid_ability/subject_accessible_by_test.rb
229
222
  - test/mongoid_ability/subject_test.rb
230
223
  - test/support/expectations.rb
231
224
  - test/support/test_classes/my_lock.rb
@@ -1,64 +0,0 @@
1
- module MongoidAbility
2
- class AccessibleQueryBuilder < Struct.new(:base_class, :ability, :action, :options)
3
- def self.call(*args)
4
- new(*args).call
5
- end
6
-
7
- def initialize(base_class, ability, action, options = {})
8
- super(base_class, ability, action, options)
9
- end
10
-
11
- # =====================================================================
12
-
13
- def call
14
- base_class.criteria.where(conditions)
15
- end
16
-
17
- def conditions
18
- { '$and' => [{ '$or' => [closed_types_condition, open_types_and_ids_condition] }, closed_ids_condition] }
19
- end
20
-
21
- private # =============================================================
22
-
23
- def closed_types_condition
24
- { type_key => { '$nin' => values.closed_types.uniq } }
25
- end
26
-
27
- def open_types_and_ids_condition
28
- {
29
- type_key => { '$in' => values.open_types_and_ids.map(&:type).uniq },
30
- id_key => { '$in' => values.open_types_and_ids.map(&:id).uniq }
31
- }
32
- end
33
-
34
- def closed_ids_condition
35
- { id_key => { '$nin' => values.closed_ids.uniq } }
36
- end
37
-
38
- # ---------------------------------------------------------------------
39
-
40
- def values
41
- @values ||= base_class.values_for_accessible_query(ability, action, options)
42
- end
43
-
44
- # ---------------------------------------------------------------------
45
-
46
- def base_class_superclass
47
- @base_class_superclass ||= (base_class.ancestors_with_default_locks.last || base_class)
48
- end
49
-
50
- # ---------------------------------------------------------------------
51
-
52
- def prefix
53
- options.fetch(:prefix, nil)
54
- end
55
-
56
- def id_key
57
- [prefix, '_id'].reject(&:blank?).join.to_sym
58
- end
59
-
60
- def type_key
61
- [prefix, '_type'].reject(&:blank?).join.to_sym
62
- end
63
- end
64
- end
@@ -1,17 +0,0 @@
1
- module MongoidAbility
2
- class ResolveDefaultLocks < Resolver
3
- def call
4
- closed_lock = default_locks.detect { |l| l.closed?(options) }
5
- return closed_lock if closed_lock
6
-
7
- open_lock = default_locks.detect { |l| l.open?(options) }
8
- return open_lock if open_lock
9
- end
10
-
11
- private
12
-
13
- def default_locks
14
- subject_class.default_locks.select { |l| l.action.to_s == action.to_s }
15
- end
16
- end
17
- end
@@ -1,35 +0,0 @@
1
- module MongoidAbility
2
- class ResolveInheritedLocks < Resolver
3
- def call
4
- owner_lock = resolved_owner_lock
5
- return owner_lock if owner_lock
6
-
7
- if owner.respond_to?(owner.class.inherit_from_relation_name) && !owner.inherit_from_relation.nil?
8
- resolved_inherited_owner_locks = owner.inherit_from_relation.map { |inherited_owner| resolved_inherited_owner_lock(inherited_owner) }.compact
9
-
10
- open_lock = resolved_inherited_owner_locks.detect { |l| l.open?(options) }
11
- return open_lock if open_lock
12
-
13
- closed_lock = resolved_inherited_owner_locks.detect { |l| l.closed?(options) }
14
- return closed_lock if closed_lock
15
- end
16
-
17
- resolved_default_lock
18
- end
19
-
20
- private
21
-
22
- def resolved_owner_lock
23
- @resolved_owner_lock ||= ResolveOwnerLocks.call(owner, action, subject_class, subject_id, options)
24
- end
25
-
26
- def resolved_inherited_owner_lock(inherited_owner)
27
- @resolved_inherited_owner_lock ||= {}
28
- @resolved_inherited_owner_lock[inherited_owner] ||= ResolveOwnerLocks.call(inherited_owner, action, subject_class, subject_id, options)
29
- end
30
-
31
- def resolved_default_lock
32
- @resolved_default_lock ||= ResolveDefaultLocks.call(nil, action, subject_class, nil, options)
33
- end
34
- end
35
- end
@@ -1,12 +0,0 @@
1
- module MongoidAbility
2
- class ResolveLocks < Resolver
3
- def call
4
- lock = nil
5
- subject_class.self_and_ancestors_with_default_locks.each do |cls|
6
- lock = ResolveInheritedLocks.call(owner, action, cls.to_s, subject_id, options)
7
- break unless lock.nil?
8
- end
9
- lock
10
- end
11
- end
12
- end
@@ -1,35 +0,0 @@
1
- # OPTIMIZE: this seems quite expensive
2
-
3
- module MongoidAbility
4
- class ResolveOwnerLocks < Resolver
5
- def call
6
- return unless owner.respond_to?(:locks_relation)
7
-
8
- locks_for_subject_type = owner.locks_relation.for_action(action).for_subject_type(subject_type).cache
9
-
10
- return unless locks_for_subject_type.exists?
11
-
12
- # return lock if owner defines lock for id
13
- if subject_id.present?
14
- id_locks = locks_for_subject_type.id_locks.for_subject_id(subject_id).cache
15
-
16
- closed_lock = id_locks.detect { |l| l.closed?(options) }
17
- return closed_lock if closed_lock
18
-
19
- open_lock = id_locks.detect { |l| l.open?(options) }
20
- return open_lock if open_lock
21
- end
22
-
23
- # return lock if owner defines lock for subject_type
24
- class_locks = locks_for_subject_type.class_locks.cache
25
-
26
- closed_lock = class_locks.class_locks.detect { |l| l.closed?(options) }
27
- return closed_lock if closed_lock
28
-
29
- open_lock = class_locks.class_locks.detect { |l| l.open?(options) }
30
- return open_lock if open_lock
31
-
32
- nil
33
- end
34
- end
35
- end
@@ -1,24 +0,0 @@
1
- module MongoidAbility
2
- class Resolver < Struct.new(:owner, :action, :subject_type, :subject_id, :options)
3
- attr_reader :subject_class
4
-
5
- def self.call(*args)
6
- new(*args).call
7
- end
8
-
9
- def initialize(owner, action, subject_type, subject_id = nil, options = {})
10
- super(owner, action, subject_type, subject_id, options)
11
-
12
- @subject_class = subject_type.to_s.constantize
13
-
14
- raise StandardError, "#{subject_type} class does not have default locks" unless @subject_class.respond_to?(:default_locks)
15
- raise StandardError, "#{subject_type} class does not have default lock for :#{action} action" unless @subject_class.self_and_ancestors_with_default_locks.any? do |cls|
16
- cls.default_locks.any? { |l| l.action == action }
17
- end
18
- end
19
-
20
- def call
21
- raise NotImplementedError
22
- end
23
- end
24
- end
@@ -1,74 +0,0 @@
1
- module MongoidAbility
2
- class ValuesForAccessibleQuery < Struct.new(:base_class, :ability, :action, :options)
3
- def self.call(*args)
4
- new(*args).call
5
- end
6
-
7
- def initialize(base_class, ability, action, options = {})
8
- super(base_class, ability, action, options)
9
- end
10
-
11
- # =====================================================================
12
-
13
- def call
14
- closed_types = [] # [cls]
15
- open_types_and_ids = [] # OpenStruct.new(type: …, id: …)
16
- closed_ids = [] # [id]
17
-
18
- base_class_and_descendants.each do |cls|
19
- closed_types << cls.to_s if ability.cannot?(action, cls, options)
20
- id_locks(cls).each do |lock|
21
- if ability.can?(action, cls.new(_id: lock.subject_id), options)
22
- open_types_and_ids << OpenStruct.new(type: cls.to_s, id: lock.subject_id)
23
- else
24
- closed_ids << lock.subject_id
25
- end
26
- end
27
- end
28
-
29
- OpenStruct.new(
30
- closed_types: closed_types,
31
- open_types_and_ids: open_types_and_ids,
32
- closed_ids: closed_ids
33
- )
34
- end
35
-
36
- private # =============================================================
37
-
38
- def base_class_and_descendants
39
- @base_class_and_descendants ||= [base_class].concat(base_class.descendants)
40
- end
41
-
42
- # ---------------------------------------------------------------------
43
-
44
- def id_locks(cls)
45
- (Array(owner_id_locks_for_subject_type(cls)) + Array(inherited_from_relation_ids_locks_for_subject_type(cls))).flatten
46
- end
47
-
48
- def owner_id_locks_for_subject_type(cls)
49
- @owner_id_locks_for_subject_type ||= {}
50
- @owner_id_locks_for_subject_type[cls] ||= owner.locks_relation.id_locks.for_action(action).for_subject_type(cls.to_s).cache
51
- end
52
-
53
- def inherited_from_relation_ids_locks_for_subject_type(cls)
54
- return [] unless inherited_from_relation
55
- @inherited_from_relation_ids_locks_for_subject_type ||= {}
56
- @inherited_from_relation_ids_locks_for_subject_type[cls] ||= inherited_from_relation.collect do |o|
57
- o.locks_relation.id_locks.for_action(action).for_subject_type(cls.to_s).cache
58
- end.flatten
59
- end
60
-
61
- # ---------------------------------------------------------------------
62
-
63
- def owner
64
- @owner ||= ability.owner
65
- end
66
-
67
- # ---------------------------------------------------------------------
68
-
69
- def inherited_from_relation
70
- return unless owner.respond_to?(owner.class.inherit_from_relation_name)
71
- owner.inherit_from_relation
72
- end
73
- end
74
- end
@@ -1,32 +0,0 @@
1
- require 'test_helper'
2
-
3
- module MongoidAbility
4
- describe 'syntactic sugar' do
5
- let(:read_lock) { MyLock.new(subject_type: MySubject, action: :read, outcome: false) }
6
- let(:owner) { MyRole.new(my_locks: [read_lock]) }
7
- let(:ability) { Ability.new(owner) }
8
- let(:options) { { x: 1 } }
9
-
10
- let(:default_locks) { [MyLock.new(action: :read, outcome: true)] }
11
-
12
- it 'owner can?' do
13
- MySubject.stub :default_locks, default_locks do
14
- [MySubject].select(&ability.can_read(options)).must_equal []
15
- [MySubject].select(&ability.can_read?(options)).must_equal []
16
-
17
- ability.can_read(MySubject, options).must_equal false
18
- ability.can_read?(MySubject, options).must_equal false
19
- end
20
- end
21
-
22
- it 'owner cannot?' do
23
- MySubject.stub :default_locks, default_locks do
24
- [MySubject].select(&ability.cannot_read(options)).must_equal [MySubject]
25
- [MySubject].select(&ability.cannot_read?(options)).must_equal [MySubject]
26
-
27
- ability.cannot_read(MySubject, options).must_equal true
28
- ability.cannot_read?(MySubject, options).must_equal true
29
- end
30
- end
31
- end
32
- end
@@ -1,119 +0,0 @@
1
- require 'test_helper'
2
-
3
- module MongoidAbility
4
- describe AccessibleQueryBuilder do
5
- let(:base_class) { MySubject }
6
- let(:owner) { MyOwner.new }
7
- let(:ability) { Ability.new(owner) }
8
- let(:action) { :read }
9
- let(:options) { Hash.new }
10
-
11
- let(:my_subject_default_locks) { [MyLock.new(subject_type: MySubject, action: :read, outcome: true)] }
12
- let(:my_subject_1_default_locks) { [MyLock.new(subject_type: MySubject1, action: :false, outcome: true)] }
13
- let(:my_subject_2_default_locks) { [] }
14
-
15
- subject { AccessibleQueryBuilder.call(base_class, ability, action, options) }
16
-
17
- it 'returns Mongoid::Criteria' do
18
- MySubject.stub :default_locks, my_subject_default_locks do
19
- MySubject1.stub :default_locks, my_subject_1_default_locks do
20
- MySubject2.stub :default_locks, my_subject_2_default_locks do
21
- subject.must_be_kind_of Mongoid::Criteria
22
- end
23
- end
24
- end
25
- end
26
-
27
- describe 'prefix' do
28
- let(:my_subject) { MySubject.create! }
29
- let(:my_subject_1) { MySubject1.create! }
30
- let(:my_subject_2) { MySubject2.create! }
31
- let(:prefix) { :foo }
32
-
33
- before do
34
- my_subject; my_subject_1
35
- owner.my_locks = [MyLock.new(subject_type: MySubject, action: :read, outcome: false)]
36
- end
37
-
38
- it 'allows to pass prefix' do
39
- MySubject.stub :default_locks, my_subject_default_locks do
40
- MySubject1.stub :default_locks, my_subject_1_default_locks do
41
- MySubject2.stub :default_locks, my_subject_2_default_locks do
42
- selector = AccessibleQueryBuilder.call(base_class, ability, action, prefix: prefix).selector
43
- selector.must_equal('$and' => [{ '$or' => [{ "#{prefix}_type" => { '$nin' => ['MongoidAbility::MySubject', 'MongoidAbility::MySubject1', 'MongoidAbility::MySubject2'] } }, { "#{prefix}_type" => { '$in' => [] }, "#{prefix}_id" => { '$in' => [] } }] }, { "#{prefix}_id" => { '$nin' => [] } }])
44
- end
45
- end
46
- end
47
- end
48
- end
49
-
50
- # ---------------------------------------------------------------------
51
-
52
- describe 'closed_types' do
53
- let(:my_subject) { MySubject.create! }
54
- let(:my_subject_1) { MySubject1.create! }
55
-
56
- before do
57
- my_subject; my_subject_1
58
- owner.my_locks = [MyLock.new(subject_type: MySubject, action: :read, outcome: false)]
59
- end
60
-
61
- it 'does not return subject with that id' do
62
- MySubject.stub :default_locks, my_subject_default_locks do
63
- MySubject1.stub :default_locks, my_subject_1_default_locks do
64
- MySubject2.stub :default_locks, my_subject_2_default_locks do
65
- MySubject.accessible_by(ability, :read).wont_include my_subject
66
- MySubject.accessible_by(ability, :read).wont_include my_subject_1
67
- MySubject1.accessible_by(ability, :read).wont_include my_subject_1
68
- end
69
- end
70
- end
71
- end
72
- end
73
-
74
- describe 'closed_ids' do
75
- let(:my_subject_a) { MySubject.create! }
76
- let(:my_subject_b) { MySubject.create! }
77
-
78
- before do
79
- my_subject_a; my_subject_b
80
- owner.my_locks = [MyLock.new(subject: my_subject_a, action: :read, outcome: false)]
81
- end
82
-
83
- it 'does not return subject with that id' do
84
- MySubject.stub :default_locks, my_subject_default_locks do
85
- MySubject1.stub :default_locks, my_subject_1_default_locks do
86
- MySubject2.stub :default_locks, my_subject_2_default_locks do
87
- MySubject.accessible_by(ability, :read).wont_include my_subject_a
88
- MySubject.accessible_by(ability, :read).must_include my_subject_b
89
- end
90
- end
91
- end
92
- end
93
- end
94
-
95
- describe 'closed_types & open_ids' do
96
- let(:my_subject) { MySubject.create! }
97
- let(:my_subject_1) { MySubject1.create! }
98
-
99
- before do
100
- owner.my_locks = [
101
- MyLock.new(subject_type: MySubject, action: :read, outcome: false),
102
- MyLock.new(subject: my_subject, action: :read, outcome: true),
103
- MyLock.new(subject: my_subject_1, action: :read, outcome: true)
104
- ]
105
- end
106
-
107
- it 'does not return subject with that id' do
108
- MySubject.stub :default_locks, my_subject_default_locks do
109
- MySubject1.stub :default_locks, my_subject_1_default_locks do
110
- MySubject2.stub :default_locks, my_subject_2_default_locks do
111
- MySubject.accessible_by(ability, :read).must_include my_subject
112
- MySubject.accessible_by(ability, :read).must_include my_subject_1
113
- end
114
- end
115
- end
116
- end
117
- end
118
- end
119
- end