[Rails] 엔음의 단점을 생각하세요.
개시하다
나는 Rails로 응용 프로그램을 구축할 때 자주 enum을 사용한다고 생각한다.
그러나 DB 속 엔움형은'SQL 역모드'라는 책 속'31Flavors'의 역모드로 널리 알려져 있다.
이번에는 엔움의 장점과 단점에 대해 생각하고 정리해 봤다.
전제 조건
이 보도의 결론
이른바 enum
열거형입니다.예를 들어'남=0','여=1'로 DB에 저장하려고 할 때 사용한다.
예를 들어, User 클래스가 있다고 가정합니다.속성은 "name(이름)", "gender(성별)", "prefecture(도도부현)"입니다.
gender와 prefecture를 enum으로 정의합니다.
app/models/user.rb
class User < ApplicationRecord
enum gender: { man: 0, woman: 1 }, _prefix: true
enum prefecture: { tokyo: 0, osaka: 1, nagoya: 2 }, _prefix: true
end
enum에서 정의한 값을 DB에 수치로 저장합니다.SELECT * FROM users;
| id | name | gender | prefecture | created_at | updated_at |
|----|-------|--------|------------|----------------------------|----------------------------|
| 1 | user1 | 0 | 0 | 2021-09-23 15:37:30.781431 | 2021-09-23 15:37:30.781431 |
| 2 | user2 | 0 | 1 | 2021-09-23 15:37:30.790889 | 2021-09-23 15:37:30.790889 |
| 3 | user3 | 1 | 0 | 2021-09-23 15:37:30.801230 | 2021-09-23 15:37:30.801230 |
| 4 | user4 | 1 | 2 | 2021-09-23 15:37:30.809486 | 2021-09-23 15:37:30.809486 |
rails console에서 수치를 얻으면enum에서 정의한 man
나 woman
등 문자열로 수치를 검색할 수 있습니다.Rails 측부터 사용할 때
User.first.gender
# => "man"
User.first.prefecture
# => "tokyo"
# 男性のUser一覧を取得したいとき
User.gender_man
# 東京都のUser一覧を取得したいとき
User.prefecture_tokyo
# 東京都以外のUser一覧を取得したいとき
User.not_prefecture_tokyo
# 定義されている性別一覧を取得したいとき
User.genders
# => {"man"=>0, "woman"=>1}
# 定義されている都道府県一覧を取得したいとき
User.prefectures
# => {"tokyo"=>0, "osaka"=>1, "nagoya"=>2}
해결책
enum 이외의 설치 방법으로'enum이 정의한 부분을 다른 표로 잘라내는 방법'이 있다.
이번 예에서 우리는genders표와prefectures표를 만들었다.
사용자 테이블
| id | name | gender_id | prefecture_id | created_at | updated_at |
|----|--------|-----------|---------------|----------------------------|----------------------------|
| 1 | user1 | 1 | 1 | 2021-09-23 15:37:30.930411 | 2021-09-23 15:37:30.930411 |
| 2 | user2 | 1 | 2 | 2021-09-23 15:37:30.939223 | 2021-09-23 15:37:30.939223 |
| 3 | user3 | 2 | 1 | 2021-09-23 15:37:30.950641 | 2021-09-23 15:37:30.950641 |
| 4 | user4 | 2 | 3 | 2021-09-23 15:37:30.959353 | 2021-09-23 15:37:30.959353 |
genders표| id | name | created_at | updated_at |
|----|-------|----------------------------|----------------------------|
| 1 | man | 2021-09-23 15:37:30.863884 | 2021-09-23 15:37:30.863884 |
| 2 | woman | 2021-09-23 15:37:30.871785 | 2021-09-23 15:37:30.871785 |
prefectures 표| id | name | created_at | updated_at |
|----|--------|----------------------------|----------------------------|
| 1 | tokyo | 2021-09-23 15:37:30.887358 | 2021-09-23 15:37:30.887358 |
| 2 | osaka | 2021-09-23 15:37:30.896177 | 2021-09-23 15:37:30.896177 |
| 3 | nagoya | 2021-09-23 15:37:30.905950 | 2021-09-23 15:37:30.905950 |
app/models/user.rbclass User < ApplicationRecord
belongs_to :gender
belongs_to :prefecture
end
Rails 측부터 사용 시User.first.gender.name
# => "man"
User.first.prefecture.name
# => "tokyo"
# 男性のUser一覧を取得したいとき
User.joins(:gender).merge(Gender.where(name: 'man'))
# 東京都のUser一覧を取得したいとき
User.joins(:prefecture).merge(Prefecture.where(name: 'tokyo'))
# 東京都以外のUser一覧を取得したいとき
User.joins(:prefecture).merge(Prefecture.where.not(name: 'tokyo'))
# 定義されている性別一覧を取得したいとき
Gender.all
# => [#<Gender:0x00007f94e6d10ee8
id: 1,
name: "man",
created_at: Thu, 23 Sep 2021 15:37:30 UTC +00:00,
updated_at: Thu, 23 Sep 2021 15:37:30 UTC +00:00>,
#<Gender:0x00007f94e6d10e20
id: 2,
name: "woman",
created_at: Thu, 23 Sep 2021 15:37:30 UTC +00:00,
updated_at: Thu, 23 Sep 2021 15:37:30 UTC +00:00>]
# 定義されている都道府県一覧を取得したいとき
Prefecture.all
# => [#<Prefecture:0x00007f94e76eea00
id: 1,
name: "tokyo",
created_at: Thu, 23 Sep 2021 15:37:30 UTC +00:00,
updated_at: Thu, 23 Sep 2021 15:37:30 UTC +00:00>,
#<Prefecture:0x00007f94e76ee8e8
id: 2,
name: "osaka",
created_at: Thu, 23 Sep 2021 15:37:30 UTC +00:00,
updated_at: Thu, 23 Sep 2021 15:37:30 UTC +00:00>,
#<Prefecture:0x00007f94e76ee7f8
id: 3,
name: "nagoya",
created_at: Thu, 23 Sep 2021 15:37:30 UTC +00:00,
updated_at: Thu, 23 Sep 2021 15:37:30 UTC +00:00>]
num의 장점과 단점
그렇다면 상술한 enum을 다른 표의 방법으로 비교할 때의 enum의 장점과 단점을 고려해 보자.
num의 장점
장점책상의join과select 횟수를 줄이다
사용자의 성별을 문자열로 비교합니다.
enum에서 정의된 상황
User.first.gender
User Load (6.4ms) SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
=> "man"
다른 테이블로 잘라낸 경우User.first.gender.name
User Load (2.8ms) SELECT `users`.* FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
Gender Load (3.0ms) SELECT `genders`.* FROM `genders` WHERE `genders`.`id` = 1 LIMIT 1
=> "man"
다른 표로 자르면 Gender 표에서도 SELECT 문장이 실행되기 때문에 얻을 때의 성능이 떨어진다.장점Rails 측에서 편리한 방법 제공
enum만 정의하면
User.gender_man
와User.not_gender_man
같은 편리한 방법으로 설치 원가를 줄일 수 있다.장점소스 코드만 수정하면 새 값을 추가할 수 있습니다
DB에 대한 INSERT 없이 소스 코드를 수정하면 새로운 enum 값을 추가할 수 있어 상황에 따라 값을 늘리는 운용 비용이 줄어들 수 있다.
만약 다른 표로 잘라낼 때 수치를 추가해야 한다
num의 단점
결점DB에서만 enum의 정의를 확인할 수 없음
결점DB 등급에 제약을 가하기 어렵다
결점다른 표와 관계를 맺을 수 없기 때문에 귀일화할 수 없다
개인적으로 엔움을 사용하는 것이 결점이 될 가능성이 가장 높은 부분이라고 생각합니다.반대로 다른 표에서 관계를 맺어 규범화할 필요가 없다면 엔움으로 정의하는 것은 문제없다.
예를 들어 업무상 다음과 같은 요건이 발생했다.
이번에는 json형을 사용해서 실현하기로 결정했다.(설명을 생략하지만 제이슨형 혈액형은 고통스러운 일이 많기 때문에 기본적으로 사용하지 말아야 한다)
Notification 테이블
| id | title | body | display_condition | created_at | updated_at |
|----|---------------|------------|-----------------------------------------|----------------------------|----------------------------|
| 1 | notifiaction1 | 大事なお知らせです1 | {"genders": [], "prefectures": []} | 2021-09-23 15:37:30.825805 | 2021-09-23 15:37:30.825805 |
| 2 | notifiaction2 | 大事なお知らせです2 | {"genders": [0], "prefectures": []} | 2021-09-23 15:37:30.833728 | 2021-09-23 15:37:30.833728 |
| 3 | notifiaction3 | 大事なお知らせです3 | {"genders": [], "prefectures": [0, 1]} | 2021-09-23 15:37:30.841235 | 2021-09-23 15:37:30.841235 |
| 4 | notifiaction4 | 大事なお知らせです4 | {"genders": [1], "prefectures": [0, 1]} | 2021-09-23 15:37:30.849499 | 2021-09-23 15:37:30.849499 |
app/models/notification.rb의 한 예class Notification < ApplicationRecord
class << self
def displayable_to(user)
notification_ids = Notification.all.select { |notification| notification.displayable_to?(user) }.map(&:id)
Notification.where(id: notification_ids)
end
end
def displayable_to?(user)
(display_condition['genders'].blank? || display_condition['genders'].include?(user.gender_before_type_cast)) &&
(display_condition['prefectures'].blank? || display_condition['prefectures'].include?(user.prefecture_before_type_cast))
end
end
(※ 표시조건을 선택하지 않으면 빈 진열에 진입)이렇게 실현하는 것도 가능하지만 몇 가지 문제가 있다.
"genders": [3]
라는 값을 입력할 수 있습니다.notification 테이블
| id | title | body | created_at | updated_at |
|----|---------------|------------|----------------------------|----------------------------|
| 1 | notifiaction1 | 大事なお知らせです1 | 2021-09-23 15:37:30.825805 | 2021-09-23 15:37:30.825805 |
| 2 | notifiaction2 | 大事なお知らせです2 | 2021-09-23 15:37:30.833728 | 2021-09-23 15:37:30.833728 |
| 3 | notifiaction3 | 大事なお知らせです3 | 2021-09-23 15:37:30.841235 | 2021-09-23 15:37:30.841235 |
| 4 | notifiaction4 | 大事なお知らせです4 | 2021-09-23 15:37:30.849499 | 2021-09-23 15:37:30.849499 |
notification_genders 테이블| id | notification_id | gender_id | created_at | updated_at |
|----|-----------------|-----------|----------------------------|----------------------------|
| 1 | 2 | 1 | 2021-09-23 15:37:31.028341 | 2021-09-23 15:37:31.028341 |
| 2 | 4 | 2 | 2021-09-23 15:37:31.037408 | 2021-09-23 15:37:31.037408 |
notification_prefectures 테이블| id | notification_id | prefecture_id | created_at | updated_at |
|----|-----------------|---------------|----------------------------|----------------------------|
| 1 | 3 | 1 | 2021-09-23 15:37:31.055711 | 2021-09-23 15:37:31.055711 |
| 2 | 3 | 2 | 2021-09-23 15:37:31.064761 | 2021-09-23 15:37:31.064761 |
| 3 | 4 | 1 | 2021-09-23 15:37:31.075442 | 2021-09-23 15:37:31.075442 |
| 4 | 4 | 2 | 2021-09-23 15:37:31.083863 | 2021-09-23 15:37:31.083863 |
app/models/notification.rb의 한 예class Notification < ApplicationRecord
has_many :notification_genders
has_many :notification_prefectures
has_many :genders, through: :notification_genders
has_many :prefectures, through: :notification_prefectures
class << self
def displayable_to(user)
Notification.left_joins(:notification_genders, :notification_prefectures)
.merge(NotificationGender.where(id: nil).or(NotificationGender.where(gender_id: user.gender_id)))
.merge(NotificationPrefecture.where(id: nil).or(NotificationPrefecture.where(prefecture_id: user.prefecture_id)))
.order(:id)
end
end
end
(디스플레이 조건을 선택하지 않으면 notification genders나 notification prefectures 테이블의 기록을 만들지 않습니다)위에서 설명한 대로 구현하여 DB의 정규화를 유지합니다.id에 외부 키 제한을 붙이면gender가 존재하지 않습니다id 저장도 방지할 수 있습니다.
기본적으로 RDB는 정규화되어야 한다. 이것은 선인의 경험 법칙이기 때문에 이번 예에서 다른 표로 제시한 설치 방법을 선택해야 한다.
언제 엔um을 사용하고 언제 책상으로 자를까요
라일스의 엔음은 준비하는 방법이 많아 편리하기 때문에 기본적으로 엔음으로 한다는 방침에는 문제가 없다.
단, 아래의 상황은 다른 탁자로 잘라야 한다.
최후
이번에는 엔움의 장점과 단점에 대해 생각했다.다음은 이 보도의 결론의 재발표이다.
그리고 더 좋은 해결 방안과 의견이 있다면 저에게 말씀해 주시면 기쁩니다.
Reference
이 문제에 관하여([Rails] 엔음의 단점을 생각하세요.), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://zenn.dev/shuhei_takada/articles/be002838ae379a텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)