validates_overlap 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +5 -0
  5. data/Gemfile +1 -1
  6. data/Gemfile.rails50 +5 -0
  7. data/README.md +16 -2
  8. data/Rakefile +3 -3
  9. data/VERSION +1 -1
  10. data/lib/validates_overlap/locale/es.yml +4 -0
  11. data/lib/validates_overlap/overlap_validator.rb +42 -38
  12. data/spec/dummy/app/models/active_meeting.rb +2 -2
  13. data/spec/dummy/app/models/end_overlap_meeting.rb +2 -2
  14. data/spec/dummy/app/models/meeting.rb +1 -1
  15. data/spec/dummy/app/models/position.rb +4 -4
  16. data/spec/dummy/app/models/secure_meeting.rb +1 -1
  17. data/spec/dummy/app/models/shift.rb +1 -1
  18. data/spec/dummy/app/models/start_end_overlap_meeting.rb +2 -2
  19. data/spec/dummy/app/models/start_overlap_meeting.rb +1 -1
  20. data/spec/dummy/app/models/time_slot.rb +4 -4
  21. data/spec/dummy/app/models/user_meeting.rb +1 -1
  22. data/spec/dummy/config/application.rb +7 -7
  23. data/spec/dummy/config/boot.rb +1 -1
  24. data/spec/dummy/config/environments/development.rb +1 -2
  25. data/spec/dummy/config/environments/production.rb +1 -1
  26. data/spec/dummy/config/initializers/session_store.rb +1 -1
  27. data/spec/dummy/db/schema.rb +52 -54
  28. data/spec/dummy/spec/factories/position.rb +2 -2
  29. data/spec/dummy/spec/factories/user_meeting.rb +2 -2
  30. data/spec/dummy/spec/models/active_meetings_spec.rb +4 -10
  31. data/spec/dummy/spec/models/end_overlap_meeting_spec.rb +26 -29
  32. data/spec/dummy/spec/models/meeting_spec.rb +51 -47
  33. data/spec/dummy/spec/models/position_spec.rb +18 -28
  34. data/spec/dummy/spec/models/secure_meeting_spec.rb +5 -11
  35. data/spec/dummy/spec/models/shift_spec.rb +32 -33
  36. data/spec/dummy/spec/models/start_end_overlap_meeting_spec.rb +26 -29
  37. data/spec/dummy/spec/models/start_overlap_meeting_spec.rb +26 -29
  38. data/spec/dummy/spec/models/time_slot_spec.rb +21 -31
  39. data/spec/dummy/spec/models/user_meeting_spec.rb +16 -19
  40. data/spec/dummy/spec/models/user_spec.rb +3 -5
  41. data/spec/dummy/spec/overlap_validator_spec.rb +12 -14
  42. data/spec/spec_helper.rb +29 -20
  43. data/validates_overlap.gemspec +19 -16
  44. metadata +48 -2
@@ -11,77 +11,75 @@
11
11
  #
12
12
  # It's strongly recommended that you check this file into your version control system.
13
13
 
14
- ActiveRecord::Schema.define(version: 20150707155107) do
15
-
16
- create_table "active_meetings", force: :cascade do |t|
17
- t.date "starts_at"
18
- t.date "ends_at"
19
- t.boolean "is_active"
20
- t.datetime "created_at", null: false
21
- t.datetime "updated_at", null: false
14
+ ActiveRecord::Schema.define(version: 20_150_707_155_107) do
15
+ create_table 'active_meetings', force: :cascade do |t|
16
+ t.date 'starts_at'
17
+ t.date 'ends_at'
18
+ t.boolean 'is_active'
19
+ t.datetime 'created_at', null: false
20
+ t.datetime 'updated_at', null: false
22
21
  end
23
22
 
24
- create_table "end_overlap_meetings", force: :cascade do |t|
25
- t.date "starts_at"
26
- t.date "ends_at"
27
- t.datetime "created_at", null: false
28
- t.datetime "updated_at", null: false
23
+ create_table 'end_overlap_meetings', force: :cascade do |t|
24
+ t.date 'starts_at'
25
+ t.date 'ends_at'
26
+ t.datetime 'created_at', null: false
27
+ t.datetime 'updated_at', null: false
29
28
  end
30
29
 
31
- create_table "meetings", force: :cascade do |t|
32
- t.date "starts_at"
33
- t.date "ends_at"
34
- t.datetime "created_at", null: false
35
- t.datetime "updated_at", null: false
30
+ create_table 'meetings', force: :cascade do |t|
31
+ t.date 'starts_at'
32
+ t.date 'ends_at'
33
+ t.datetime 'created_at', null: false
34
+ t.datetime 'updated_at', null: false
36
35
  end
37
36
 
38
- create_table "positions", force: :cascade do |t|
39
- t.integer "user_id"
40
- t.integer "time_slot_id"
41
- t.datetime "created_at", null: false
42
- t.datetime "updated_at", null: false
37
+ create_table 'positions', force: :cascade do |t|
38
+ t.integer 'user_id'
39
+ t.integer 'time_slot_id'
40
+ t.datetime 'created_at', null: false
41
+ t.datetime 'updated_at', null: false
43
42
  end
44
43
 
45
- create_table "secure_meetings", force: :cascade do |t|
46
- t.date "starts_at"
47
- t.date "ends_at"
48
- t.datetime "created_at"
49
- t.datetime "updated_at"
44
+ create_table 'secure_meetings', force: :cascade do |t|
45
+ t.date 'starts_at'
46
+ t.date 'ends_at'
47
+ t.datetime 'created_at'
48
+ t.datetime 'updated_at'
50
49
  end
51
50
 
52
- create_table "start_end_overlap_meetings", force: :cascade do |t|
53
- t.date "starts_at"
54
- t.date "ends_at"
55
- t.datetime "created_at", null: false
56
- t.datetime "updated_at", null: false
51
+ create_table 'start_end_overlap_meetings', force: :cascade do |t|
52
+ t.date 'starts_at'
53
+ t.date 'ends_at'
54
+ t.datetime 'created_at', null: false
55
+ t.datetime 'updated_at', null: false
57
56
  end
58
57
 
59
- create_table "start_overlap_meetings", force: :cascade do |t|
60
- t.date "starts_at"
61
- t.date "ends_at"
62
- t.datetime "created_at", null: false
63
- t.datetime "updated_at", null: false
58
+ create_table 'start_overlap_meetings', force: :cascade do |t|
59
+ t.date 'starts_at'
60
+ t.date 'ends_at'
61
+ t.datetime 'created_at', null: false
62
+ t.datetime 'updated_at', null: false
64
63
  end
65
64
 
66
- create_table "time_slots", force: :cascade do |t|
67
- t.date "starts_at"
68
- t.date "ends_at"
69
- t.datetime "created_at", null: false
70
- t.datetime "updated_at", null: false
65
+ create_table 'time_slots', force: :cascade do |t|
66
+ t.date 'starts_at'
67
+ t.date 'ends_at'
68
+ t.datetime 'created_at', null: false
69
+ t.datetime 'updated_at', null: false
71
70
  end
72
71
 
73
- create_table "user_meetings", force: :cascade do |t|
74
- t.integer "user_id"
75
- t.date "starts_at"
76
- t.date "ends_at"
77
- t.datetime "created_at", null: false
78
- t.datetime "updated_at", null: false
72
+ create_table 'user_meetings', force: :cascade do |t|
73
+ t.integer 'user_id'
74
+ t.date 'starts_at'
75
+ t.date 'ends_at'
76
+ t.datetime 'created_at', null: false
77
+ t.datetime 'updated_at', null: false
79
78
  end
80
79
 
81
- create_table "users", force: :cascade do |t|
82
- t.string "name"
83
- t.datetime "created_at", null: false
84
- t.datetime "updated_at", null: false
80
+ create_table 'users', force: :cascade do |t|
81
+ t.string 'name'
82
+ t.datetime 'created_at', null: false
83
+ t.datetime 'updated_at', null: false
85
84
  end
86
-
87
85
  end
@@ -1,6 +1,6 @@
1
1
  FactoryGirl.define do
2
2
  factory :position do |u|
3
- u.association(:user, :factory => :user)
4
- u.association(:time_slot, :factory => :time_slot)
3
+ u.association(:user, factory: :user)
4
+ u.association(:time_slot, factory: :time_slot)
5
5
  end
6
6
  end
@@ -1,11 +1,11 @@
1
1
  FactoryGirl.define do
2
- factory :johns_meeting, :class => UserMeeting do |u|
2
+ factory :johns_meeting, class: UserMeeting do |u|
3
3
  u.starts_at '2011-01-05'.to_date
4
4
  u.ends_at '2011-01-08'.to_date
5
5
  u.user_id 1
6
6
  end
7
7
 
8
- factory :peters_meeting, :class => UserMeeting do |u|
8
+ factory :peters_meeting, class: UserMeeting do |u|
9
9
  u.starts_at '2011-01-05'.to_date
10
10
  u.ends_at '2011-01-08'.to_date
11
11
  u.user_id 2
@@ -2,17 +2,11 @@ require_relative '../../../spec_helper'
2
2
  require_relative '../factories/active_meeting'
3
3
 
4
4
  describe ActiveMeeting do
5
-
6
- before(:all) do
7
- ActiveMeeting.delete_all
8
- end
9
-
10
- context "scoping" do
11
- it "should apply scope" do
12
- ActiveMeeting.should_receive(:active).and_call_original
5
+ context 'scoping' do
6
+ it 'should apply scope' do
7
+ expect(ActiveMeeting).to receive(:active).and_call_original
13
8
  active_meeting = ActiveMeeting.new
14
- active_meeting.should be_valid
9
+ expect(active_meeting).to be_valid
15
10
  end
16
11
  end
17
-
18
12
  end
@@ -2,51 +2,48 @@ require_relative '../../../spec_helper'
2
2
  require_relative '../factories/end_overlap_meeting'
3
3
 
4
4
  describe EndOverlapMeeting do
5
-
6
- before(:all) do
7
- EndOverlapMeeting.delete_all
5
+ it 'create meeting' do
6
+ expect do
7
+ FactoryGirl.create(:end_overlap_meeting)
8
+ end.to change(EndOverlapMeeting, :count).by(1)
8
9
  end
9
10
 
10
- it "create meeting" do
11
- lambda {
11
+ context 'Validation with exclude edges ends_at' do
12
+ before do
12
13
  FactoryGirl.create(:end_overlap_meeting)
13
- }.should change(EndOverlapMeeting, :count).by(1)
14
- end
14
+ end
15
15
 
16
- context "Validation with exclude edges ends_at" do
17
- @valid_times = {
18
- "starts at time of end and ends after" => ['2011-01-08'.to_date, '2011-01-19'.to_date]
16
+ @valid_times = {
17
+ 'starts at time of end and ends after' => ['2011-01-08'.to_date, '2011-01-19'.to_date]
19
18
  }
20
19
 
21
20
  @not_valid_times = {
22
- "has same starts_at and ends_at" => ['2011-01-05'.to_date, '2011-01-08'.to_date],
23
- "starts before starts_at and ends after ends_at" => ['2011-01-04'.to_date, '2011-01-09'.to_date],
24
- "starts before starts_at and ends inside" => ['2011-01-04'.to_date, '2011-01-06'.to_date],
25
- "starts inside and ends after ends_at" => ['2011-01-06'.to_date, '2011-01-09'.to_date],
26
- "starts inside and ends inside" => ['2011-01-06'.to_date, '2011-01-07'.to_date],
27
- "starts at same time and ends inside" => ['2011-01-05'.to_date, '2011-01-07'.to_date],
28
- "starts inside and ends at same time" => ['2011-01-06'.to_date, '2011-01-08'.to_date],
29
- "starts before and ends at time of start" => ['2011-01-03'.to_date, '2011-01-05'.to_date],
30
- }
21
+ 'has same starts_at and ends_at' => ['2011-01-05'.to_date, '2011-01-08'.to_date],
22
+ 'starts before starts_at and ends after ends_at' => ['2011-01-04'.to_date, '2011-01-09'.to_date],
23
+ 'starts before starts_at and ends inside' => ['2011-01-04'.to_date, '2011-01-06'.to_date],
24
+ 'starts inside and ends after ends_at' => ['2011-01-06'.to_date, '2011-01-09'.to_date],
25
+ 'starts inside and ends inside' => ['2011-01-06'.to_date, '2011-01-07'.to_date],
26
+ 'starts at same time and ends inside' => ['2011-01-05'.to_date, '2011-01-07'.to_date],
27
+ 'starts inside and ends at same time' => ['2011-01-06'.to_date, '2011-01-08'.to_date],
28
+ 'starts before and ends at time of start' => ['2011-01-03'.to_date, '2011-01-05'.to_date]
29
+ }
31
30
 
32
31
  @not_valid_times.each do |description, time_range|
33
32
  it "is not valid if exists meeting which #{description}" do
34
- meeting = FactoryGirl.build(:end_overlap_meeting, :starts_at => time_range.first, :ends_at => time_range.last)
35
- meeting.should_not be_valid
36
- meeting.errors[:starts_at].should_not be_empty
37
- meeting.errors[:ends_at].should be_empty
33
+ meeting = FactoryGirl.build(:end_overlap_meeting, starts_at: time_range.first, ends_at: time_range.last)
34
+ expect(meeting).not_to be_valid
35
+ expect(meeting.errors[:starts_at]).not_to be_empty
36
+ expect(meeting.errors[:ends_at]).to be_empty
38
37
  end
39
38
  end
40
39
 
41
40
  @valid_times.each do |description, time_range|
42
41
  it "is valid if exists meeting which #{description}" do
43
- meeting = FactoryGirl.build(:end_overlap_meeting, :starts_at => time_range.first, :ends_at => time_range.last)
44
- meeting.should be_valid
45
- meeting.errors[:starts_at].should be_empty
46
- meeting.errors[:ends_at].should be_empty
42
+ meeting = FactoryGirl.build(:end_overlap_meeting, starts_at: time_range.first, ends_at: time_range.last)
43
+ expect(meeting).to be_valid
44
+ expect(meeting.errors[:starts_at]).to be_empty
45
+ expect(meeting.errors[:ends_at]).to be_empty
47
46
  end
48
47
  end
49
-
50
48
  end
51
-
52
49
  end
@@ -2,64 +2,68 @@ require_relative '../../../spec_helper'
2
2
  require_relative '../factories/meeting'
3
3
 
4
4
  describe Meeting do
5
-
6
- context "Validation" do
7
- before(:all) do
8
- Meeting.delete_all
9
- end
10
-
11
- it "create meeting" do
12
- lambda {
5
+ context 'Validation' do
6
+ it 'create meeting' do
7
+ expect do
13
8
  FactoryGirl.create(:meeting)
14
- }.should change(Meeting, :count).by(1)
9
+ end.to change(Meeting, :count).by(1)
15
10
  end
16
11
 
17
- OVERLAP_TIME_RANGES.each do |description, time_range|
18
- it "is not valid if exists meeting which #{description}" do
19
- meeting = FactoryGirl.build(:meeting, :starts_at => time_range.first, :ends_at => time_range.last)
20
- meeting.should_not be_valid
21
- meeting.errors[:starts_at].should_not be_empty
22
- meeting.errors[:ends_at].should be_empty
12
+ context 'simple validation' do
13
+ let!(:existing_meeting) { FactoryGirl.create(:meeting) }
14
+
15
+ OVERLAP_TIME_RANGES.each do |description, time_range|
16
+ it "is not valid if exists meeting which #{description}" do
17
+ meeting = FactoryGirl.build(:meeting, starts_at: time_range.first, ends_at: time_range.last)
18
+ expect(meeting).not_to be_valid
19
+ expect(meeting.errors[:starts_at]).not_to be_empty
20
+ expect(meeting.errors[:ends_at]).to be_empty
21
+ end
23
22
  end
24
- end
25
23
 
26
- it " validate object which has not got overlap" do
27
- meeting = FactoryGirl.build(:meeting, :starts_at => "2011-01-09".to_date, :ends_at => "2011-01-11".to_date)
28
- meeting.should be_valid
29
- meeting.errors[:starts_at].should be_empty
30
- meeting.errors[:ends_at].should be_empty
24
+ it 'validate object which has not got overlap' do
25
+ meeting = FactoryGirl.build(:meeting, starts_at: '2011-01-09'.to_date, ends_at: '2011-01-11'.to_date)
26
+ expect(meeting).to be_valid
27
+ expect(meeting.errors[:starts_at]).to be_empty
28
+ expect(meeting.errors[:ends_at]).to be_empty
29
+
30
+ meeting = FactoryGirl.build(:meeting, starts_at: '2011-01-01'.to_date, ends_at: '2011-01-02'.to_date)
31
+ expect(meeting).to be_valid
32
+ expect(meeting.errors[:starts_at]).to be_empty
33
+ expect(meeting.errors[:ends_at]).to be_empty
34
+ end
31
35
 
32
- meeting = FactoryGirl.build(:meeting, :starts_at => "2011-01-01".to_date, :ends_at => "2011-01-02".to_date)
33
- meeting.should be_valid
34
- meeting.errors[:starts_at].should be_empty
35
- meeting.errors[:ends_at].should be_empty
36
+ describe '@overlapped_records' do
37
+ it 'store the overlapped records' do
38
+ meeting = FactoryGirl.build(:meeting, starts_at: '2011-01-05'.to_date, ends_at: '2011-01-08'.to_date)
39
+ expect(meeting).not_to be_valid
40
+ expect(meeting.instance_variable_get(:@overlapped_records)).to eq [existing_meeting]
41
+ end
42
+ end
36
43
  end
37
44
 
38
- end
39
45
 
40
- context "Validation of endless objects" do
41
- before(:all) do
42
- Meeting.delete_all
43
- FactoryGirl.create(:meeting, :ends_at => nil)
44
- end
46
+ context 'Validation of endless objects' do
47
+ xit 'with overlap object' do
48
+ FactoryGirl.create(:meeting)
49
+ meeting = FactoryGirl.build(:meeting, starts_at: '2011-01-05'.to_date, ends_at: '2011-01-08'.to_date)
50
+ expect(meeting).not_to be_valid
51
+ meeting = FactoryGirl.build(:meeting, starts_at: '2012-01-05'.to_date, ends_at: '2012-01-08'.to_date)
52
+ expect(meeting).not_to be_valid
53
+ meeting = FactoryGirl.build(:meeting, starts_at: '2010-01-05'.to_date, ends_at: '2010-01-08'.to_date)
54
+ expect(meeting).to be_valid
55
+ end
45
56
 
46
- it "with overlap object" do
47
- meeting = FactoryGirl.build(:meeting, :starts_at => "2011-01-05".to_date, :ends_at => "2011-01-08".to_date)
48
- meeting.should_not be_valid
49
- meeting = FactoryGirl.build(:meeting, :starts_at => "2012-01-05".to_date, :ends_at => "2012-01-08".to_date)
50
- meeting.should_not be_valid
51
- meeting = FactoryGirl.build(:meeting, :starts_at => "2010-01-05".to_date, :ends_at => "2010-01-08".to_date)
52
- meeting.should be_valid
53
- end
57
+ it 'with another endless object' do
58
+ FactoryGirl.create(:meeting)
54
59
 
55
- it "with another endless obejct" do
56
- meeting = FactoryGirl.build(:meeting, :starts_at => "2010-01-05".to_date, :ends_at => nil)
57
- meeting.should_not be_valid
58
- meeting = FactoryGirl.build(:meeting, :starts_at => nil, :ends_at => "2010-01-05".to_date)
59
- meeting.should be_valid
60
- meeting = FactoryGirl.build(:meeting, :starts_at => nil, :ends_at => nil)
61
- meeting.should_not be_valid
60
+ meeting = FactoryGirl.build(:meeting, starts_at: '2010-01-05'.to_date, ends_at: nil)
61
+ expect(meeting).not_to be_valid
62
+ meeting = FactoryGirl.build(:meeting, starts_at: nil, ends_at: '2010-01-05'.to_date)
63
+ expect(meeting).to be_valid
64
+ meeting = FactoryGirl.build(:meeting, starts_at: nil, ends_at: nil)
65
+ expect(meeting).not_to be_valid
66
+ end
62
67
  end
63
68
  end
64
-
65
69
  end
@@ -4,41 +4,31 @@ require_relative '../factories/time_slot'
4
4
  require_relative '../factories/user'
5
5
 
6
6
  describe Position do
7
-
8
- before(:all) do
9
- Position.delete_all
10
- TimeSlot.delete_all
11
- User.delete_all
12
- end
13
-
14
- it "create position" do
15
- lambda {
7
+ it 'create position' do
8
+ expect do
16
9
  FactoryGirl.create(:position)
17
- }.should change(Position, :count).by(1)
10
+ end.to change(Position, :count).by(1)
18
11
  end
19
12
 
20
- context "Validation with scope and association" do
21
-
22
- it "is not valid if exists time slot which have position with same person" do
23
- time_slot1 = FactoryGirl.create(:time_slot, :starts_at => "2012-10-11".to_date, :ends_at => "2012-10-13".to_date)
24
- time_slot2 = FactoryGirl.create(:time_slot, :starts_at => "2012-10-12".to_date, :ends_at => "2012-10-13".to_date)
13
+ context 'Validation with scope and association' do
14
+ it 'is not valid if exists time slot which have position with same person' do
15
+ time_slot1 = FactoryGirl.create(:time_slot, starts_at: '2012-10-11'.to_date, ends_at: '2012-10-13'.to_date)
16
+ time_slot2 = FactoryGirl.create(:time_slot, starts_at: '2012-10-12'.to_date, ends_at: '2012-10-13'.to_date)
25
17
  user = FactoryGirl.create(:user)
26
- position1 = FactoryGirl.create(:position, :time_slot => time_slot1, :user => user)
27
- position2 = FactoryGirl.build(:position, :time_slot => time_slot2, :user => user)
28
- position2.should_not be_valid
29
- position2.errors[:base].should_not be_empty
18
+ position1 = FactoryGirl.create(:position, time_slot: time_slot1, user: user)
19
+ position2 = FactoryGirl.build(:position, time_slot: time_slot2, user: user)
20
+ expect(position2).not_to be_valid
21
+ expect(position2.errors[:base]).not_to be_empty
30
22
  end
31
23
 
32
- it "is be valid if exists time slot which have position with same person" do
33
- time_slot1 = FactoryGirl.create(:time_slot, :starts_at => "2012-10-11".to_date, :ends_at => "2012-10-13".to_date)
34
- time_slot2 = FactoryGirl.create(:time_slot, :starts_at => "2012-10-14".to_date, :ends_at => "2012-10-15".to_date)
24
+ it 'is be valid if exists time slot which have position with same person' do
25
+ time_slot1 = FactoryGirl.create(:time_slot, starts_at: '2012-10-11'.to_date, ends_at: '2012-10-13'.to_date)
26
+ time_slot2 = FactoryGirl.create(:time_slot, starts_at: '2012-10-14'.to_date, ends_at: '2012-10-15'.to_date)
35
27
  user = FactoryGirl.create(:user)
36
- position1 = FactoryGirl.create(:position, :time_slot => time_slot1, :user => user)
37
- position2 = FactoryGirl.build(:position, :time_slot => time_slot2, :user => user)
38
- position2.should be_valid
39
- position2.errors[:base].should be_empty
28
+ position1 = FactoryGirl.create(:position, time_slot: time_slot1, user: user)
29
+ position2 = FactoryGirl.build(:position, time_slot: time_slot2, user: user)
30
+ expect(position2).to be_valid
31
+ expect(position2.errors[:base]).to be_empty
40
32
  end
41
-
42
33
  end
43
-
44
34
  end
@@ -2,20 +2,14 @@ require_relative '../../../spec_helper'
2
2
  require_relative '../factories/secure_meeting'
3
3
 
4
4
  describe SecureMeeting do
5
- context "A model with a UUID as a primary key" do
6
- before(:all) do
7
- if SecureMeeting.count >= 1 then
8
- SecureMeeting.delete_all
9
- end
10
- end
11
-
12
- it "updates the relevant record" do
5
+ context 'A model with a UUID as a primary key' do
6
+ it 'updates the relevant record' do
13
7
  securemeeting = FactoryGirl.create(:secure_meeting)
14
8
  securemeeting.starts_at = '2012-01-05'.to_date
15
9
  securemeeting.ends_at = '2012-02-05'.to_date
16
- securemeeting.should be_valid
17
- securemeeting.errors[:starts_at].should be_empty
18
- securemeeting.errors[:ends_at].should be_empty
10
+ expect(securemeeting).to be_valid
11
+ expect(securemeeting.errors[:starts_at]).to be_empty
12
+ expect(securemeeting.errors[:ends_at]).to be_empty
19
13
  end
20
14
  end
21
15
  end