Skip to content

Commit 65090f9

Browse files
committed
전체적인 예외처리 추가
1 parent 4d5fdba commit 65090f9

8 files changed

Lines changed: 538 additions & 254 deletions

File tree

app/models/reservation.rb

Lines changed: 5 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ class Reservation < ApplicationRecord
88
# ========================================
99
before_validation :set_defaults
1010
before_validation :generate_code, on: :create
11-
before_validation :check_and_handle_reservation_conflicts, on: [:create, :update]
1211

1312
# ========================================
1413
# Validations
@@ -19,7 +18,7 @@ class Reservation < ApplicationRecord
1918
validates :priority, presence: true
2019
validates :start_time, presence: true
2120
validates :end_time, presence: true
22-
validate :start_time_before_end_time
21+
validate :start_time_before_end_time
2322

2423
# ========================================
2524
# Default scope (Soft delete)
@@ -29,7 +28,7 @@ class Reservation < ApplicationRecord
2928
# ========================================
3029
# Soft delete method
3130
# ========================================
32-
def soft_delete
31+
def soft_delete(deleted_by: nil)
3332
update(deleted_at: Time.current)
3433
end
3534

@@ -40,7 +39,7 @@ def self.with_deleted
4039
private
4140

4241
# ========================================
43-
# Default values (NEW)
42+
# Default values
4443
# ========================================
4544
def set_defaults
4645
self.priority ||= 0
@@ -54,70 +53,13 @@ def generate_code
5453
end
5554

5655
# ========================================
57-
# Custom validation methods
56+
# Time validation
5857
# ========================================
5958
def start_time_before_end_time
6059
return unless start_time && end_time
6160

6261
if start_time >= end_time
63-
errors.add(:start_time, "시작 시간은 종료 시간보다 빨라야 합니다.")
62+
errors.add(:start_time, "must be earlier than end_time")
6463
end
6564
end
66-
67-
def check_and_handle_reservation_conflicts
68-
# 운영 시간 확인
69-
unless is_available_during_operating_hours?
70-
errors.add(:base, "요청하신 시간은 스터디룸 운영 시간 범위에 포함되지 않거나 휴일입니다.")
71-
throw :abort
72-
end
73-
74-
# 중복 예약 확인
75-
conflicting_reservations = Reservation.where(room_id: room_id)
76-
.where.not(id: id)
77-
.where("start_time < ? AND end_time > ?", end_time, start_time)
78-
79-
conflicting_reservations.each do |existing_reservation|
80-
if priority > existing_reservation.priority
81-
existing_reservation.soft_delete
82-
else
83-
errors.add(:base, "요청하신 시간에 이미 예약이 존재하며, 우선순위가 낮거나 같아 예약할 수 없습니다.")
84-
throw :abort
85-
end
86-
end
87-
end
88-
89-
def is_available_during_operating_hours?
90-
reservation_date = start_time.to_date
91-
start_time_str = start_time.strftime('%H:%M')
92-
end_time_str = end_time.strftime('%H:%M')
93-
94-
# RoomException 우선 확인
95-
room_exception = room.room_exceptions.find_by(holiday_date: reservation_date)
96-
97-
if room_exception.present?
98-
# Case 1: 완전 휴무
99-
if room_exception.opening_time.nil? && room_exception.closing_time.nil?
100-
return false
101-
end
102-
103-
# Case 2: 특별 운영 시간
104-
if room_exception.opening_time.present? && room_exception.closing_time.present?
105-
exception_opening_time_str = room_exception.opening_time.strftime('%H:%M')
106-
exception_closing_time_str = room_exception.closing_time.strftime('%H:%M')
107-
return start_time_str >= exception_opening_time_str && end_time_str <= exception_closing_time_str
108-
end
109-
110-
# Case 3: 이상 데이터
111-
return false
112-
end
113-
114-
# 일반 운영 시간 확인
115-
operating_hour = room.room_operating_hours.find_by(day_of_week: start_time.wday)
116-
return false if operating_hour.nil?
117-
118-
opening_time_str = operating_hour.opening_time.strftime('%H:%M')
119-
closing_time_str = operating_hour.closing_time.strftime('%H:%M')
120-
121-
start_time_str >= opening_time_str && end_time_str <= closing_time_str
122-
end
12365
end

app/models/room.rb

Lines changed: 47 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,53 @@
11
class Room < ApplicationRecord
2-
# 스터디룸 상태 정의: 공실(vacant) 또는 입실(occupied)
3-
# ERD의 'status' ENUM('공실', '입실')에 해당하며, Rails에서는 integer 타입으로 저장됩니다.
4-
5-
# 이게 postman 테스트할 때 계속 문제라서 일단 주석처리함
6-
# 나중에 지문인식이나 얼굴인식으로 입실확인 하는거 넣으면 같이 추가하기로
7-
# enum status: { vacant: 0, occupied: 1 }
8-
9-
# 다른 모델과의 연관 관계 정의
10-
# RoomOperatingHour 모델과 일대다 관계를 가집니다.
11-
has_many :room_operating_hours
12-
# RoomException 모델과 일대다 관계를 가집니다。
13-
has_many :room_exceptions
14-
# Reservation 모델과 일대다 관계를 가집니다.
2+
# 연관 관계
3+
has_many :room_operating_hours, dependent: :destroy
4+
has_many :room_exceptions, dependent: :destroy
155
has_many :reservations
166

17-
# 유효성 검사 (Validations)
18-
# name 컬럼은 필수이며, 최대 100자까지 허용합니다.
7+
# -------------------------------
8+
# 필수값 검증
9+
# -------------------------------
1910
validates :name, presence: true, length: { maximum: 100 }
20-
# minimum_member 컬럼은 필수이며, 0 이상의 정수만 허용합니다.
21-
validates :maximum_member, presence: true, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
22-
# status 컬럼은 필수입니다.
23-
# validates :status, presence: true
11+
validates :maximum_member, presence: true,
12+
numericality: { only_integer: true, greater_than_or_equal_to: 1 }
13+
14+
# room_type이 enum이라면 자동 처리
15+
# enum room_type: { study_room: 0, lecture_room: 1, lab: 2 }, _prefix: :type
16+
#
17+
# enum 타입이라면 다음 validation은 안 넣어도 되는데
18+
# enum이 아니라면 아래처럼 강제해야 함:
19+
# validates :room_type, inclusion: { in: ["study", "lecture", "lab"] }
20+
21+
# -------------------------------
22+
# 이름 중복 검증
23+
# 1) department_id가 같은 방끼리는 같은 name 금지
24+
# 2) department_id가 nil(공용 방)일 경우, 전체에서 name 중복 금지
25+
# -------------------------------
26+
validate :unique_name_within_department
27+
28+
def unique_name_within_department
29+
if department_id.present?
30+
# 같은 학과 내 중복 name 금지
31+
if Room.where(department_id: department_id, name: name).where.not(id: id).exists?
32+
errors.add(:name, "해당 학과에 동일한 이름의 방이 이미 존재합니다.")
33+
end
34+
else
35+
# 공용 방은 전체에서 name 중복 금지
36+
if Room.where(department_id: nil, name: name).where.not(id: id).exists?
37+
errors.add(:name, "공용 방 이름은 전체에서 중복될 수 없습니다.")
38+
end
39+
end
40+
end
41+
42+
# -------------------------------
43+
# 삭제된 방 수정 금지
44+
# -------------------------------
45+
before_update :prevent_update_if_deleted
2446

25-
# department_id는 외부 서비스(유저 서비스)를 참조하므로,
26-
# 여기서는 데이터베이스 레벨의 외래 키 제약 조건은 추가하지 않습니다.
27-
# 유효성 검사는 애플리케이션 로직에서 처리하거나, 필요에 따라 추가할 수 있습니다.
28-
# validates :department_id, presence: true
47+
def prevent_update_if_deleted
48+
if deleted_at.present?
49+
errors.add(:base, "삭제된 방은 수정할 수 없습니다.")
50+
throw :abort
51+
end
52+
end
2953
end

app/models/room_exception.rb

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@ class RoomException < ApplicationRecord
55
# Callbacks
66
after_save :cancel_conflicting_reservations
77

8-
# 유효성 검사 (Validations)
9-
# room_id, holiday_date, created_by는 필수입니다.
8+
# 기본 유효성 검사
109
validates :room_id, presence: true
1110
validates :holiday_date, presence: true
1211
validates :created_by, presence: true
1312

13+
# 추가 유효성 검사
14+
validate :validate_time_rule
15+
validate :validate_time_order
16+
validate :validate_duplicate_date
17+
1418
# 소프트 삭제 (Soft Delete)
15-
# deleted_at 컬럼에 값이 있으면 삭제된 것으로 간주합니다.
1619
default_scope { where(deleted_at: nil) }
1720

1821
def soft_delete
@@ -25,22 +28,62 @@ def self.with_deleted
2528

2629
private
2730

28-
# RoomException 변경 시 충돌되는 예약을 자동으로 취소하는 로직
31+
# ----------------------------------------
32+
# 1) 전체 휴일 vs 부분 휴일 규칙
33+
# ----------------------------------------
34+
def validate_time_rule
35+
# 전체 휴일 (both nil) → OK
36+
return if opening_time.nil? && closing_time.nil?
37+
38+
# 부분 휴일 → 둘 다 있어야 함
39+
if opening_time.present? ^ closing_time.present?
40+
errors.add(:base, "Both opening_time and closing_time must be present for partial holiday")
41+
end
42+
end
43+
44+
# ----------------------------------------
45+
# 2) 시간 순서 검증
46+
# ----------------------------------------
47+
def validate_time_order
48+
return if opening_time.blank? || closing_time.blank?
49+
50+
if opening_time >= closing_time
51+
errors.add(:opening_time, "must be earlier than closing_time")
52+
end
53+
end
54+
55+
# ----------------------------------------
56+
# 3) 동일 날짜 중복 방지 (soft-delete 제외)
57+
# ----------------------------------------
58+
def validate_duplicate_date
59+
return if room_id.blank? || holiday_date.blank?
60+
61+
duplicate = RoomException.with_deleted
62+
.where(room_id: room_id, holiday_date: holiday_date, deleted_at: nil)
63+
.where.not(id: id)
64+
.exists?
65+
66+
if duplicate
67+
errors.add(:holiday_date, "exception already exists for this date")
68+
end
69+
end
70+
71+
# ----------------------------------------
72+
# 기존 예약 자동 취소 로직 (그대로 유지)
73+
# ----------------------------------------
2974
def cancel_conflicting_reservations
30-
# 변경된 예외 날짜에 해당하는 모든 예약을 조회합니다.
3175
reservations_on_date = Reservation.where(
3276
room_id: room_id,
3377
start_time: holiday_date.all_day
3478
)
3579

36-
# Case 1: 완전 휴무일 (운영 시간이 모두 nil)
80+
# Case 1: 완전 휴무일
3781
if opening_time.nil? && closing_time.nil?
38-
# 해당 날짜의 모든 예약을 시스템(deleted_by: 0)에 의해 소프트 삭제합니다.
3982
reservations_on_date.find_each { |reservation| reservation.soft_delete(deleted_by: 0) }
4083
return
4184
end
4285

43-
# Case 2: 특별 운영 시간 지정
86+
# Case 2: 부분 휴무
4487
if opening_time.present? && closing_time.present?
4588
exception_opening_time_str = opening_time.strftime('%H:%M')
4689
exception_closing_time_str = closing_time.strftime('%H:%M')
@@ -49,11 +92,11 @@ def cancel_conflicting_reservations
4992
start_time_str = reservation.start_time.strftime('%H:%M')
5093
end_time_str = reservation.end_time.strftime('%H:%M')
5194

52-
# 예약이 새로운 특별 운영 시간의 범위 밖에 있는 경우, 소프트 삭제합니다.
53-
unless start_time_str >= exception_opening_time_str && end_time_str <= exception_closing_time_str
95+
unless start_time_str >= exception_opening_time_str &&
96+
end_time_str <= exception_closing_time_str
5497
reservation.soft_delete(deleted_by: 0)
5598
end
5699
end
57100
end
58101
end
59-
end
102+
end

app/models/room_operating_hour.rb

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
class RoomOperatingHour < ApplicationRecord
2-
# Room 모델과 다대일 관계를 가집니다.
2+
# Room 모델과 다대일 관계
33
belongs_to :room
44

5-
# 유효성 검사 (Validations)
6-
# room_id, day_of_week, opening_time, closing_time는 필수입니다.
5+
# 기본 유효성 검사
76
validates :room_id, presence: true
8-
validates :day_of_week, presence: true, numericality: { only_integer: true, in: 0..6 } # 0(일)부터 6(토)까지
7+
validates :day_of_week, presence: true, numericality: { only_integer: true, in: 0..6 }
98
validates :opening_time, presence: true
109
validates :closing_time, presence: true
1110

12-
# 소프트 삭제 (Soft Delete)
13-
# deleted_at 컬럼에 값이 있으면 삭제된 것으로 간주합니다.
11+
# 추가 유효성 검사 (예외 처리)
12+
validate :validate_time_order
13+
validate :validate_day_of_week_duplication
14+
15+
# 소프트 삭제
1416
default_scope { where(deleted_at: nil) }
1517

1618
def soft_delete
@@ -20,4 +22,33 @@ def soft_delete
2022
def self.with_deleted
2123
unscope(where: :deleted_at)
2224
end
23-
end
25+
26+
private
27+
28+
# ----------------------------------------
29+
# 1. opening_time < closing_time 검증
30+
# ----------------------------------------
31+
def validate_time_order
32+
return if opening_time.blank? || closing_time.blank?
33+
34+
if opening_time >= closing_time
35+
errors.add(:opening_time, "must be earlier than closing_time")
36+
end
37+
end
38+
39+
# ----------------------------------------
40+
# 2. 동일 room_id + day_of_week 중복 금지
41+
# ----------------------------------------
42+
def validate_day_of_week_duplication
43+
return if room_id.blank? || day_of_week.blank?
44+
45+
duplicate = RoomOperatingHour.with_deleted
46+
.where(room_id: room_id, day_of_week: day_of_week, deleted_at: nil)
47+
.where.not(id: id)
48+
.exists?
49+
50+
if duplicate
51+
errors.add(:day_of_week, "already exists for this room")
52+
end
53+
end
54+
end

0 commit comments

Comments
 (0)