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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/Gemfile +7 -0
- data/README.md +55 -23
- data/lib/cancancan/model_adapters/mongoid_adapter.rb +156 -0
- data/lib/cancancan/model_additions.rb +30 -0
- data/lib/mongoid_ability.rb +12 -12
- data/lib/mongoid_ability/ability.rb +67 -26
- data/lib/mongoid_ability/find_lock.rb +71 -0
- data/lib/mongoid_ability/lock.rb +25 -16
- data/lib/mongoid_ability/locks_decorator.rb +45 -0
- data/lib/mongoid_ability/owner.rb +23 -3
- data/lib/mongoid_ability/subject.rb +10 -22
- data/lib/mongoid_ability/version.rb +1 -1
- data/test/cancancan/model_adapters/mongoid_adapter_options_test.rb +102 -0
- data/test/cancancan/model_adapters/mongoid_adapter_test.rb +207 -0
- data/test/mongoid_ability/ability_basic_benchmark.rb +30 -0
- data/test/mongoid_ability/ability_basic_test.rb +44 -13
- data/test/mongoid_ability/ability_marshal_test.rb +17 -0
- data/test/mongoid_ability/ability_options_test.rb +93 -0
- data/test/mongoid_ability/ability_test.rb +87 -106
- data/test/mongoid_ability/find_lock_test.rb +67 -0
- data/test/mongoid_ability/lock_test.rb +32 -40
- data/test/mongoid_ability/owner_locks_test.rb +14 -21
- data/test/mongoid_ability/owner_test.rb +4 -14
- data/test/mongoid_ability/subject_test.rb +32 -58
- data/test/support/test_classes/my_lock.rb +8 -13
- data/test/support/test_classes/my_owner.rb +13 -15
- data/test/support/test_classes/my_role.rb +9 -11
- data/test/support/test_classes/my_subject.rb +16 -9
- data/test/test_helper.rb +12 -2
- metadata +18 -25
- data/lib/mongoid_ability/accessible_query_builder.rb +0 -64
- data/lib/mongoid_ability/resolve_default_locks.rb +0 -17
- data/lib/mongoid_ability/resolve_inherited_locks.rb +0 -35
- data/lib/mongoid_ability/resolve_locks.rb +0 -12
- data/lib/mongoid_ability/resolve_owner_locks.rb +0 -35
- data/lib/mongoid_ability/resolver.rb +0 -24
- data/lib/mongoid_ability/values_for_accessible_query.rb +0 -74
- data/test/mongoid_ability/ability_syntactic_sugar_test.rb +0 -32
- data/test/mongoid_ability/accessible_query_builder_test.rb +0 -119
- data/test/mongoid_ability/can_options_test.rb +0 -17
- data/test/mongoid_ability/resolve_default_locks_test.rb +0 -41
- data/test/mongoid_ability/resolve_inherited_locks_test.rb +0 -50
- data/test/mongoid_ability/resolve_owner_locks_test.rb +0 -56
- data/test/mongoid_ability/resolver_test.rb +0 -23
- data/test/mongoid_ability/subject_accessible_by_test.rb +0 -147
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module MongoidAbility
|
4
|
+
describe FindLock do
|
5
|
+
let(:owner) { MyOwner.new }
|
6
|
+
|
7
|
+
describe 'default lock' do
|
8
|
+
before { MySubject.default_lock MyLock, :read, true }
|
9
|
+
|
10
|
+
it { FindLock.call(owner, :read, MySubject).must_equal MySubject.default_locks.for_action(:read).first }
|
11
|
+
it { FindLock.call(owner, :read, MySubject1).must_equal MySubject.default_locks.for_action(:read).first }
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'inherited lock' do
|
15
|
+
before { MySubject.default_lock MyLock, :read, true }
|
16
|
+
|
17
|
+
let(:lock) { MyLock.new(subject_type: MySubject, action: :read, outcome: false) }
|
18
|
+
let(:role) { MyRole.new(my_locks: [lock]) }
|
19
|
+
let(:owner) { MyOwner.new(my_roles: [role]) }
|
20
|
+
|
21
|
+
it { FindLock.call(owner, :read, MySubject).must_equal lock }
|
22
|
+
|
23
|
+
describe 'conflicting locks' do
|
24
|
+
let(:lock_1) { MyLock.new(subject_type: MySubject, action: :read, outcome: true) }
|
25
|
+
let(:lock_2) { MyLock.new(subject_type: MySubject, action: :read, outcome: false) }
|
26
|
+
|
27
|
+
let(:role_1) { MyRole.new(my_locks: [lock_1]) }
|
28
|
+
let(:role_2) { MyRole.new(my_locks: [lock_2]) }
|
29
|
+
let(:owner) { MyOwner.new(my_roles: [role_1, role_2]) }
|
30
|
+
|
31
|
+
it { FindLock.call(owner, :read, MySubject).must_equal lock_1 }
|
32
|
+
end
|
33
|
+
|
34
|
+
describe 'id lock' do
|
35
|
+
let(:my_subject) { MySubject.new }
|
36
|
+
let(:other_subject) { MySubject.new }
|
37
|
+
let(:lock_1) { MyLock.new(subject_type: my_subject.model_name, subject_id: my_subject.id, action: :read, outcome: false) }
|
38
|
+
let(:lock_2) { MyLock.new(subject_type: other_subject.model_name, subject_id: other_subject.id, action: :read, outcome: false) }
|
39
|
+
let(:role) { MyRole.new(my_locks: [lock_1, lock_2]) }
|
40
|
+
let(:owner) { MyOwner.new(my_roles: [role]) }
|
41
|
+
|
42
|
+
it { FindLock.call(owner, :read, MySubject).must_equal MySubject.default_locks.for_action(:read).first }
|
43
|
+
it { FindLock.call(owner, :read, my_subject.class, my_subject.id).must_equal lock_1 }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'owned lock' do
|
48
|
+
before { MySubject.default_lock MyLock, :read, true }
|
49
|
+
|
50
|
+
let(:lock) { MyLock.new(subject_type: MySubject, action: :read, outcome: false) }
|
51
|
+
let(:owner) { MyOwner.new(my_locks: [lock]) }
|
52
|
+
|
53
|
+
it { FindLock.call(owner, :read, MySubject).must_equal lock }
|
54
|
+
|
55
|
+
describe 'id lock' do
|
56
|
+
let(:my_subject) { MySubject.new }
|
57
|
+
let(:other_subject) { MySubject.new }
|
58
|
+
let(:lock_1) { MyLock.new(subject_type: my_subject.model_name, subject_id: my_subject.id, action: :read, outcome: false) }
|
59
|
+
let(:lock_2) { MyLock.new(subject_type: other_subject.model_name, subject_id: other_subject.id, action: :read, outcome: false) }
|
60
|
+
let(:owner) { MyOwner.new(my_locks: [lock_1, lock_2]) }
|
61
|
+
|
62
|
+
it { FindLock.call(owner, :read, MySubject).must_equal MySubject.default_locks.for_action(:read).first }
|
63
|
+
it { FindLock.call(owner, :read, my_subject.class, my_subject.id).must_equal lock_1 }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -3,13 +3,10 @@ require 'test_helper'
|
|
3
3
|
module MongoidAbility
|
4
4
|
describe Lock do
|
5
5
|
subject { MyLock.new }
|
6
|
+
|
6
7
|
let(:my_subject) { MySubject.new }
|
7
8
|
let(:inherited_lock) { MyLock1.new }
|
8
9
|
|
9
|
-
let(:my_subject_default_locks) { [MyLock.new(subject_type: MySubject, action: :read, outcome: true)] }
|
10
|
-
let(:my_subject_1_default_locks) { [MyLock.new(subject_type: MySubject1, action: :false, outcome: true)] }
|
11
|
-
let(:my_subject_2_default_locks) { [] }
|
12
|
-
|
13
10
|
# ---------------------------------------------------------------------
|
14
11
|
|
15
12
|
it { subject.must_respond_to :action }
|
@@ -55,47 +52,42 @@ module MongoidAbility
|
|
55
52
|
|
56
53
|
# ---------------------------------------------------------------------
|
57
54
|
|
55
|
+
describe 'sort' do
|
56
|
+
let(:lock0) { MyLock.new(subject_type: MySubject, action: :update, outcome: false) }
|
57
|
+
let(:lock1) { MyLock.new(subject_type: MySubject, action: :update, outcome: true) }
|
58
|
+
let(:lock2) { MyLock.new(subject_type: MySubject, action: :read, outcome: false, options: { override: true }) }
|
59
|
+
let(:lock3) { MyLock.new(subject_type: MySubject, action: :read, outcome: false) }
|
60
|
+
let(:lock4) { MyLock.new(subject_type: MySubject, action: :read, outcome: true) }
|
61
|
+
|
62
|
+
let(:owner) { MyOwner.new(my_locks: [lock1, lock2, lock3, lock4]) }
|
63
|
+
|
64
|
+
let(:sorted_locks) { owner.my_locks.sort(&Lock.sort) }
|
65
|
+
|
66
|
+
it { sorted_locks[0].must_equal lock4 }
|
67
|
+
it { sorted_locks[3].must_equal lock1 }
|
68
|
+
end
|
69
|
+
|
70
|
+
# ---------------------------------------------------------------------
|
71
|
+
|
58
72
|
describe '#inherited_outcome' do
|
59
|
-
|
60
|
-
let(:subject_type_lock) { MyLock.new(subject_type: MySubject, action: :read, outcome: false) }
|
61
|
-
let(:subject_lock) { MyLock.new(subject: my_subject, action: :read, outcome: true) }
|
62
|
-
let(:owner) { MyOwner.new(my_locks: [subject_type_lock, subject_lock]) }
|
73
|
+
before(:all) { MySubject.default_lock MyLock, :read, true }
|
63
74
|
|
64
|
-
|
65
|
-
|
66
|
-
|
75
|
+
let(:owner) { MyOwner.new(my_locks: [
|
76
|
+
MyLock.new(subject_type: MySubject, action: :read, outcome: false),
|
77
|
+
MyLock.new(subject: my_subject, action: :read, outcome: true)
|
78
|
+
]) }
|
67
79
|
|
68
|
-
|
69
|
-
MySubject.stub :default_locks, my_subject_default_locks do
|
70
|
-
MySubject1.stub :default_locks, my_subject_1_default_locks do
|
71
|
-
MySubject2.stub :default_locks, my_subject_2_default_locks do
|
72
|
-
@ability.can?(:read, my_subject).must_equal true
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
80
|
+
let(:ability) { Ability.new(owner) }
|
77
81
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
MySubject2.stub :default_locks, my_subject_2_default_locks do
|
82
|
-
subject_lock.inherited_outcome.must_equal false
|
83
|
-
subject_type_lock.inherited_outcome.must_equal true
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
82
|
+
let(:subject_type_lock) { owner.my_locks.detect(&:class_lock?) }
|
83
|
+
let(:subject_lock) { owner.my_locks.detect(&:id_lock?) }
|
84
|
+
let(:default_lock) { MySubject.default_locks.detect { |l| l.action == :read } }
|
88
85
|
|
89
|
-
it
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
lock.inherited_outcome.must_equal true
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
86
|
+
it { ability.can?(:read, my_subject).must_equal true }
|
87
|
+
|
88
|
+
it { subject_lock.inherited_outcome.must_equal false }
|
89
|
+
it { subject_type_lock.inherited_outcome.must_equal true }
|
90
|
+
it { default_lock.inherited_outcome.must_equal true }
|
99
91
|
end
|
100
92
|
end
|
101
93
|
end
|
@@ -4,34 +4,27 @@ module MongoidAbility
|
|
4
4
|
describe 'owner locks test' do
|
5
5
|
subject { MySubject.new }
|
6
6
|
|
7
|
-
let(:subject_lock) { MyLock.new(action: :read, subject: subject, outcome: false) }
|
8
|
-
let(:subject_type_lock) { MyLock.new(action: :read, subject_type: subject.class, outcome: false) }
|
9
|
-
|
10
7
|
let(:owner) { MyOwner.new }
|
11
8
|
let(:ability) { Ability.new(owner) }
|
12
9
|
|
13
|
-
let(:default_locks) { [MyLock.new(subject_type: MySubject, action: :read, outcome: true)] }
|
14
|
-
|
15
10
|
describe 'when lock for subject' do
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
11
|
+
let(:subject_lock) { MyLock.new(action: :read, subject: subject, outcome: false) }
|
12
|
+
let(:owner) { MyOwner.new(my_locks: [subject_lock]) }
|
13
|
+
|
14
|
+
before(:all) { MySubject.default_lock MyLock, :read, true }
|
15
|
+
|
16
|
+
it { ability.can?(:read, subject.class).must_equal true }
|
17
|
+
it { ability.can?(:read, subject).must_equal false }
|
24
18
|
end
|
25
19
|
|
26
20
|
describe 'when lock for subject type' do
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
end
|
21
|
+
let(:subject_type_lock) { MyLock.new(action: :read, subject_type: subject.class, outcome: false) }
|
22
|
+
let(:owner) { MyOwner.new(my_locks: [subject_type_lock]) }
|
23
|
+
|
24
|
+
before(:all) { MySubject.default_lock MyLock, :read, true }
|
25
|
+
|
26
|
+
it { ability.can?(:read, subject.class).must_equal false }
|
27
|
+
it { ability.can?(:read, subject).must_equal false }
|
35
28
|
end
|
36
29
|
end
|
37
30
|
end
|
@@ -13,9 +13,7 @@ module MongoidAbility
|
|
13
13
|
subject.run_callbacks(:save)
|
14
14
|
end
|
15
15
|
|
16
|
-
it
|
17
|
-
subject.my_locks.sort.must_equal [closed_lock].sort
|
18
|
-
end
|
16
|
+
it { subject.my_locks.sort(&Lock.sort).must_equal [closed_lock].sort(&Lock.sort) }
|
19
17
|
|
20
18
|
describe 'locks relation' do
|
21
19
|
it { subject.class.locks_relation_name.must_equal :my_locks }
|
@@ -29,17 +27,9 @@ module MongoidAbility
|
|
29
27
|
let(:other_lock) { MyLock.new(action: :update, subject: MySubject.new) }
|
30
28
|
let(:owner) { MyOwner.new(my_locks: [subject_type_lock, subject_lock]) }
|
31
29
|
|
32
|
-
it
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
it 'returns true when lock for same action & subject' do
|
37
|
-
owner.has_lock?(subject_lock).must_equal true
|
38
|
-
end
|
39
|
-
|
40
|
-
it 'returns false for non existing lock' do
|
41
|
-
owner.has_lock?(other_lock).must_equal false
|
42
|
-
end
|
30
|
+
it { owner.has_lock?(subject_type_lock).must_equal true }
|
31
|
+
it { owner.has_lock?(subject_lock).must_equal true }
|
32
|
+
it { owner.has_lock?(other_lock).must_equal false }
|
43
33
|
end
|
44
34
|
end
|
45
35
|
end
|
@@ -2,79 +2,53 @@ require 'test_helper'
|
|
2
2
|
|
3
3
|
module MongoidAbility
|
4
4
|
describe Subject do
|
5
|
-
let(:my_subject_default_locks) { [] }
|
6
|
-
let(:my_subject_1_default_locks) { [] }
|
7
|
-
let(:my_subject_2_default_locks) { [] }
|
8
|
-
|
9
5
|
describe '.default_lock' do
|
10
|
-
|
11
|
-
MySubject.
|
12
|
-
|
13
|
-
|
14
|
-
MySubject.default_lock MyLock, :read, true
|
15
|
-
MySubject.default_lock MyLock, :update, true
|
16
|
-
MySubject1.default_lock MyLock1, :update, false
|
17
|
-
|
18
|
-
MySubject.default_locks.map(&:action).map(&:to_s).sort.must_equal %w(read update)
|
19
|
-
MySubject1.default_locks.map(&:action).map(&:to_s).sort.must_equal %w(update)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
6
|
+
before(:all) do
|
7
|
+
MySubject.default_lock MyLock, :read, true
|
8
|
+
MySubject.default_lock MyLock, :update, true
|
9
|
+
MySubject1.default_lock MyLock1, :update, false
|
23
10
|
end
|
11
|
+
|
12
|
+
it { MySubject.default_locks.map(&:action).map(&:to_s).sort.must_equal %w(read update) }
|
13
|
+
it { MySubject1.default_locks.map(&:action).map(&:to_s).sort.must_equal %w(update) }
|
24
14
|
end
|
25
15
|
|
26
16
|
describe 'prevents conflicts' do
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
MySubject.default_lock MyLock1, :read, false
|
32
|
-
MySubject1.default_lock MyLock, :read, true
|
33
|
-
|
34
|
-
MySubject.default_locks.count { |l| l.action == :read }.must_equal 1
|
35
|
-
end
|
36
|
-
end
|
17
|
+
describe 'multiple locks for same action' do
|
18
|
+
before(:all) do
|
19
|
+
MySubject.default_lock MyLock1, :read, false
|
20
|
+
MySubject1.default_lock MyLock, :read, true
|
37
21
|
end
|
38
|
-
end
|
39
22
|
|
40
|
-
|
41
|
-
|
42
|
-
MySubject1.stub :default_locks, my_subject_1_default_locks do
|
43
|
-
MySubject2.stub :default_locks, my_subject_2_default_locks do
|
44
|
-
MySubject.default_lock MyLock1, :read, false
|
45
|
-
MySubject1.default_lock MyLock, :read, true
|
23
|
+
it { MySubject.default_locks.count { |l| l.action == :read }.must_equal 1 }
|
24
|
+
end
|
46
25
|
|
47
|
-
|
48
|
-
|
49
|
-
|
26
|
+
describe 'replace existing locks with new attributes' do
|
27
|
+
before(:all) do
|
28
|
+
MySubject.default_lock MyLock1, :read, false
|
29
|
+
MySubject1.default_lock MyLock, :read, true
|
50
30
|
end
|
51
|
-
end
|
52
31
|
|
53
|
-
|
54
|
-
|
55
|
-
MySubject1.stub :default_locks, my_subject_1_default_locks do
|
56
|
-
MySubject2.stub :default_locks, my_subject_2_default_locks do
|
57
|
-
MySubject.default_lock MyLock1, :read, false
|
58
|
-
MySubject1.default_lock MyLock, :read, true
|
32
|
+
it { MySubject.default_locks.detect { |l| l.action == :read }.outcome.must_equal false }
|
33
|
+
end
|
59
34
|
|
60
|
-
|
61
|
-
|
62
|
-
|
35
|
+
describe 'replaces existing locks with new one' do
|
36
|
+
before(:all) do
|
37
|
+
MySubject.default_lock MyLock1, :read, false
|
38
|
+
MySubject1.default_lock MyLock, :read, true
|
63
39
|
end
|
64
|
-
end
|
65
40
|
|
66
|
-
|
67
|
-
|
68
|
-
MySubject1.stub :default_locks, my_subject_1_default_locks do
|
69
|
-
MySubject2.stub :default_locks, my_subject_2_default_locks do
|
70
|
-
MySubject.default_lock MyLock1, :read, false
|
71
|
-
MySubject1.default_lock MyLock, :read, true
|
41
|
+
it { MySubject.default_locks.detect { |l| l.action == :read }.class.must_equal MyLock1 }
|
42
|
+
end
|
72
43
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
44
|
+
describe 'replaces superclass locks' do
|
45
|
+
before(:all) do
|
46
|
+
MySubject.default_lock MyLock1, :read, false
|
47
|
+
MySubject1.default_lock MyLock, :read, true
|
77
48
|
end
|
49
|
+
|
50
|
+
it { MySubject1.default_locks.count.must_equal 1 }
|
51
|
+
it { MySubject1.default_locks.detect { |l| l.action == :read }.outcome.must_equal true }
|
78
52
|
end
|
79
53
|
end
|
80
54
|
|
@@ -1,15 +1,10 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
include MongoidAbility::Lock
|
6
|
-
|
7
|
-
embedded_in :owner, polymorphic: true, touch: true
|
8
|
-
end
|
1
|
+
class MyLock
|
2
|
+
include Mongoid::Document
|
3
|
+
include Mongoid::Timestamps
|
4
|
+
include MongoidAbility::Lock
|
9
5
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
6
|
+
embedded_in :owner, polymorphic: true, touch: true
|
7
|
+
end
|
8
|
+
|
9
|
+
class MyLock1 < MyLock
|
15
10
|
end
|
@@ -1,21 +1,19 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
include MongoidAbility::Owner
|
1
|
+
class MyOwner
|
2
|
+
include Mongoid::Document
|
3
|
+
include Mongoid::Timestamps
|
4
|
+
include MongoidAbility::Owner
|
6
5
|
|
7
|
-
|
8
|
-
|
6
|
+
embeds_many :my_locks, class_name: 'MyLock', as: :owner
|
7
|
+
has_and_belongs_to_many :my_roles
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
end
|
13
|
-
|
14
|
-
def self.inherit_from_relation_name
|
15
|
-
:my_roles
|
16
|
-
end
|
9
|
+
def self.locks_relation_name
|
10
|
+
:my_locks
|
17
11
|
end
|
18
12
|
|
19
|
-
|
13
|
+
def self.inherit_from_relation_name
|
14
|
+
:my_roles
|
20
15
|
end
|
21
16
|
end
|
17
|
+
|
18
|
+
class MyOwner1 < MyOwner
|
19
|
+
end
|
@@ -1,16 +1,14 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
include MongoidAbility::Owner
|
1
|
+
class MyRole
|
2
|
+
include Mongoid::Document
|
3
|
+
include MongoidAbility::Owner
|
5
4
|
|
6
|
-
|
7
|
-
|
5
|
+
embeds_many :my_locks, class_name: 'MyLock', as: :owner
|
6
|
+
has_and_belongs_to_many :my_owners
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
end
|
8
|
+
def self.locks_relation_name
|
9
|
+
:my_locks
|
12
10
|
end
|
11
|
+
end
|
13
12
|
|
14
|
-
|
15
|
-
end
|
13
|
+
class MyRole1 < MyRole
|
16
14
|
end
|
@@ -1,12 +1,19 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
include MongoidAbility::Subject
|
5
|
-
end
|
1
|
+
class MySubject
|
2
|
+
include Mongoid::Document
|
3
|
+
include MongoidAbility::Subject
|
6
4
|
|
7
|
-
|
8
|
-
|
5
|
+
field :str_val, type: String
|
6
|
+
field :override, type: Boolean, default: false
|
7
|
+
end
|
8
|
+
|
9
|
+
class MySubject1 < MySubject
|
10
|
+
end
|
11
|
+
|
12
|
+
class MySubject11 < MySubject
|
13
|
+
end
|
14
|
+
|
15
|
+
class MySubject2 < MySubject1
|
16
|
+
end
|
9
17
|
|
10
|
-
|
11
|
-
end
|
18
|
+
class MySubject21 < MySubject11
|
12
19
|
end
|