활성 레코드 delegated_type

13673 단어 railsruby
Rails 6에는 Active Record에 delegated_type를 추가하는 새로운 기능이 추가되었습니다. 이 블로그 게시물에서는 실제 프로젝트를 사용하여 Active Record 모델에서 delegated_type를 사용하는 방법을 배우고 사용의 이점에 대해서도 논의합니다.

프로젝트



학교 관리 시스템을 구현합니다. 시스템에는 다른 프로필이 있는 Users가 있습니다. 예: 학생, 교사, 부서장, 지원 직원.

해결책


delegated_type로 넘어가기 전에 다양한 가능한 솔루션을 탐색할 것입니다.

1. 단일 테이블 상속(STI)



이름에서 알 수 있듯이 단일 테이블 상속은 다양한 사용자 프로필의 모든 필드를 결합하여 단일 메가 테이블에 저장합니다. 특정 사용자 프로필에 필드가 필요하지 않은 경우 해당 값은 nil 입니다.

이것이 결과 메가 테이블의 모양입니다.


ID
이름
레코드 유형
등급
부서 이름
서비스 카테고리


1
남자
학생
5


2
암사슴
학생
1



보그
선생님

수학

4
에릭
선생님

영어

5
안나
support_staff

청소

6
베니스
support_staff

관리자


문제
  • 이 테이블은 각 사용자 프로필에 차이가 많고 공통점이 거의 없는 경우 낭비되는 많은 공간으로 드물게 채워집니다.

  • 2. 추상 클래스



    이 방법에서는 추상 클래스를 사용하여 다양한 사용자 프로필 간에 사용되는 공유 코드의 범위를 지정합니다.

    
    class User < ApplicationRecord
      self.abstract_class = true
    
      def say_hello
        "Hello #{name}"
      end
    end
    
    #Schema: students[ id, name, grade ]
    class Student < User
    
    end
    
    #Schema: teachers[ id, name, department ]
    class Teacher < User
    
    end
    

    예시

    
    > Student.create(name: "John", grade: 1)
    > Student.first.say_hello
    "Hello John" 
    
    > Teacher.create(name: "Lisa", department: "English")
    > Teacher.first.say_hello
    "Hello Lisa" 
    

    문제
  • 테이블이 정규화되지 않음
  • Combined Users 의 페이지 매김을 구현하는 것은 불가능합니다. 시도해야 하는 경우에도 적절한 제한 및 오프셋 없이 두 테이블을 동시에 쿼리해야 합니다.

  • 3. 연관이 있는 여러 테이블



    여기에서 하나의 상위 테이블을 사용하여 모든 공통 테이블 속성을 추출하고 Active Record 연결을 사용하여 프로필별 데이터를 참조합니다.

    #Schema: users[ id, name ]
    class User < ApplicationRecord
      has_one :student_profile, class_name: "Student"
      has_one :teacher_profile, class_name: "Teacher"
    
      enum user_type: %i(student teacher)
    
      def say_hello
        "Hello #{name}"
      end
    
      def profile
        return student_profile if self.student?
        return teacher_profile if self.teacher?
      end
    end
    
    #Schema: teachers[ id, department, user_id ]
    class Teacher < ApplicationRecord
    end
    
    #Schema: students[ id, grade, user_id ]
    class Student < ApplicationRecord
    end
    

    예시

    > User.where(name: "John").first.profile.grade
    1
    > User.where(name: "John").say_hello
    "Hello John" 
    
    > User.where(name: "Lisa").first.profile.department
    "English"
    > User.where(name: "Lisa").say_hello
    "Hello Lisa" 
    

    4. 액티브 레코드 delegated_type


    delegated_type와 함께 Active Record를 사용하는 것은 연관이 있는 다중 테이블과 유사하지만 모든 조건부 코드를 추상화하여 깔끔한 슬레이트를 제공합니다. 공통 속성User이 있는 부모 테이블과 필요한 프로필 정보Student, Teacher가 포함된 자식 테이블이 있습니다.

    #Schema: users[ id, name, profilable_type, profilable_id ]
    class User < ApplicationRecord
      delegated_type :profilable, types: %w[ Student Teacher Support ]
    
      def say_hello
        "Hello #{name}"
      end
    end
    
    #Schema: teachers[ id, department ]
    class Teacher < ApplicationRecord
      include Profilable
    end
    
    #Schema: teachers[ id, grade ]
    class Student < ApplicationRecord
      include Profilable
    end
    
    module Profilable
      extend ActiveSupport::Concern
    
      included do
        has_one :user, as: :profilable, touch: true
      end
    end
    

    새 레코드 만들기

    > User.create! profilable: Student.new(grade: 5), name: "John"
    > User.create! profilable: Teacher.new(department: 'Math'), name: "Lisa"
    

    쿼리 기능

    > User.where(name: "John").first.profilable
      User Load (0.2ms)  SELECT "users".* FROM "users" WHERE "users"."name" = ? ORDER BY "users"."id" ASC LIMIT ?  [["name", "John"], ["LIMIT", 1]]
      Student Load (0.1ms)  SELECT "students".* FROM "students" WHERE "students"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
     => #<Student id: 2, grade: 5> 
    > User.where(name: "Lisa").first.profilable
      User Load (0.1ms)  SELECT "users".* FROM "users" WHERE "users"."name" = ? ORDER BY "users"."id" ASC LIMIT ?  [["name", "Lisa"], ["LIMIT", 1]]
      Teacher Load (0.2ms)  SELECT "teachers".* FROM "teachers" WHERE "teachers"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
     => #<Teacher id: 1, department: "Math"> 
    > Teacher.where(department: "Math").count
       (0.2ms)  SELECT COUNT(*) FROM "teachers" WHERE "teachers"."department" = ?  [["department", "Math"]]
     => 1 
    > Student.where(grade: 5).first.user.name
      Student Load (0.1ms)  SELECT "students".* FROM "students" WHERE "students"."grade" = ? ORDER BY "students"."id" ASC LIMIT ?  [["grade", 5], ["LIMIT", 1]]
      User Load (0.1ms)  SELECT "users".* FROM "users" WHERE "users"."profilable_id" = ? AND "users"."profilable_type" = ? LIMIT ?  [["profilable_id", 2], ["profilable_type", "Student"], ["LIMIT", 1]]
     => "John" 
    
    delegated_type는 연관이 있는 다중 테이블과 유사하지만
    레일즈 매직을 사용하여 구현 세부 사항. 이제 결합된User 엔티티에 대해 페이지 매김이 가능합니다.

    추가 읽기


  • Rails Antipattern
  • 좋은 웹페이지 즐겨찾기