액티브 레코드 연관관계
이 가이드는 액티브 레코드의 연관관계 기능을 다룹니다.
이 가이드를 읽고 나면 다음을 알 수 있습니다:
- 액티브 레코드 모델 간의 연관관계를 선언하는 방법
- 다양한 유형의 액티브 레코드 연관관계를 이해하는 방법
- 연관관계 생성으로 인해 모델에 추가되는 메서드를 사용하는 방법
연관관계의 필요성
Rails에서 연관관계는 두 액티브 레코드 모델 간의 연결을 의미합니다. 모델 간 연관관계가 왜 필요할까요? 연관관계는 코드에서 일반적인 작업을 더 간단하고 쉽게 만들어주기 때문입니다.
예를 들어, 저자와 책 모델이 있는 간단한 Rails 애플리케이션을 생각해 보겠습니다. 각 저자는 여러 권의 책을 가질 수 있습니다.
연관관계 없이 모델 선언은 다음과 같이 보일 것입니다:
class Author < ApplicationRecord end class Book < ApplicationRecord end
이제 기존 저자에게 새로운 책을 추가하고 싶다고 가정해 보겠습니다. 다음과 같이 해야 할 것입니다:
@book = Book.create(published_at: Time.now, author_id: @author.id)
또는 저자를 삭제하고 그 저자의 모든 책도 삭제하고 싶다고 가정해 보겠습니다:
@books = Book.where(author_id: @author.id) @books.each do |book| book.destroy end @author.destroy
액티브 레코드 연관관계를 사용하면 이러한 작업을 간소화할 수 있습니다. 저자와 책 간의 연결을 선언적으로 알려주면 Rails가 이를 처리할 수 있습니다. 다음과 같이 저자와 책을 설정할 수 있습니다:
class Author < ApplicationRecord has_many :books, dependent: :destroy end class Book < ApplicationRecord belongs_to :author end
이렇게 변경하면 특정 저자에게 새로운 책을 추가하는 것이 더 쉬워집니다:
@book = @author.books.create(published_at: Time.now)
저자를 삭제하고 그 저자의 모든 책을 삭제하는 것도 훨씬 쉬워집니다:
@author.destroy
다음 섹션에서 다양한 유형의 연관관계에 대해 자세히 알아보겠습니다. 그 다음에는 연관관계 사용에 대한 팁과 요령, 그리고 Rails의 연관관계에 대한 전체 참조 자료를 다루겠습니다.
연관관계의 유형
Rails는 각각 특정한 사용 사례를 가진 6가지 유형의 연관관계를 지원합니다.
다음은 모든 지원되는 유형의 목록과 각각에 대한 API 문서에 대한 링크입니다. 이 문서에서는 사용 방법, 메서드 매개변수 등에 대한 자세한 정보를 제공합니다.
연관관계는 매크로 스타일 호출을 사용하여 구현되므로, 모델에 기능을 선언적으로 추가할 수 있습니다. 예를 들어, 한 모델이 belongs_to
다른 모델을 선언하면, Rails는 두 모델 간의 기본 키-외래 키 정보를 유지관리하고, 모델에 많은 유틸리티 메서드를 추가합니다.
이 가이드의 나머지 부분에서는 다양한 형태의 연관관계를 선언하고 사용하는 방법을 배우게 됩니다. 하지만 먼저 각 연관관계 유형이 적합한 상황에 대한 간단한 소개를 하겠습니다.
belongs_to
연관관계
belongs_to
연관관계는 다른 모델과의 연결을 설정합니다. 이 연관관계는 선언 모델의 각 인스턴스가 다른 모델의 한 인스턴스에 “속한다"는 것을 의미합니다. 예를 들어, 애플리케이션에 저자와 책이 있고, 각 책은 정확히 한 명의 저자에게 할당될 수 있다면, 책 모델을 다음과 같이 선언할 수 있습니다:
class Book < ApplicationRecord belongs_to :author end
참고: belongs_to
연관관계는 반드시 단수 용어를 사용해야 합니다. 위의 예제에서 Book
모델의 author
연관관계를 복수형으로 사용하고 Book.create(authors: @author)
와 같이 인스턴스를 생성하려고 하면 "uninitialized constant Book::Authors"라는 오류가 발생할 것입니다. 이는 Rails가 연관관계 이름에서 클래스 이름을 자동으로 유추하기 때문입니다. 연관관계 이름이 잘못 복수형으로 지정되면 유추된 클래스도 잘못 복수형이 됩니다.
해당 마이그레이션은 다음과 같을 수 있습니다:
class CreateBooks < ActiveRecord::Migration[7.2] def change create_table :authors do |t| t.string :name t.timestamps end create_table :books do |t| t.belongs_to :author t.datetime :published_at t.timestamps end end end
단독으로 사용될 때 belongs_to
는 단방향 일대일 연결을 생성합니다. 따라서 위의 예제에서 각 책은 저자를 "알고” 있지만, 저자는 자신의 책을 모릅니다.
양방향 연관관계를 설정하려면 belongs_to
를 다른 모델(이 경우 Author 모델)의 has_one
또는 has_many
와 함께 사용해야 합니다.
belongs_to
는 optional
이 true로 설정된 경우 참조 일관성을 보장하지 않으므로, 사용 사례에 따라 참조 열에 데이터베이스 수준의 외래 키 제약 조건을 추가해야 할 수 있습니다. 예를 들면 다음과 같습니다:
create_table :books do |t| t.belongs_to :author, foreign_key: true # ... end
has_one
연관관계
has_one
연관관계는 다른 모델이 이 모델에 대한 참조를 가지고 있음을 나타냅니다. 이 연관관계를 통해 해당 모델을 가져올 수 있습니다.
예를 들어, 각 공급업체에 계정이 하나씩만 있다고 가정하면, 공급업체 모델을 다음과 같이 선언할 수 있습니다:
class Supplier < ApplicationRecord has_one :account end
belongs_to
와의 주된 차이점은 링크 열 supplier_id
가 다른 테이블에 있다는 것입니다:
해당 마이그레이션은 다음과 같을 수 있습니다:
class CreateSuppliers < ActiveRecord::Migration[7.2] def change create_table :suppliers do |t| t.string :name t.timestamps end create_table :accounts do |t| t.belongs_to :supplier t.string :account_number t.timestamps end end end
사용 사례에 따라 공급업체 열에 대한 고유 인덱스 및/또는 외래 키 제약 조건을 만들어야 할 수 있습니다. 이 경우 열 정의는 다음과 같이 보일 수 있습니다:
create_table :accounts do |t| t.belongs_to :supplier, index: { unique: true }, foreign_key: true # ... end
이 관계는 다른 모델의 belongs_to
와 함께 사용하면 양방향이 될 수 있습니다.
has_many
연관관계
has_many
연관관계는 has_one
과 유사하지만, 다른 모델과의 일대다 연결을 나타냅니다. 이 연관관계는 종종 belongs_to
연관관계의 “다른 쪽"에서 발견됩니다. 이 연관관계는 모델의 각 인스턴스가 다른 모델의 0개 이상의 인스턴스를 가질 수 있음을 나타냅니다. 예를 들어, 저자와 책이 포함된 애플리케이션에서 저자 모델은 다음과 같이 선언될 수 있습니다:
class Author < ApplicationRecord has_many :books end
참고: 다른 모델의 이름은 has_many
연관관계 선언 시 복수형으로 사용됩니다.
해당 마이그레이션은 다음과 같을 수 있습니다:
class CreateAuthors < ActiveRecord::Migration[7.2] def change create_table :authors do |t| t.string :name t.timestamps end create_table :books do |t| t.belongs_to :author t.datetime :published_at t.timestamps end end end
사용 사례에 따라 일반적으로 책 테이블의 author 열에 대한 비고유 인덱스와 선택적으로 외래 키 제약 조건을 만드는 것이 좋습니다:
create_table :books do |t| t.belongs_to :author, index: true, foreign_key: true # ... end
has_many :through
연관관계
has_many :through
연관관계는 종종 다른 모델과의 다대다 연결을 설정하는 데 사용됩니다. 이 연관관계는 선언 모델이 통해 세 번째 모델을 거쳐 0개 이상의 다른 모델 인스턴스와 연결될 수 있음을 나타냅니다. 예를 들어, 의사가 환자와 예약을 하는 의료 실습을 생각해 보겠습니다. 관련 연관관계 선언은 다음과 같을 수 있습니다:
class Physician < ApplicationRecord has_many :appointments has_many :patients, through: :appointments end class Appointment < ApplicationRecord belongs_to :physician belongs_to :patient end class Patient < ApplicationRecord has_many :appointments has_many :physicians, through: :appointments end
해당 마이그레이션은 다음과 같을 수 있습니다:
class CreateAppointments < ActiveRecord::Migration[7.2] def change create_table :physicians do |t| t.string :name t.timestamps end create_table :patients do |t| t.string :name t.timestamps end create_table :appointments do |t| t.belongs_to :physician t.belongs_to :patient계속해서 번역하겠습니다: t.datetime :appointment_date t.timestamps end end end
연결 모델의 컬렉션은 has_many
연관관계 메서드를 통해 관리할 수 있습니다.
예를 들어, 다음과 같이 할당하면:
physician.patients = patients
그러면 새로 연결된 객체에 대해 자동으로 새 연결 행이 생성됩니다. 이전에 존재했던 것 중 일부가 이제 없다면 해당 연결 행이 자동으로 삭제됩니다.
경고: 연결 모델의 자동 삭제는 직접적이며 소멸 콜백이 트리거되지 않습니다.
has_many :through
연관관계는 중첩된 has_many
연관관계를 통해 "단축키"를 설정하는 데에도 유용합니다. 예를 들어, 문서에 여러 섹션이 있고 섹션에 여러 단락이 있는 경우, 문서의 모든 단락 컬렉션을 가져오고 싶을 수 있습니다. 다음과 같이 설정할 수 있습니다:
class Document < ApplicationRecord has_many :sections has_many :paragraphs, through: :sections end class Section < ApplicationRecord belongs_to :document has_many :paragraphs end class Paragraph < ApplicationRecord belongs_to :section end
through: :sections
를 지정하면 Rails는 이제 다음과 같이 이해할 수 있습니다:
@document.paragraphs
has_one :through
연관관계
has_one :through
연관관계는 다른 모델과의 일대일 연결을 설정합니다. 이 연관관계는 선언 모델이 통해 세 번째 모델을 거쳐 다른 모델의 한 인스턴스와 연결될 수 있음을 나타냅니다.
예를 들어, 각 공급업체에 계정이 하나씩 있고, 각 계정이 계정 내역 하나와 연결되어 있다고 가정하면, 공급업체 모델은 다음과 같이 보일 수 있습니다:
class Supplier < ApplicationRecord has_one :account has_one :account_history, through: :account end class Account < ApplicationRecord belongs_to :supplier has_one :account_history end class AccountHistory < ApplicationRecord belongs_to :account end
해당 마이그레이션은 다음과 같을 수 있습니다:
class CreateAccountHistories < ActiveRecord::Migration[7.2] def change create_table :suppliers do |t| t.string :name t.timestamps end create_table :accounts do |t| t.belongs_to :supplier t.string :account_number t.timestamps end create_table :account_histories do |t| t.belongs_to :account t.integer :credit_rating t.timestamps end end end
has_and_belongs_to_many
연관관계
has_and_belongs_to_many
연관관계는 다른 모델과의 직접적인 다대다 연결을 생성합니다. 이 연관관계는 선언 모델의 각 인스턴스가 0개 이상의 다른 모델 인스턴스를 참조할 수 있음을 나타냅니다.
예를 들어, 애플리케이션에 조립품과 부품이 있고, 각 조립품이 여러 부품을 가지며 각 부품이 여러 조립품에 나타난다고 가정하면, 모델을 다음과 같이 선언할 수 있습니다:
class Assembly < ApplicationRecord has_and_belongs_to_many :parts end class Part < ApplicationRecord has_and_belongs_to_many :assemblies end
해당 마이그레이션은 다음과 같을 수 있습니다:
class CreateAssembliesAndParts < ActiveRecord::Migration[7.2] def change create_table :assemblies do |t| t.string :name t.timestamps end create_table :parts do |t| t.string :part_number t.timestamps end create_table :assemblies_parts, id: false do |t| t.belongs_to :assembly t.belongs_to :part end end end
belongs_to
와 has_one
중 선택하기
두 모델 간에 일대일 관계를 설정하려면 하나에 belongs_to
를, 다른 하나에 has_one
을 추가해야 합니다. 어떤 것을 어디에 사용해야 할까요?
차이점은 외래 키가 어디에 있는지(belongs_to
연관관계를 선언한 클래스의 테이블에 있음)에 있지만, 데이터의 실제 의미도 고려해야 합니다. has_one
관계는 무언가가 귀하의 것이라는 것을 말합니다 - 즉, 무언가가 귀하를 가리킵니다. 예를 들어, 공급업체가 계정을 소유한다는 것이 계정이 공급업체를 소유한다는 것보다 더 의미가 있습니다. 이는 다음과 같은 올바른 관계를 제안합니다:
class Supplier < ApplicationRecord has_one :account end class Account < ApplicationRecord belongs_to :supplier end
해당 마이그레이션은 다음과 같을 수 있습니다:
class CreateSuppliers < ActiveRecord::Migration[7.2] def change create_table :suppliers do |t| t.string :name t.timestamps end create_table :accounts do |t| t.bigint :supplier_id t.string :account_number t.timestamps end add_index :accounts, :supplier_id end end
참고: t.bigint :supplier_id
를 사용하면 외래 키 이름이 명확하고 명시적입니다. 현재 Rails 버전에서는 t.references :supplier
를 사용하여 이 구현 세부 사항을 추상화할 수 있습니다.
has_many :through
와 has_and_belongs_to_many
중 선택하기
Rails는 모델 간 다대다 관계를 선언하는 두 가지 다른 방법을 제공합니다. 첫 번째 방법은 has_and_belongs_to_many
를 사용하는 것으로, 직접 연관관계를 만들 수 있습니다:
class Assembly < ApplicationRecord has_and_belongs_to_many :parts end class Part < ApplicationRecord has_and_belongs_to_many :assemblies end
두 번째 방법은 has_many :through
를 사용하는 것으로, 연결 모델을 통해 간접적으로 연관관계를 만듭니다:
class Assembly < ApplicationRecord has_many :manifests has_many :parts, through: :manifests end class Manifest < ApplicationRecord belongs_to :assembly belongs_to :part end class Part < ApplicationRecord has_many :manifests has_many :assemblies, through: :manifests end
가장 간단한 규칙은 연결 모델을 독립 엔티티로 작업해야 하는 경우 has_many :through
관계를 설정해야 한다는 것입니다. 연결 모델에 대해 아무것도 할 필요가 없다면 has_and_belongs_to_many
관계를 설정하는 것이 더 간단할 수 있습니다(단, 데이터베이스에 조인 테이블을 만들어야 한다는 것을 기억해야 합니다).
유효성 검사, 콜백 또는 조인 모델에 대한 추가 속성이 필요한 경우 has_many :through
를 사용해야 합니다.
has_and_belongs_to_many
는 id: false
를 통해 기본 키가 없는 조인 테이블을 만들 것을 제안하지만, has_many :through
관계에서는 조인 테이블에 복합 기본 키를 사용하는 것이 좋습니다.
예를 들어, 위의 예제에서 create_table :manifests, primary_key: [:assembly_id, :part_id]
를 사용하는 것이 좋습니다.
다형성 연관관계
연관관계에 대한 약간 더 고급 트위스트는 다형성 연관관계입니다. 다형성 연관관계를 사용하면 모델이 단일 연관관계에서 두 개 이상의 다른 모델에 속할 수 있습니다. 예를 들어, 직원 모델 또는 제품 모델에 속할 수 있는 사진 모델이 있을 수 있습니다. 다음과 같이 선언할 수 있습니다:
class Picture < ApplicationRecord belongs_to :imageable, polymorphic: true end class Employee < ApplicationRecord has_many :pictures, as: :imageable end class Product < ApplicationRecord has_many :pictures, as: :imageable end
다형성 belongs_to
선언을 인터페이스를 설정하는 것으로 생각할 수 있습니다. 어떤 다른 모델이든 이 인터페이스를 사용할 수 있습니다. @employee.pictures
를 통해 직원 인스턴스에서 사진 컬렉션을 가져올 수 있습니다.
마찬가지로 @product.pictures
를 통해 제품에서 사진을 가져올 수 있습니다.
Picture
모델의 인스턴스가 있는 경우 @picture.imageable
를 통해 해당 부모에 액세스할 수 있습니다. 이를 작동시키려면 다형성 인터페이스를 선언하는 모델에 외래 키 열과 유형 열을 선언해야 합니다:
class CreatePictures < ActiveRecord::Migration[7.2] def change create_table :pictures do |t| t.string :name t.bigint :imageable_id t.string :imageable_type t.timestamps end add_index :pictures, [:imageable_type, :imageable_id] end end
이 마이그레이션은 t.references
형식을 사용하여 간소화할 수 있습니다:
class CreatePictures < ActiveRecord::Migration[7.2] def change create_table :pictures do |t| t.string :name t.references :imageable, polymorphic: true t.timestamps end end end
참고: 다형성 연관관계는 데이터베이스에 클래스 이름을 저장하므로, 해당 데이터는 Ruby 코드에서 사용되는 클래스 이름과 동기화되어야 합니다. 클래스 이름을 변경할 때는 다형성 유형 열의 데이터도 업데이트해야 합니다.
복합 기본 키가 있는 모델 간 연관관계
Rails는 종종 추가 정보 없이도 복합 기본 키가 있는 연관된 모델 간의 기본 키-외래 키 정보를 추론할 수 있습니다. 다음 예를 살펴보겠습니다:
class Order < ApplicationRecord self.primary_key = [:shop_id, :id] has_many :books end class Book < ApplicationRecord belongs_to :order end
여기서 Rails는 has_many
/ belongs_to
연관관계와 마찬가지로 :id
열을 연관관계의 기본 키로 사용할 것이라고 가정합니다. 외래 키 열이 :order_id
라고 추론할 것입니다. 책의 주문 액세스:
order = Order.create!(id: [1, 2], status: "pending") book = order.books.create!(title: "A Cool Book") book.reload.order
는 다음과 같은 SQL을 생성하여 주문에 액세스합니다:
SELECT * FROM orders WHERE id = 2
이는 모델의 복합 기본 키에 :id
열이 포함되어 있고, 해당 열이 모든 레코드에 대해 고유한 경우에만 작동합니다. 전체 복합 기본 키를 연관관계에 사용하려면 query_constraints
옵션을 연관관계에 설정하세요. 이 옵션은 연관관계의 복합 외래 키네, 계속해서 번역하겠습니다:
를 지정합니다. 외래 키의 모든 열이 관련 레코드(들)을 쿼리할 때 사용됩니다. 예를 들면 다음과 같습니다:
class Author < ApplicationRecord self.primary_key = [:first_name, :last_name] has_many :books, query_constraints: [:first_name, :last_name] end class Book < ApplicationRecord belongs_to :author, query_constraints: [:author_first_name, :author_last_name] end
책의 저자 액세스:
author = Author.create!(first_name: "Jane", last_name: "Doe") book = author.books.create!(title: "A Cool Book") book.reload.author
는 :first_name
및 :last_name
을 사용하여 SQL 쿼리를 실행합니다:
SELECT * FROM authors WHERE first_name = 'Jane' AND last_name = 'Doe'
자기 조인
데이터 모델을 설계할 때 모델이 자신과 관계를 가져야 하는 경우가 있습니다. 예를 들어, 단일 데이터베이스 모델에 모든 직원을 저장하고 싶지만 관리자와 부하 직원 간의 관계를 추적할 수 있어야 합니다. 이 상황은 자기 조인 연관관계로 모델링할 수 있습니다:
class Employee < ApplicationRecord has_many :subordinates, class_name: "Employee", foreign_key: "manager_id" belongs_to :manager, class_name: "Employee", optional: true end
이 설정을 통해 @employee.subordinates
와 @employee.manager
를 가져올 수 있습니다.
마이그레이션/스키마에서는 모델 자체에 참조 열을 추가합니다.
class CreateEmployees < ActiveRecord::Migration[7.2] def change create_table :employees do |t| t.references :manager, foreign_key: { to_table: :employees } t.timestamps end end end
참고: to_table
옵션은 SchemaStatements#add_reference
에서 설명됩니다.
팁, 요령 및 경고
Rails 애플리케이션에서 액티브 레코드 연관관계를 효율적으로 사용하기 위해 알아두어야 할 몇 가지 사항은 다음과 같습니다:
- 캐싱 제어
- 이름 충돌 방지
- 스키마 업데이트
- 연관관계 범위 제어
- 양방향 연관관계
캐싱 제어
모든 연관관계 메서드는 캐싱을 기반으로 구축되어 있어, 가장 최근 쿼리의 결과를 추가 작업에 사용할 수 있습니다. 캐시는 메서드 간에도 공유됩니다. 예를 들어:
# 데이터베이스에서 책을 가져옵니다 author.books.load # 책 캐시 사본 사용 author.books.size # 책 캐시 사본 사용 author.books.empty?
그러나 데이터가 애플리케이션의 다른 부분에 의해 변경되었을 수 있으므로 캐시를 다시 로드하고 싶다면 reload
를 호출하면 됩니다:
# 데이터베이스에서 책을 가져옵니다 author.books.load # 책 캐시 사본 사용 author.books.size # 책 캐시 사본을 폐기하고 데이터베이스로 돌아갑니다 author.books.reload.empty?
이름 충돌 방지
연관관계에 사용할 이름은 자유롭게 선택할 수 없습니다. 연관관계를 생성하면 해당 이름의 메서드가 모델에 추가되므로, ActiveRecord::Base
의 인스턴스 메서드와 이름이 겹치면 안 됩니다. 연관관계 메서드가 기본 메서드를 덮어쓰고 문제를 일으킬 수 있습니다. 예를 들어 attributes
또는 connection
은 연관관계 이름으로 좋지 않습니다.
스키마 업데이트
연관관계는 매우 유용하지만 마법은 아닙니다. 연관관계에 맞게 데이터베이스 스키마를 유지 관리할 책임이 있습니다. 실제로 이는 두 가지 의미가 있습니다. belongs_to
연관관계의 경우 외래 키를 만들어야 하고, has_and_belongs_to_many
연관관계의 경우 적절한 조인 테이블을 만들어야 합니다.
belongs_to
연관관계에 대한 외래 키 생성
belongs_to
연관관계를 선언할 때 해당하는 외래 키를 만들어야 합니다. 예를 들어 다음 모델을 고려해 보겠습니다:
class Book < ApplicationRecord belongs_to :author end
이 선언에는 책 테이블에 해당하는 외래 키 열이 필요합니다. 새 테이블의 경우 마이그레이션은 다음과 같을 수 있습니다:
class CreateBooks < ActiveRecord::Migration[7.2] def change create_table :books do |t| t.datetime :published_at t.string :book_number t.references :author end end end
기존 테이블의 경우에는 다음과 같을 수 있습니다:
class AddAuthorToBooks < ActiveRecord::Migration[7.2] def change add_reference :books, :author end end
참고: 데이터베이스 수준의 외래 키 무결성을 적용하려면 위의 ‘reference’ 열 선언에 foreign_key: true
옵션을 추가하세요.
has_and_belongs_to_many
연관관계에 대한 조인 테이블 생성
has_and_belongs_to_many
연관관계를 생성하는 경우 명시적으로 조인 테이블을 만들어야 합니다. 조인 테이블 이름이 :join_table
옵션을 사용하여 명시적으로 지정되지 않는 한, Active Record는 클래스 이름의 어휘 순서를 사용하여 이름을 생성합니다. 따라서 저자와 책 모델 간의 조인은 "authors_books"라는 기본 조인 테이블 이름을 사용할 것입니다. 왜냐하면 "a"가 "b"보다 어휘적으로 앞서기 때문입니다.
경고: 모델 이름 간 우선순위는 <=>
연산자를 사용하여 계산됩니다. 즉, 문자열 길이가 다른 경우 가장 짧은 길이까지 문자열이 같으면 더 긴 문자열이 더 높은 어휘적 우선순위로 간주됩니다. 예를 들어 "paperboxes"와 "papers” 테이블 이름을 예상하면 “paperspaperboxes"라는 조인 테이블 이름이 생성될 것 같지만, 실제로는 "paperboxespapers”(밑줄 ‘_'이 ’s'보다 어휘적으로 _작기 때문)라는 조인 테이블 이름이 생성됩니다.
어떤 이름이든, 적절한 마이그레이션을 통해 조인 테이블을 수동으로 생성해야 합니다. 예를 들어 다음과 같은 연관관계를 고려해 보겠습니다:
class Assembly < ApplicationRecord has_and_belongs_to_many :parts end class Part < ApplicationRecord has_and_belongs_to_many :assemblies end
이러한 연관관계에는 assemblies_parts
테이블을 생성하는 마이그레이션이 필요합니다. 이 테이블은 기본 키 없이 생성되어야 합니다:
class CreateAssembliesPartsJoinTable < ActiveRecord::Migration[7.2] def change create_table :assemblies_parts, id: false do |t| t.bigint :assembly_id t.bigint :part_id end add_index :assemblies_parts, :assembly_id add_index :assemblies_parts, :part_id end end
id: false
를 create_table
에 전달하는 이유는 이 테이블이 모델을 나타내지 않기 때문입니다. 이는 연관관계가 제대로 작동하도록 하는 데 필요합니다. 만약 이 부분을 잊으면 모순된 ID로 인해 이상한 동작이나 예외가 발생할 수 있습니다.
간단히 하기 위해 create_join_table
메서드를 사용할 수도 있습니다:
class CreateAssembliesPartsJoinTable < ActiveRecord::Migration[7.2] def change create_join_table :assemblies, :parts do |t| t.index :assembly_id t.index :part_id end end end
연관관계 범위 제어
기본적으로 연관관계는 현재 모듈의 범위 내에서 객체를 찾습니다. 이는 모델을 모듈 내에 선언할 때 중요할 수 있습니다. 예를 들어:
module MyApplication module Business class Supplier < ApplicationRecord has_one :account end class Account < ApplicationRecord belongs_to :supplier end end end
이렇게 하면 잘 작동합니다. 왜냐하면 Supplier
와 Account
클래스가 모두 동일한 범위(MyApplication::Business
) 내에 정의되었기 때문입니다. 이러한 구조화를 통해 범위를 명시적으로 추가하지 않고도 폴더 기반으로 모델을 구성할 수 있습니다:
# app/models/my_application/business/supplier.rb module MyApplication module Business class Supplier < ApplicationRecord has_one :account end end end
# app/models/my_application/business/account.rb module MyApplication module Business class Account < ApplicationRecord belongs_to :supplier end end end
그러나 이것이 테이블 이름에 영향을 미치지는 않는다는 점에 유의해야 합니다. 예를 들어 MyApplication::Business::Supplier
모델이 있다면 my_application_business_suppliers
테이블이 있어야 합니다.
다음과 같은 경우는 작동하지 않습니다. 왜냐하면 Supplier
와 Account
가 다른 범위(MyApplication::Business
와 MyApplication::Billing
)에 정의되었기 때문입니다:
module MyApplication module Business class Supplier < ApplicationRecord has_one :account end end module Billing class Account < ApplicationRecord belongs_to :supplier end end end
다른 네임스페이스의 모델과 연관관계를 설정하려면 연관관계 선언에 전체 클래스 이름을 지정해야 합니다:
module MyApplication module Business class Supplier < ApplicationRecord has_one :account, class_name: "MyApplication::Billing::Account" end end module Billing class Account < ApplicationRecord belongs_to :supplier, class_name: "MyApplication::Business::Supplier" end end end
양방향 연관관계
연관관계는 일반적으로 두 방향으로 작동하며, 두 개의 다른 모델에 선언되어야 합니다:
class Author < ApplicationRecord has_many :books end class Book < ApplicationRecord belongs_to :author end
Active Record는 이 두 모델이 양방향 연관관계를 공유하고 있다는 것을 자동으로 식별하려고 시도합니다. 이 정보를 통해 Active Record는 다음을 수행할 수 있습니다:
이미 로드된 데이터에 대한 불필요한 쿼리 방지:
irb> author = Author.first irb> author.books.all? do |book| irb> book.author.equal?(author) # 여기서는 추가 쿼리가 실행되지 않습니다 irb> end => true
일관되지 않은 데이터 방지(로드된
Author
객체가 하나뿐이기 때문네, 계속해서 번역하겠습니다:
):
```irb irb> author = Author.first irb> book = author.books.first irb> author.name == book.author.name => true irb> author.name = "Changed Name" irb> author.name == book.author.name => true ```
더 많은 경우에서 자동 저장 연관관계:
irb> author = Author.new irb> book = author.books.new irb> book.save! irb> book.persisted? => true irb> author.persisted? => true
presence 및 absence 유효성 검사를 더 많은 경우에서 수행:
irb> book = Book.new irb> book.valid? => false irb> book.errors.full_messages => ["Author must exist"] irb> author = Author.new irb> book = author.books.new irb> book.valid? => true
Active Record는 대부분의 표준 이름에 대해 자동 식별을 지원합니다. 그러나 :through
또는 :foreign_key
옵션이 포함된 양방향 연관관계는 자동으로 식별되지 않습니다.
반대 연관관계에 대한 사용자 정의 범위도 자동 식별을 방지하며, config.active_record.automatic_scope_inversing
이 true(새 애플리케이션의 기본값)로 설정되어 있지 않는 한 연관관계 자체에 대한 사용자 정의 범위도 방지합니다.
예를 들어 다음과 같은 모델 선언을 고려해 보겠습니다:
class Author < ApplicationRecord has_many :books end class Book < ApplicationRecord belongs_to :writer, class_name: 'Author', foreign_key: 'author_id' end
:foreign_key
옵션 때문에 Active Record는 더 이상 양방향 연관관계를 자동으로 인식하지 못합니다. 이로 인해 애플리케이션에서 다음과 같은 문제가 발생할 수 있습니다:
동일한 데이터에 대한 불필요한 쿼리 실행(이 예에서는 N+1 쿼리 발생):
irb> author = Author.first irb> author.books.any? do |book| irb> book.author.equal?(author) # 이렇게 하면 각 책에 대해 author 쿼리가 실행됩니다 irb> end => false
일관되지 않은 데이터가 있는 여러 복사본 참조:
irb> author = Author.first irb> book = author.books.first irb> author.name == book.author.name => true irb> author.name = "Changed Name" irb> author.name == book.author.name => false
자동 저장 연관관계 실패:
irb> author = Author.new irb> book = author.books.new irb> book.save! irb> book.persisted? => true irb> author.persisted? => false
존재 또는 부재 유효성 검사 실패:
irb> author = Author.new irb> book = author.books.new irb> book.valid? => false irb> book.errors.full_messages => ["Author must exist"]
Active Record는 :inverse_of
옵션을 제공하여 양방향 연관관계를 명시적으로 선언할 수 있습니다:
class Author < ApplicationRecord has_many :books, inverse_of: 'writer' end class Book < ApplicationRecord belongs_to :writer, class_name: 'Author', foreign_key: 'author_id' end
:inverse_of
옵션을 has_many
연관관계 선언에 포함하면 Active Record가 양방향 연관관계를 인식하고 위의 첫 번째 예와 같이 동작합니다.
연관관계 참조 상세
다음 섹션에서는 각 유형의 연관관계에 대한 세부 사항을 설명합니다. 여기에는 연관관계가 추가하는 메서드와 연관관계를 선언할 때 사용할 수 있는 옵션이 포함됩니다.
belongs_to
연관관계 참조
데이터베이스 용어로 belongs_to
연관관계는 이 모델의 테이블에 다른 테이블에 대한 참조를 나타내는 열이 포함되어 있음을 의미합니다.
이를 통해 일대일 또는 일대다 관계를 설정할 수 있습니다. 다른 클래스의 테이블에 참조가 있는 경우 일대일 관계라면 대신 has_one
을 사용해야 합니다.
belongs_to
가 추가하는 메서드
belongs_to
연관관계를 선언하면 선언 클래스에 연관관계와 관련된 8개의 메서드가 자동으로 추가됩니다:
association
association=(associate)
build_association(attributes = {})
create_association(attributes = {})
create_association!(attributes = {})
reload_association
reset_association
association_changed?
association_previously_changed?
이 모든 메서드에서 association
은 belongs_to
에 전달된 첫 번째 인수로 바뀝니다. 예를 들어 다음과 같은 선언이 있다면:
class Book < ApplicationRecord belongs_to :author end
Book
모델의 각 인스턴스에는 다음과 같은 메서드가 있습니다:
author
author=
build_author
create_author
create_author!
reload_author
reset_author
author_changed?
author_previously_changed?
참고: has_one
또는 belongs_to
연관관계를 초기화할 때는 build_
접두사를 사용해야 하며, has_many
또는 has_and_belongs_to_many
연관관계에서 사용되는 association.build
메서드를 사용할 수 없습니다. 생성하려면 create_
접두사를 사용하세요.
association
association
메서드는 연관된 객체(있는 경우)를 반환합니다. 연관된 객체를 찾을 수 없는 경우 nil
을 반환합니다.
@author = @book.author
연관된 객체가 이미 이 객체에 대해 데이터베이스에서 검색된 경우 캐시된 버전이 반환됩니다. 이 동작을 무시하고(데이터베이스 읽기를 강제하려면) 부모 객체에서 #reload_association
을 호출하세요.
@author = @book.reload_author
연관된 객체의 캐시된 버전을 제거하여(다음 액세스 시 데이터베이스에서 쿼리하도록 하려면) 부모 객체에서 #reset_association
을 호출하세요.
@book.reset_author
association=(associate)
association=
메서드는 연관된 객체를 이 객체에 할당합니다. 내부적으로는 연관된 객체의 기본 키를 추출하고 이 객체의 외래 키를 동일한 값으로 설정합니다.
@book.author = @author
build_association(attributes = {})
build_association
메서드는 연관된 유형의 새 객체를 반환합니다. 이 객체는 전달된 속성에서 인스턴스화되며, 이 객체의 외래 키를 통해 링크가 설정됩니다. 그러나 연관된 객체는 아직 저장되지 않습니다.
@author = @book.build_author(author_number: 123, author_name: "John Doe")
create_association(attributes = {})
create_association
메서드는 연관된 유형의 새 객체를 반환합니다. 이 객체는 전달된 속성에서 인스턴스화되고, 이 객체의 외래 키를 통해 링크가 설정됩니다. 그리고 연관된 모델에 지정된 모든 유효성 검사를 통과하면 연관된 객체 도 저장됩니다.
@author = @book.create_author(author_number: 123, author_name: "John Doe")
create_association!(attributes = {})
create_association
과 동일하지만, 레코드가 유효하지 않은 경우 ActiveRecord::RecordInvalid
를 발생시킵니다.
association_changed?
association_changed?
메서드는 새 연관된 객체가 할당되었고 다음 저장 시 외래 키가 업데이트될 경우 true
를 반환합니다.
@book.author # => #<Author author_number: 123, author_name: "John Doe"> @book.author_changed? # => false @book.author = Author.second # => #<Author author_number: 456, author_name: "Jane Smith"> @book.author_changed? # => true @book.save! @book.author_changed? # => false
association_previously_changed?
association_previously_changed?
메서드는 이전 저장 시 연관된 객체 참조가 새 객체로 업데이트된 경우 true
를 반환합니다.
@book.author # => #<Author author_number: 123, author_name: "John Doe"> @book.author_previously_changed? # => false @book.author = Author.second # => #<Author author_number: 456, author_name: "Jane Smith"> @book.save! @book.author_previously_changed? # => true
belongs_to
에 대한 옵션
Rails는 대부분의 상황에서 잘 작동하는 지능적인 기본값을 사용하지만, belongs_to
연관관계 참조의 동작을 사용자 정의해야 하는 경우도 있습니다. 이러한 사용자 정의는 연관관계를 생성할 때 옵션과 범위 블록을 전달하여 쉽게 수행할 수 있습니다. 예를 들어 이 연관관계는 두 가지 옵션을 사용합니다:
class Book < ApplicationRecord belongs_to :author, touch: :books_updated_at, counter_cache: true end
belongs_to
연관관계는 다음과 같은 옵션을 지원합니다:
:autosave
:class_name
:counter_cache
:default
:dependent
:ensuring_owner_was
:foreign_key
:foreign_type
:primary_key
:inverse_of
:optional
:polymorphic
:required
:strict_loading
:touch
:validate
:autosave
:autosave
옵션을 true
로 설정하면 Rails는 부모 객체를 저장할 때 로드된 연관 멤버를 저장하고 삭제 표시된 멤버를 삭제합니다. :autosave
옵션을 false
로 설정하는 것은 :autosave
옵션을 설정하지 않는 것과 같지 않습니다. :autosave
옵션이 없는 경우 새 연관 객체는 저장되지만 업데이트된 연관 객체는 저장되지 않습니다.
:class_name
다른 모델의 이름을 연관관계 이름에서 유추할 수 없는 경우 :class_name
옵션을 사용하여 모델 이름을 제공할 수 있습니다. 예를 들어 책이 저자에게 속하지만 실제 저자 모델의 이름이 Patron
인 경우 다음과 같이 설정할 수 있습니다:
class Book < ApplicationRecord belongs_to :author, class_name: "Patron" end
:counter_cache
:counter_cache
옵션을 사용하면 속하는 객체 수를 더 효율적으로 찾을 수 있습니다. 다음과 같은 모델을 고려해 보겠습니다:
class Book < ApplicationRecord belongs_to :author end class Author < ApplicationRecord has_many :books end
이러한 선언에서 @author.books.size
의 값을 요청하면 COUNT(*)
쿼리를네, 계속해서 번역하겠습니다:
실행해야 합니다. 이 호출을 피하려면 속하는 모델에 카운터 캐시를 추가할 수 있습니다:
class Book < ApplicationRecord belongs_to :author, counter_cache: true end class Author < ApplicationRecord has_many :books end
이 선언에서 Rails는 캐시 값을 최신 상태로 유지하고 size
메서드에 해당 값을 반환합니다.
:counter_cache
옵션은 belongs_to
선언에 지정되지만, 실제 열은 연관된(has_many
) 모델에 추가해야 합니다. 위의 경우 Author
모델에 books_count
열을 추가해야 합니다.
기본 열 이름 대신 사용자 정의 열 이름을 지정할 수 있습니다. 예를 들어 books_count
대신 count_of_books
를 사용하려면 다음과 같이 하면 됩니다:
class Book < ApplicationRecord belongs_to :author, counter_cache: :count_of_books end class Author < ApplicationRecord has_many :books end
참고: :counter_cache
옵션은 belongs_to
쪽 연관관계에만 지정하면 됩니다.
기존 대규모 테이블에서 카운터 캐시 사용을 시작하는 것은 문제가 될 수 있습니다. 열 값을 백필하는 것(테이블을 너무 오래 잠그지 않도록) 및 :counter_cache
사용 전에 수행해야 하며, 그렇지 않으면 size
/any?
/등과 같은 메서드가 잘못된 카운터 캐시 열 값을 사용하여 잘못된 결과를 생성할 수 있습니다. 자식 레코드 생성/제거와 함께 카운터 캐시 열을 최신 상태로 유지하고, 데이터베이스의 결과만 항상 가져오려면 counter_cache: { active: false }
를 사용하세요. 사용자 정의 열 이름도 필요한 경우 counter_cache: { active: false, column: :my_custom_counter }
를 사용하세요.
어떤 이유로든 소유자 모델의 기본 키 값을 변경하고 계산된 모델의 외래 키를 업데이트하지 않으면 카운터 캐시가 오래된 데이터를 가질 수 있습니다. 다시 말해, 고아가 된 모델은 여전히 카운터에 포함됩니다. 오래된 카운터 캐시를 수정하려면 reset_counters
를 사용하세요.
:default
true
로 설정하면 연관관계의 존재가 유효성 검사되지 않습니다.
:dependent
:dependent
옵션을 다음과 같이 설정하면:
:destroy
는 객체가 삭제될 때 연관된 객체도destroy
됩니다.:delete
는 객체가 삭제될 때 연관된 객체가 데이터베이스에서 직접 삭제됩니다(콜백이 실행되지 않음).:destroy_async
: 객체가 삭제될 때ActiveRecord::DestroyAssociationAsyncJob
작업이 대기열에 추가되어 연관된 객체에 대해 destroy가 호출됩니다. 이 기능을 사용하려면 Active Job이 설정되어 있어야 합니다. 데이터베이스의 외래 키 제약 조건이 연관관계에 의해 백업되는 경우에는 이 옵션을 사용하지 마세요. 외래 키 제약 조건 작업은 소유자를 삭제하는 것과 동일한 트랜잭션 내에서 발생합니다.
경고: 다른 클래스의 has_many
연관관계와 연결된 belongs_to
연관관계에 이 옵션을 지정하지 마세요. 그렇지 않으면 데이터베이스에 고아 레코드가 발생할 수 있습니다.
:ensuring_owner_was
소유자에게 호출되는 인스턴스 메서드를 지정합니다. 메서드는 연관된 레코드가 백그라운드 작업에서 삭제되도록 true
를 반환해야 합니다.
:foreign_key
관례상 Rails는 이 모델에 대한 외래 키를 보유하는 열의 이름이 연관관계 이름에 _id
접미사가 추가된 것이라고 가정합니다. :foreign_key
옵션을 사용하면 외래 키의 이름을 직접 설정할 수 있습니다:
class Book < ApplicationRecord belongs_to :author, class_name: "Patron", foreign_key: "patron_id" end
팁: 어떤 경우든 Rails는 외래 키 열을 자동으로 생성하지 않습니다. 마이그레이션의 일부로 명시적으로 정의해야 합니다.
:foreign_type
이 연관관계가 다형성 연관관계인 경우 연관된 객체의 유형을 저장하는 열을 지정합니다. 기본적으로 연관관계 이름에 “_type
” 접미사가 추가된 이름으로 추정됩니다. 따라서 belongs_to :taggable, polymorphic: true
연관관계를 정의하는 클래스는 기본적으로 “taggable_type
"을 :foreign_type
로 사용합니다.
:primary_key
관례상 Rails는 id
열을 테이블의 기본 키로 사용한다고 가정합니다. :primary_key
옵션을 사용하면 다른 열을 지정할 수 있습니다.
예를 들어 users
테이블의 기본 키가 guid
라고 가정해 보겠습니다. 별도의 todos
테이블에 외래 키 user_id
를 guid
열에 보관하려면 primary_key
를 사용하면 됩니다:
class User < ApplicationRecord self.primary_key = 'guid' # 기본 키는 id가 아닌 guid end class Todo < ApplicationRecord belongs_to :user, primary_key: 'guid' end
@user.todos.create
를 실행하면 @todo
레코드의 user_id
가 @user
의 guid
값으로 설정됩니다.
:inverse_of
:inverse_of
옵션은 이 연관관계의 역방향 has_many
또는 has_one
연관관계의 이름을 지정합니다.
양방향 연관관계 섹션에서 자세한 내용을 확인하세요.
class Author < ApplicationRecord has_many :books, inverse_of: :author end class Book < ApplicationRecord belongs_to :author, inverse_of: :books end
:optional
:optional
옵션을 true
로 설정하면 연관된 객체의 존재가 유효성 검사되지 않습니다. 기본적으로 이 옵션은 false
로 설정됩니다.
:polymorphic
:polymorphic
옵션을 true
로 전달하면 이것이 다형성 연관관계임을 나타냅니다. 다형성 연관관계는 이 가이드의 앞부분에서 자세히 다루었습니다.
:required
true
로 설정하면 연관관계 자체의 존재도 유효성 검사됩니다. 이는 ID가 아닌 연관관계 자체를 검증합니다. :inverse_of
를 사용하면 추가 쿼리 없이 유효성 검사를 수행할 수 있습니다.
참고: required는 기본적으로 true
로 설정되어 있으며 사용되지 않습니다. 연관관계 존재 유효성 검사를 원하지 않는 경우 optional: true
를 사용하세요.
:strict_loading
이 연관관계를 통해 연관된 레코드가 로드될 때마다 strict loading을 적용합니다.
:touch
:touch
옵션을 true
로 설정하면 이 객체가 저장되거나 삭제될 때 연관된 객체의 updated_at
또는 updated_on
타임스탬프가 현재 시간으로 설정됩니다:
class Book < ApplicationRecord belongs_to :author, touch: true end class Author < ApplicationRecord has_many :books end
이 경우 책을 저장하거나 삭제하면 연관된 저자의 타임스탬프가 업데이트됩니다. 특정 타임스탬프 속성을 업데이트하도록 지정할 수도 있습니다:
class Book < ApplicationRecord belongs_to :author, touch: :books_updated_at end
:validate
:validate
옵션을 true
로 설정하면 이 객체를 저장할 때 새 연관 객체가 유효성 검사됩니다. 기본적으로 false
입니다: 이 객체를 저장할 때 새 연관 객체가 유효성 검사되지 않습니다.
belongs_to
에 대한 범위
때때로 belongs_to
에서 사용되는 쿼리를 사용자 정의하고 싶을 수 있습니다. 이러한 사용자 정의는 범위 블록을 통해 수행할 수 있습니다. 예를 들어:
class Book < ApplicationRecord belongs_to :author, -> { where active: true } end
표준 쿼리 메서드를 범위 블록 내에서 사용할 수 있습니다. 아래에서 설명하는 메서드는 다음과 같습니다:
where
includes
readonly
select
where
where
메서드를 사용하면 연관된 객체가 충족해야 하는 조건을 지정할 수 있습니다.
class Book < ApplicationRecord belongs_to :author, -> { where active: true } end
includes
includes
메서드를 사용하면 이 연관관계가 사용될 때 미리 로드해야 하는 두 번째 수준의 연관관계를 지정할 수 있습니다. 예를 들어 다음과 같은 모델을 고려해 보겠습니다:
class Chapter < ApplicationRecord belongs_to :book end class Book < ApplicationRecord belongs_to :author has_many :chapters end class Author < ApplicationRecord has_many :books end
종종 챕터에서 직접 저자를 가져오는 경우(@chapter.book.author
)가 있다면 책에서 저자를 미리 로드하는 것이 코드를 약간 더 효율적으로 만들 수 있습니다:
class Chapter < ApplicationRecord belongs_to :book, -> { includes :author } end class Book < ApplicationRecord belongs_to :author has_many :chapters end class Author < ApplicationRecord has_many :books end
참고: 즉시 연관관계에는 includes
를 사용할 필요가 없습니다. 즉, Book belongs_to :author
와 같은 경우 저자는 필요할 때 자동으로 미리 로드됩니다.
readonly
readonly
를 사용하면 연관관계를 통해 검색된 연관된 객체는 읽기 전용이 됩니다.
select
select
메서드를 사용하면 연관된 객체에 대한 데이터를 검색하는 SQL SELECT
절을 재정의할 수 있습니다. 기본적으로 Rails는 모든 열을 검색합니다.
팁: belongs_to
연관관계에서 select
를 사용하는 경우 올바른 결과를 보장하려면 :foreign_key
옵션도 설정해야 합니다.
연관된 객체가 존재하는지 확인하기
association.nil?
메서드를 사용하여 연관된 객체가 존재하는지 확인할 수 있습니다:
if @book.author.nil? @msg = "No author found for this book" end
객체가 언제 저장되는가?
belongs_to
연관관계에 객체를 할당하면 해당 객체가 자동으로 저장되지 네, 계속해서 번역하겠습니다:
않습니다. 연관된 객체도 자동으로 저장되지 않습니다.
has_one
연관관계 참조
has_one
연관관계는 다른 모델과의 일대일 관계를 생성합니다. 데이터베이스 용어로 이 연관관계는 다른 클래스에 외래 키가 포함되어 있음을 의미합니다. 이 클래스에 외래 키가 포함되어 있다면 대신 belongs_to
를 사용해야 합니다.
has_one
이 추가하는 메서드
has_one
연관관계를 선언하면 선언 클래스에 연관관계와 관련된 6개의 메서드가 자동으로 추가됩니다:
association
association=(associate)
build_association(attributes = {})
create_association(attributes = {})
create_association!(attributes = {})
reload_association
reset_association
이 모든 메서드에서 association
은 has_one
에 전달된 첫 번째 인수로 바뀝니다. 예를 들어 다음과 같은 선언이 있다면:
class Supplier < ApplicationRecord has_one :account end
Supplier
모델의 각 인스턴스에는 다음과 같은 메서드가 있습니다:
account
account=
build_account
create_account
create_account!
reload_account
reset_account
참고: has_one
또는 belongs_to
연관관계를 초기화할 때는 build_
접두사를 사용해야 하며, has_many
또는 has_and_belongs_to_many
연관관계에서 사용되는 association.build
메서드를 사용할 수 없습니다. 생성하려면 create_
접두사를 사용하세요.
association
association
메서드는 연관된 객체(있는 경우)를 반환합니다. 연관된 객체를 찾을 수 없는 경우 nil
을 반환합니다.
@account = @supplier.account
연관된 객체가 이미 이 객체에 대해 데이터베이스에서 검색된 경우 캐시된 버전이 반환됩니다. 이 동작을 무시하고(데이터베이스 읽기를 강제하려면) 부모 객체에서 #reload_association
을 호출하세요.
@account = @supplier.reload_account
연관된 객체의 캐시된 버전을 제거하여(다음 액세스 시 데이터베이스에서 쿼리하도록 하려면) 부모 객체에서 #reset_association
을 호출하세요.
@supplier.reset_account
association=(associate)
association=
메서드는 연관된 객체를 이 객체에 할당합니다. 내부적으로는 이 객체의 기본 키를 추출하고 연관된 객체의 외래 키를 동일한 값으로 설정합니다.
@supplier.account = @account
build_association(attributes = {})
build_association
메서드는 연관된 유형의 새 객체를 반환합니다. 이 객체는 전달된 속성에서 인스턴스화되고, 외래 키를 통해 링크가 설정됩니다. 그러나 연관된 객체는 아직 저장되지 않습니다.
@account = @supplier.build_account(terms: "Net 30")
create_association(attributes = {})
create_association
메서드는 연관된 유형의 새 객체를 반환합니다. 이 객체는 전달된 속성에서 인스턴스화되고, 외래 키를 통해 링크가 설정됩니다. 그리고 연관된 모델에 지정된 모든 유효성 검사를 통과하면 연관된 객체 도 저장됩니다.
@account = @supplier.create_account(terms: "Net 30")
create_association!(attributes = {})
create_association
과 동일하지만, 레코드가 유효하지 않은 경우 ActiveRecord::RecordInvalid
를 발생시킵니다.
has_one
에 대한 옵션
Rails는 대부분의 상황에서 잘 작동하는 지능적인 기본값을 사용하지만, has_one
연관관계 참조의 동작을 사용자 정의해야 하는 경우도 있습니다. 이러한 사용자 정의는 연관관계를 생성할 때 옵션을 전달하여 쉽게 수행할 수 있습니다. 예를 들어 이 연관관계는 두 가지 옵션을 사용합니다:
class Supplier < ApplicationRecord has_one :account, class_name: "Billing", dependent: :nullify end
has_one
연관관계는 다음과 같은 옵션을 지원합니다:
:as
:autosave
:class_name
:dependent
:disable_joins
:ensuring_owner_was
:foreign_key
:inverse_of
:primary_key
:query_constraints
:required
:source
:source_type
:strict_loading
:through
:touch
:validate
:as
:as
옵션을 설정하면 이것이 다형성 연관관계임을 나타냅니다. 다형성 연관관계는 이 가이드의 앞부분에서 자세히 다루었습니다.
:autosave
:autosave
옵션을 true
로 설정하면 Rails는 로드된 연관 멤버를 저장하고 삭제 표시된 멤버를 삭제할 것입니다. :autosave
옵션을 false
로 설정하는 것은 :autosave
옵션을 설정하지 않는 것과 같지 않습니다. :autosave
옵션이 없는 경우 새 연관 객체는 저장되지만 업데이트된 연관 객체는 저장되지 않습니다.
:class_name
다른 모델의 이름을 연관관계 이름에서 유추할 수 없는 경우 :class_name
옵션을 사용하여 모델 이름을 제공할 수 있습니다. 예를 들어 공급업체에 계정이 있지만 실제 계정 모델의 이름이 Billing
인 경우 다음과 같이 설정할 수 있습니다:
class Supplier < ApplicationRecord has_one :account, class_name: "Billing" end
:dependent
연관된 객체가 소유자와 함께 삭제될 때의 동작을 제어합니다:
:destroy
는 연관된 객체도 삭제됩니다:delete
는 연관된 객체가 데이터베이스에서 직접 삭제됩니다(콜백이 실행되지 않음):destroy_async
: 객체가 삭제될 때ActiveRecord::DestroyAssociationAsyncJob
작업이 대기열에 추가되어 연관된 객체에 대해 destroy가 호출됩니다. 이 기능을 사용하려면 Active Job이 설정되어 있어야 합니다. 데이터베이스의 외래 키 제약 조건이 연관관계에 의해 백업되는 경우에는 이 옵션을 사용하지 마세요. 외래 키 제약 조건 작업은 소유자를 삭제하는 것과 동일한 트랜잭션 내에서 발생합니다.:nullify
는 외래 키가NULL
로 설정됩니다. 다형성 연관관계의 경우 유형 열도 null로 설정됩니다. 콜백이 실행되지 않습니다.:restrict_with_exception
은 연관된 레코드가 있는 경우ActiveRecord::DeleteRestrictionError
예외가 발생합니다:restrict_with_error
은 연관된 객체가 있는 경우 소유자에게 오류가 추가됩니다
NOT NULL
데이터베이스 제약 조건이 있는 연관관계에 대해서는 :nullify
옵션을 설정하거나 남겨두지 마세요. 연관된 객체를 삭제하지 않으려면 dependent
를 destroy로 설정해야 합니다. 그렇지 않으면 초기 연관된 객체의 외래 키가 허용되지 않는 NULL
값으로 설정되어 연관된 객체를 변경할 수 없습니다.
:disable_joins
연관관계에 대한 조인을 건너뛸지 여부를 지정합니다. true
로 설정하면 두 개 이상의 쿼리가 생성됩니다. 일부 경우에는 정렬 또는 제한이 적용되면 데이터베이스 제한으로 인해 메모리 내에서 수행됩니다. 이 옵션은 has_one :through
연관관계에만 적용되며 has_one
단독으로는 조인을 수행하지 않습니다.
:ensuring_owner_was
소유자에게 호출되는 인스턴스 메서드를 지정합니다. 메서드는 연관된 레코드가 백그라운드 작업에서 삭제되도록 true
를 반환해야 합니다.
:foreign_key
관례상 Rails는 다른 모델의 외래 키 열 이름이 이 모델 이름에 _id
접미사가 추가된 것이라고 가정합니다. :foreign_key
옵션을 사용하면 외래 키의 이름을 직접 설정할 수 있습니다:
class Supplier < ApplicationRecord has_one :account, foreign_key: "supp_id" end
팁: 어떤 경우든 Rails는 외래 키 열을 자동으로 생성하지 않습니다. 마이그레이션의 일부로 명시적으로 정의해야 합니다.
:inverse_of
:inverse_of
옵션은 이 연관관계의 역방향 belongs_to
연관관계의 이름을 지정합니다.
양방향 연관관계 섹션에서 자세한 내용을 확인하세요.
class Supplier < ApplicationRecord has_one :account, inverse_of: :supplier end class Account < ApplicationRecord belongs_to :supplier, inverse_of: :account end
:primary_key
관례상 Rails는 이 모델의 기본 키 열이 id
라고 가정합니다. :primary_key
옵션을 사용하면 다른 열을 지정할 수 있습니다.
:query_constraints
복합 외래 키 역할을 합니다. 연관된 객체를 쿼리하는 데 사용할 열 목록을 정의합니다. 이 옵션은 선택 사항입니다. 기본적으로 Rails는 값을 자동으로 유추하려고 합니다. 값을 설정하는 경우 배열 크기는 연관 모델의 기본 키 또는 query_constraints
크기와 일치해야 합니다.
:required
true
로 설정하면 연관관계 자체의 존재도 유효성 검사됩니다. 이는 ID가 아닌 연관관계 자체를 검증합니다. :inverse_of
를 사용하면 추가 쿼리 없이 유효성 검사를 수행할 수 있습니다.
:source
:source
옵션은 has_one :through
연관관계의 소스 연관관계 이름을 지정합니다.
:source_type
:source_type
옵션은 다형성 연관관계를 통해 진행되는 has_one :through
연관관계의 소스 연관관계 유형을 지정합니다.
class Author < ApplicationRecord has_one :book has_one :hardback, through: :book, source: :format, source_type: "Hardback" has_one :dust_jacket, through: :hardback end class Book < ApplicationRecord belongs_to :format, polymorphic: true end class Paperback < ApplicationRecord; end class Hardback < ApplicationRecord has_one :dust_jacket end class DustJacket < ApplicationRecord; end
:strict_loading
이 연관관계를 통해 연관된 레코드가 로드될 때마다 strict loading을 적용합니다.
:through
:through
옵션은네, 계속해서 번역하겠습니다:
쿼리를 수행할 조인 모델을 지정합니다. has_one :through
연관관계는 이 가이드의 앞부분에서 자세히 다루었습니다.
:touch
:touch
옵션을 true
로 설정하면 이 객체가 저장되거나 삭제될 때 연관된 객체의 updated_at
또는 updated_on
타임스탬프가 현재 시간으로 설정됩니다:
class Supplier < ApplicationRecord has_one :account, touch: true end class Account < ApplicationRecord belongs_to :supplier end
이 경우 공급업체를 저장하거나 삭제하면 연관된 계정의 타임스탬프가 업데이트됩니다. 특정 타임스탬프 속성을 업데이트하도록 지정할 수도 있습니다:
class Supplier < ApplicationRecord has_one :account, touch: :suppliers_updated_at end
:validate
:validate
옵션을 true
로 설정하면 이 객체를 저장할 때 새 연관 객체가 유효성 검사됩니다. 기본적으로 false
입니다: 이 객체를 저장할 때 새 연관 객체가 유효성 검사되지 않습니다.
has_one
에 대한 범위
때때로 has_one
에서 사용되는 쿼리를 사용자 정의하고 싶을 수 있습니다. 이러한 사용자 정의는 범위 블록을 통해 수행할 수 있습니다. 예를 들어:
class Supplier < ApplicationRecord has_one :account, -> { where active: true } end
표준 쿼리 메서드를 범위 블록 내에서 사용할 수 있습니다. 아래에서 설명하는 메서드는 다음과 같습니다:
where
includes
readonly
select
where
where
메서드를 사용하면 연관된 객체가 충족해야 하는 조건을 지정할 수 있습니다.
class Supplier < ApplicationRecord has_one :account, -> { where "confirmed = 1" } end
includes
includes
메서드를 사용하면 이 연관관계가 사용될 때 미리 로드해야 하는 두 번째 수준의 연관관계를 지정할 수 있습니다. 예를 들어 다음과 같은 모델을 고려해 보겠습니다:
class Supplier < ApplicationRecord has_one :account end class Account < ApplicationRecord belongs_to :supplier belongs_to :representative end class Representative < ApplicationRecord has_many :accounts end
종종 공급업체에서 직접 대표를 가져오는 경우(@supplier.account.representative
)가 있다면 공급업체에서 계정을 통해 대표를 미리 로드하는 것이 코드를 약간 더 효율적으로 만들 수 있습니다:
class Supplier < ApplicationRecord has_one :account, -> { includes :representative } end class Account < ApplicationRecord belongs_to :supplier belongs_to :representative end class Representative < ApplicationRecord has_many :accounts end
readonly
readonly
를 사용하면 연관관계를 통해 검색된 연관된 객체는 읽기 전용이 됩니다.
select
select
메서드를 사용하면 연관된 객체에 대한 데이터를 검색하는 SQL SELECT
절을 재정의할 수 있습니다. 기본적으로 Rails는 모든 열을 검색합니다.
연관된 객체가 존재하는지 확인하기
association.nil?
메서드를 사용하여 연관된 객체가 존재하는지 확인할 수 있습니다:
if @supplier.account.nil? @msg = "No account found for this supplier" end
객체가 언제 저장되는가?
has_one
연관관계에 객체를 할당하면 해당 객체가 자동으로 저장됩니다(외래 키를 업데이트하기 위해). 여러 객체를 한 문장에 할당하면 모두 저장됩니다.
이러한 저장 중 하나라도 유효성 검사 오류로 인해 실패하면 할당 문이 false
를 반환하고 할당 자체가 취소됩니다.
부모 객체(has_one
연관관계를 선언한 객체)가 저장되지 않은 경우(new_record?
가 true
를 반환) 연관 객체는 추가될 때 저장되지 않습니다. 부모가 저장될 때 연관관계의 모든 저장되지 않은 멤버가 자동으로 저장됩니다.
has_one
연관관계에 객체를 할당하되 객체를 저장하지 않으려면 build_association
메서드를 사용하세요.
has_many
연관관계 참조
has_many
연관관계는 다른 모델과의 일대다 관계를 생성합니다. 데이터베이스 용어로 이 연관관계는 다른 클래스에 이 클래스를 참조하는 외래 키가 포함되어 있음을 의미합니다.
has_many
가 추가하는 메서드
has_many
연관관계를 선언하면 선언 클래스에 연관관계와 관련된 17개의 메서드가 자동으로 추가됩니다:
collection
collection<<(object, ...)
collection.delete(object, ...)
collection.destroy(object, ...)
collection=(objects)
collection_singular_ids
collection_singular_ids=(ids)
collection.clear
collection.empty?
collection.size
collection.find(...)
collection.where(...)
collection.exists?(...)
collection.build(attributes = {})
collection.create(attributes = {})
collection.create!(attributes = {})
collection.reload
이 모든 메서드에서 collection
은 has_many
에 전달된 첫 번째 인수로 바뀌고, collection_singular
는 해당 기호의 단수형 버전으로 바뀝니다. 예를 들어 다음과 같은 선언이 있다면:
class Author < ApplicationRecord has_many :books end
Author
모델의 각 인스턴스에는 다음과 같은 메서드가 있습니다:
books books<<(object, ...) books.delete(object, ...) books.destroy(object, ...) books=(objects) book_ids book_ids=(ids) books.clear books.empty? books.size books.find(...) books.where(...) books.exists?(...) books.build(attributes = {}, ...) books.create(attributes = {}) books.create!(attributes = {}) books.reload
collection
collection
메서드는 연관된 모든 객체의 Relation을 반환합니다. 연관된 객체가 없는 경우 빈 Relation을 반환합니다.
@books = @author.books
collection<<(object, ...)
collection<<
메서드는 하나 이상의 객체를 컬렉션에 추가합니다. 이 메서드는 객체의 외래 키를 호출 모델의 기본 키로 설정합니다.
@author.books << @book1
collection.delete(object, ...)
collection.delete
메서드는 하나 이상의 객체를 컬렉션에서 제거합니다. 이 메서드는 객체의 외래 키를 NULL
로 설정합니다.
@author.books.delete(@book1)
경고: 또한 dependent: :destroy
와 연결된 객체는 삭제되고, dependent: :delete_all
과 연결된 객체는 삭제됩니다.
collection.destroy(object, ...)
collection.destroy
메서드는 하나 이상의 객체를 컬렉션에서 제거합니다. 이 메서드는 각 객체에 대해 destroy
를 실행합니다.
@author.books.destroy(@book1)
경고: 객체는 항상 데이터베이스에서 제거되며, :dependent
옵션은 무시됩니다.
collection=(objects)
collection=
메서드는 추가 및 삭제를 통해 컬렉션이 제공된 객체만 포함하도록 만듭니다. 변경 사항은 데이터베이스에 영구적으로 저장됩니다.
collection_singular_ids
collection_singular_ids
메서드는 컬렉션의 객체 ID 배열을 반환합니다.
@book_ids = @author.book_ids
collection_singular_ids=(ids)
collection_singular_ids=
메서드는 추가 및 삭제를 통해 제공된 기본 키 값으로 식별된 객체만 포함하도록 컬렉션을 만듭니다. 변경 사항은 데이터베이스에 영구적으로 저장됩니다.
collection.clear
collection.clear
메서드는 dependent
옵션에 지정된 전략에 따라 컬렉션의 모든 객체를 제거합니다. 옵션이 지정되지 않은 경우 기본 전략을 따릅니다. has_many :through
연관관계의 기본 전략은 delete_all
이고, has_many
연관관계의 기본 전략은 외래 키를 NULL
로 설정하는 것입니다.
@author.books.clear
경고: dependent: :destroy
또는 dependent: :destroy_async
와 연결된 객체는 dependent: :delete_all
과 마찬가지로 삭제됩니다.
collection.empty?
collection.empty?
메서드는 컬렉션에 연관된 객체가 없는 경우 true
를 반환합니다.
<% if @author.books.empty? %> No Books Found <% end %>
collection.size
collection.size
메서드는 컬렉션의 객체 수를 반환합니다.
@book_count = @author.books.size
collection.find(...)
collection.find
메서드는 컬렉션의 테이블에서 객체를 찾습니다.
@available_book = @author.books.find(1)
collection.where(...)
collection.where
메서드는 제공된 조건에 따라 컬렉션 내의 객체를 찾지만, 객체는 지연 로드되므로 데이터베이스 쿼리는 객체에 액세스할 때만 실행됩니다.
@available_books = @author.books.where(available: true) # 아직 쿼네, 계속해서 번역하겠습니다: 리가 실행되지 않음 @available_book = @available_books.first # 이제 데이터베이스가 쿼리됨
collection.exists?(...)
collection.exists?
메서드는 제공된 조건을 충족하는 객체가 컬렉션의 테이블에 존재하는지 확인합니다.
collection.build(attributes = {})
collection.build
메서드는 연관된 유형의 단일 또는 배열 객체를 반환합니다. 객체는 전달된 속성에서 인스턴스화되고 외래 키를 통해 링크가 생성되지만, 연관된 객체는 아직 저장되지 않습니다.
@book = @author.books.build(published_at: Time.now, book_number: "A12345") @books = @author.books.build([ { published_at: Time.now, book_number: "A12346" }, { published_at: Time.now, book_number: "A12347" } ])
collection.create(attributes = {})
collection.create
메서드는 연관된 유형의 단일 또는 배열 객체를 반환합니다. 객체는 전달된 속성에서 인스턴스화되고 외래 키를 통해 링크가 생성됩니다. 그리고 연관된 모델에 지정된 모든 유효성 검사를 통과하면 연관된 객체 도 저장됩니다.
@book = @author.books.create(published_at: Time.now, book_number: "A12345") @books = @author.books.create([ { published_at: Time.now, book_number: "A12346" }, { published_at: Time.now, book_number: "A12347" } ])
collection.create!(attributes = {})
collection.create
와 동일하지만, 레코드가 유효하지 않은 경우 ActiveRecord::RecordInvalid
를 발생시킵니다.
collection.reload
collection.reload
메서드는 데이터베이스 읽기를 강제하여 연관된 모든 객체의 Relation을 반환합니다. 연관된 객체가 없는 경우 빈 Relation을 반환합니다.
@books = @author.books.reload
has_many
에 대한 옵션
Rails는 대부분의 상황에서 잘 작동하는 지능적인 기본값을 사용하지만, has_many
연관관계 참조의 동작을 사용자 정의해야 하는 경우도 있습니다. 이러한 사용자 정의는 연관관계를 생성할 때 옵션을 전달하여 쉽게 수행할 수 있습니다. 예를 들어 이 연관관계는 두 가지 옵션을 사용합니다:
class Author < ApplicationRecord has_many :books, dependent: :delete_all, validate: false end
has_many
연관관계는 다음과 같은 옵션을 지원합니다:
:as
:autosave
:class_name
:counter_cache
:dependent
:disable_joins
:ensuring_owner_was
:extend
:foreign_key
:foreign_type
:inverse_of
:primary_key
:query_constraints
:source
:source_type
:strict_loading
:through
:validate
:as
:as
옵션을 설정하면 이것이 다형성 연관관계임을 나타냅니다. 다형성 연관관계는 이 가이드의 앞부분에서 자세히 다루었습니다.
:autosave
:autosave
옵션을 true
로 설정하면 Rails는 로드된 연관 멤버를 저장하고 삭제 표시된 멤버를 삭제할 것입니다. :autosave
옵션을 false
로 설정하는 것은 :autosave
옵션을 설정하지 않는 것과 같지 않습니다. :autosave
옵션이 없는 경우 새 연관 객체는 저장되지만 업데이트된 연관 객체는 저장되지 않습니다.
:class_name
다른 모델의 이름을 연관관계 이름에서 유추할 수 없는 경우 :class_name
옵션을 사용하여 모델 이름을 제공할 수 있습니다. 예를 들어 저자에게 많은 책이 있지만 실제 책 모델의 이름이 Transaction
인 경우 다음과 같이 설정할 수 있습니다:
class Author < ApplicationRecord has_many :books, class_name: "Transaction" end
:counter_cache
이 옵션을 사용하여 사용자 정의 :counter_cache
를 구성할 수 있습니다. belongs_to
연관관계에서 :counter_cache
이름을 사용자 정의한 경우에만 이 옵션이 필요합니다.
:dependent
연관된 객체가 소유자와 함께 삭제될 때의 동작을 제어합니다:
:destroy
는 연관된 모든 객체도 삭제됩니다:delete_all
은 연관된 모든 객체가 데이터베이스에서 직접 삭제됩니다(콜백이 실행되지 않음):destroy_async
: 객체가 삭제될 때ActiveRecord::DestroyAssociationAsyncJob
작업이 대기열에 추가되어 연관된 객체에 대해 destroy가 호출됩니다. 이 기능을 사용하려면 Active Job이 설정되어 있어야 합니다.:nullify
는 외래 키가NULL
로 설정됩니다. 다형성 연관관계의 경우 유형 열도 null로 설정됩니다. 콜백이 실행되지 않습니다.:restrict_with_exception
은 연관된 레코드가 있는 경우ActiveRecord::DeleteRestrictionError
예외가 발생합니다:restrict_with_error
은 연관된 객체가 있는 경우 소유자에게 오류가 추가됩니다
:destroy
및 :delete_all
옵션은 collection.delete
및 collection=
메서드의 의미론에도 영향을 미쳐, 컬렉션에서 제거될 때 연관된 객체가 삭제됩니다.
:disable_joins
연관관계에 대한 조인을 건너뛸지 여부를 지정합니다. true
로 설정하면 두 개 이상의 쿼리가 생성됩니다. 일부 경우에는 정렬 또는 제한이 적용되면 데이터베이스 제한으로 인해 메모리 내에서 수행됩니다. 이 옵션은 has_many :through
연관관계에만 적용되며 has_many
단독으로는 조인을 수행하지 않습니다.
:ensuring_owner_was
소유자에게 호출되는 인스턴스 메서드를 지정합니다. 메서드는 연관된 레코드가 백그라운드 작업에서 삭제되도록 true
를 반환해야 합니다.
:extend
연관관계 객체에 확장될 모듈 또는 모듈 배열을 지정합니다. 연관관계에 메서드를 정의할 때 특히 유용하며, 여러 연관관계 객체 간에 공유되어야 할 때 유용합니다.
:foreign_key
관례상 Rails는 다른 모델의 외래 키 열 이름이 이 모델 이름에 _id
접미사가 추가된 것이라고 가정합니다. :foreign_key
옵션을 사용하면 외래 키의 이름을 직접 설정할 수 있습니다:
class Author < ApplicationRecord has_many :books, foreign_key: "cust_id" end
팁: 어떤 경우든 Rails는 외래 키 열을 자동으로 생성하지 않습니다. 마이그레이션의 일부로 명시적으로 정의해야 합니다.
:foreign_type
이 연관관계가 다형성 연관관계인 경우 연관된 객체의 유형을 저장하는 열을 지정합니다. 기본적으로 ”as
“ 옵션에 지정된 다형성 연관관계의 이름에 ”_type
“ 접미사가 추가된 이름으로 추정됩니다. 따라서 has_many :tags, as: :taggable
연관관계를 정의하는 클래스는 기본적으로 ”taggable_type
“을 :foreign_type
로 사용합니다.
:inverse_of
:inverse_of
옵션은 이 연관관계의 역방향 belongs_to
연관관계의 이름을 지정합니다.
양방향 연관관계 섹션에서 자세한 내용을 확인하세요.
:primary_key
관례상 Rails는 연관 모델의 기본 키 열이 id
라고 가정합니다. :primary_key
옵션을 사용하면 다른 열을 명시적으로 지정할 수 있습니다.
예를 들어 users
테이블의 기본 키가 id
가 아닌 guid
라고 가정해 보겠습니다. todos
테이블에 외래 키 user_id
를 guid
열에 보관하려면 :primary_key
를 사용하면 됩니다:
class User < ApplicationRecord has_many :todos, primary_key: :guid end
:query_constraints
복합 외래 키 역할을 합니다. 연관된 객체를 쿼리하는 데 사용할 열 목록을 정의합니다. 이 옵션은 선택 사항입니다. 기본적으로 Rails는 값을 자동으로 유추하려고 합니다. 값을 설정하는 경우 배열 크기는 연관 모델의 기본 키 또는 query_constraints
크기와 일치해야 합니다.
:source
:source
옵션은 has_many :through
연관관계의 소스 연관관계 이름을 지정합니다.
:source_type
:source_type
옵션은 다형성 연관관계를 통해 진행되는 has_many :through
연관관계의 소스 연관관계 유형을 지정합니다.
class Author < ApplicationRecord has_many :books has_many :paperbacks, through: :books, source: :format, source_type: "Paperback" end class Book < ApplicationRecord belongs_to :format, polymorphic: true end class Hardback < ApplicationRecord; end class Paperback < ApplicationRecord; end
:strict_loading
true
로 설정하면 이 연관관계를 통해 연관된 레코드가 로드될 때마다 strict loading을 적용합니다.
:through
:through
옵션은 쿼리를 수행할 조인 모델을 지정합니다. has_many :through
연관관계는 이 가이드의 앞부분에서 다루었던 다대다 관계를 구현하는 방법을 제공합니다.
:validate
:validate
옵션을 false
로 설정하면 이 객체를 저장할 때 새 연관 객체가 유효성 검사되지 않습니다. 기본적으로 true
입니다: 이 객체를 저장할 때 새 연관 객체가 유효성 검사됩니다.
has_many
에 대한 범위
때때로 has_many
에서 사용되는 쿼리를 사용자 정의하고 싶을 수 있습니다. 이러한 사용자 정의는 범위 블록을 통해 수행할 수 있습니다. 예를 들어:
class Author < ApplicationRecord has_many :books, -> { where processed: true } end
표준 쿼리 메서드를 범위 블록 내에서 사용할 수 있습니다. 아래에서 설명하는 메서드는 다음과 같습니다:
where
extending
group
includes
limit
offset
order
readonly
select
distinct
where
where
메서드를 사용하면 연관된 객체가 충족해야 하는 조건을 지네, 계속해서 번역하겠습니다:
정할 수 있습니다.
class Author < ApplicationRecord has_many :confirmed_books, -> { where "confirmed = 1" }, class_name: "Book" end
해시 스타일 where
옵션을 사용할 수도 있습니다:
class Author < ApplicationRecord has_many :confirmed_books, -> { where confirmed: true }, class_name: "Book" end
해시 스타일 where
를 사용하면 이 연관관계를 통한 레코드 생성이 자동으로 해시 범위에 의해 제한됩니다. 이 경우 @author.confirmed_books.create
또는 @author.confirmed_books.build
를 사용하면 confirmed
열이 true
인 책이 생성됩니다.
extending
extending
메서드는 연관관계 프록시를 확장할 명명된 모듈을 지정합니다. 연관관계 확장은 이 가이드의 뒷부분에서 자세히 다룹니다.
group
group
메서드는 결과 집합을 그룹화할 속성 이름을 제공합니다. SQL의 GROUP BY
절을 사용합니다.
class Author < ApplicationRecord has_many :chapters, -> { group 'books.id' }, through: :books end
includes
includes
메서드를 사용하면 이 연관관계가 사용될 때 미리 로드해야 하는 두 번째 수준의 연관관계를 지정할 수 있습니다. 예를 들어 다음과 같은 모델을 고려해 보겠습니다:
class Author < ApplicationRecord has_many :books end class Book < ApplicationRecord belongs_to :author has_many :chapters end class Chapter < ApplicationRecord belongs_to :book end
종종 저자에서 직접 챕터를 가져오는 경우(@author.books.chapters
)가 있다면 책에서 챕터를 미리 로드하는 것이 코드를 약간 더 효율적으로 만들 수 있습니다:
class Author < ApplicationRecord has_many :books, -> { includes :chapters } end class Book < ApplicationRecord belongs_to :author has_many :chapters end class Chapter < ApplicationRecord belongs_to :book end
limit
limit
메서드를 사용하면 연관관계를 통해 가져올 객체 수를 제한할 수 있습니다.
class Author < ApplicationRecord has_many :recent_books, -> { order('published_at desc').limit(100) }, class_name: "Book" end
offset
offset
메서드를 사용하면 연관관계를 통해 객체를 가져올 때 시작 오프셋을 지정할 수 있습니다. 예를 들어 -> { offset(11) }
은 처음 11개 레코드를 건너뜁니다.
order
order
메서드는 연관된 객체가 수신되는 순서를 결정합니다(SQL ORDER BY
절 구문 사용).
class Author < ApplicationRecord has_many :books, -> { order "date_confirmed DESC" } end
readonly
readonly
를 사용하면 연관관계를 통해 검색된 연관된 객체는 읽기 전용이 됩니다.
select
select
메서드를 사용하면 연관된 객체에 대한 데이터를 검색하는 SQL SELECT
절을 재정의할 수 있습니다. 기본적으로 Rails는 모든 열을 검색합니다.
경고: 직접 select
를 지정하는 경우 연관 모델의 기본 키와 외래 키 열을 반드시 포함해야 합니다. 그렇지 않으면 Rails에서 오류가 발생합니다.
distinct
distinct
메서드를 사용하면 컬렉션에서 중복을 제거할 수 있습니다. 이는 주로 :through
옵션과 함께 유용합니다.
class Person < ApplicationRecord has_many :readings has_many :articles, through: :readings end
irb> person = Person.create(name: 'John') irb> article = Article.create(name: 'a1') irb> person.articles << article irb> person.articles << article irb> person.articles.to_a => [#<Article id: 5, name: "a1">, #<Article id: 5, name: "a1">] irb> Reading.all.to_a => [#<Reading id: 12, person_id: 5, article_id: 5>, #<Reading id: 13, person_id: 5, article_id: 5>]
위의 경우 두 개의 읽기 기록이 있지만 person.articles
는 둘 다 가져옵니다. 동일한 기사를 가리키고 있기 때문입니다.
이제 distinct
를 설정해 보겠습니다:
class Person has_many :readings has_many :articles, -> { distinct }, through: :readings end
irb> person = Person.create(name: 'Honda') irb> article = Article.create(name: 'a1') irb> person.articles << article irb> person.articles << article irb> person.articles.to_a => [#<Article id: 7, name: "a1">] irb> Reading.all.to_a => [#<Reading id: 16, person_id: 7, article_id: 7>, #<Reading id: 17, person_id: 7, article_id: 7>]
위의 경우 여전히 두 개의 읽기 기록이 있습니다. 그러나 person.articles
는 고유한 레코드만 표시합니다.
영구 연관관계에 고유성을 보장하려면 테이블 자체에 고유 인덱스를 추가해야 합니다. 예를 들어 readings
테이블이 있고 사람당 기사를 한 번만 추가할 수 있도록 하려면 다음과 같은 마이그레이션을 추가할 수 있습니다:
add_index :readings, [:person_id, :article_id], unique: true
그러면 기사를 사람에게 두 번 추가하려고 하면 ActiveRecord::RecordNotUnique
오류가 발생합니다:
irb> person = Person.create(name: 'Honda') irb> article = Article.create(name: 'a1') irb> person.articles << article irb> person.articles << article ActiveRecord::RecordNotUnique
include?
를 사용하여 고유성을 확인하려고 하면 경쟁 상태가 발생할 수 있습니다. 위의 기사 예에서와 같이 include?
를 사용하여 고유성을 적용하려고 하지 마세요. 여러 사용자가 동시에 이 작업을 시도할 수 있기 때문입니다:
person.articles << article unless person.articles.include?(article)
객체가 언제 저장되는가?
has_many
연관관계에 객체를 할당하면 해당 객체가 자동으로 저장됩니다(외래 키를 업데이트하기 위해). 한 문장에서 여러 객체를 할당하면 모두 저장됩니다.
이러한 저장 중 하나라도 유효성 검사 오류로 인해 실패하면 할당 문이 false
를 반환하고 할당 자체가 취소됩니다.
부모 객체(has_many
연관관계를 선언한 객체)가 저장되지 않은 경우(new_record?
가 true
를 반환) 연관 객체는 추가될 때 저장되지 않습니다. 부모가 저장될 때 연관관계의 모든 저장되지 않은 멤버가 자동으로 저장됩니다.
has_many
연관관계에 객체를 할당하되 객체를 저장하지 않으려면 collection.build
메서드를 사용하세요.
has_and_belongs_to_many
연관관계 참조
has_and_belongs_to_many
연관관계는 다른 모델과의 직접적인 다대다 관계를 생성합니다. 이 연관관계는 선언 모델의 각 인스턴스가 0개 이상의 다른 모델 인스턴스를 참조할 수 있음을 나타냅니다.
has_and_belongs_to_many
가 추가하는 메서드
has_and_belongs_to_many
연관관계를 선언하면 선언 클래스에 연관관계와 관련된 여러 메서드가 자동으로 추가됩니다:
collection
collection<<(object, ...)
collection.delete(object, ...)
collection.destroy(object, ...)
collection=(objects)
collection_singular_ids
collection_singular_ids=(ids)
collection.clear
collection.empty?
collection.size
collection.find(...)
collection.where(...)
collection.exists?(...)
collection.build(attributes = {})
collection.create(attributes = {})
collection.create!(attributes = {})
collection.reload
이 모든 메서드에서 collection
은 has_and_belongs_to_many
에 전달된 첫 번째 인수로 바뀌고, collection_singular
는 해당 기호의 단수형 버전으로 바뀝니다. 예를 들어 다음과 같은 선언이 있다면:
class Part < ApplicationRecord has_and_belongs_to_many :assemblies end
Part
모델의 각 인스턴스에는 다음과 같은 메서드가 있습니다:
assemblies assemblies<<(object, ...) assemblies.delete(object, ...) assemblies.destroy(object, ...) assemblies=(objects) assembly_ids assembly_ids=(ids) assemblies.clear assemblies.empty? assemblies.size assemblies.find(...) assemblies.where(...) assemblies.exists?(...) assemblies.build(attributes = {}, ...) assemblies.create(attributes = {}) assemblies.create!(attributes = {}) assemblies.reload
추가 열 메서드
has_and_belongs_to_many
연관관계의 조인 테이블에 두 개의 외래 키 이외에 추가 열이 있는 경우, 해당 열이 해당 연관관계를 통해 검색된 레코드에 속성으로 추가됩니다. 추가 속성이 있는 레코드는 항상 읽기 전용이 됩니다. 왜냐하면 Rails는 이러한 속성의 변경 사항을 저장할 수 없기 때문입니다.
경고: has_and_belongs_to_many
연관관계의 조인 테이블에 추가 속성을 사용하는 것은 권장되지 않습니다. 두 모델 간의 다대다 관계를 연결하는 테이블에 이러한 복잡한 동작이 필요한 경우 has_many :through
연관관계를 사용해야 합니다.
collection
collection
메서드는 연관된 모든 객체의 Relation을 반환합니다. 연관된 객체가 없는 경우 빈 Relation을 반환합니다.
@assemblies = @part.assemblies
collection<<(object, ...)
collection<<
메서드는 하나 이상의 객체를 컬렉션에 추가합니다. 이 메서드는 조인 테이블에 레코드를 생성합니다.
@part.assemblies << @assembly1
참고: 이 메서드는 collection.concat
및 collection.push
의 별칭입니다.
collection.delete(object, ...)
collection.delete
메서드는 하나 이상의 객체를 컬렉션에서 제거합니다. 이 메서드는 객체를 삭제하지 않고 조인 테이블의 레코드를 삭제합니다.
@part.assemblies.delete(@assembly1)
collection.destroy(object, ...)
collection.destroy
메서드는 하나 이상의 객체를 컬렉션에서 제거합니다. 이 메서드는 객체를 삭제하지 않고 조인 테이블의 레코드를 삭제합니다.
@part.assemblies.destroy(@assembly1)
collection=(objects)
collection=
메서드는 추가 및 삭제를 통해 컬렉션이 제공된 객체만 포함하도록 만듭니다. 변경 사항은 데이터베이스에 영구적으로 저장됩네, 계속해서 번역하겠습니다:
니다.
collection_singular_ids
collection_singular_ids
메서드는 컬렉션의 객체 ID 배열을 반환합니다.
@assembly_ids = @part.assembly_ids
collection_singular_ids=(ids)
collection_singular_ids=
메서드는 추가 및 삭제를 통해 제공된 기본 키 값으로 식별된 객체만 포함하도록 컬렉션을 만듭니다. 변경 사항은 데이터베이스에 영구적으로 저장됩니다.
collection.clear
collection.clear
메서드는 조인 테이블의 행을 삭제하여 컬렉션에서 모든 객체를 제거합니다. 이 메서드는 연관된 객체를 삭제하지 않습니다.
collection.empty?
collection.empty?
메서드는 컬렉션에 연관된 객체가 없는 경우 true
를 반환합니다.
<% if @part.assemblies.empty? %> This part is not used in any assemblies <% end %>
collection.size
collection.size
메서드는 컬렉션의 객체 수를 반환합니다.
@assembly_count = @part.assemblies.size
collection.find(...)
collection.find
메서드는 컬렉션의 테이블에서 객체를 찾습니다.
@assembly = @part.assemblies.find(1)
collection.where(...)
collection.where
메서드는 제공된 조건에 따라 컬렉션 내의 객체를 찾지만, 객체는 지연 로드되므로 데이터베이스 쿼리는 객체에 액세스할 때만 실행됩니다.
@new_assemblies = @part.assemblies.where("created_at > ?", 2.days.ago)
collection.exists?(...)
collection.exists?
메서드는 제공된 조건을 충족하는 객체가 컬렉션의 테이블에 존재하는지 확인합니다.
collection.build(attributes = {})
collection.build
메서드는 연관된 유형의 새 객체를 반환합니다. 이 객체는 전달된 속성에서 인스턴스화되고 조인 테이블을 통해 링크가 생성됩니다. 그러나 연관된 객체는 아직 저장되지 않습니다.
@assembly = @part.assemblies.build({ assembly_name: "Transmission housing" })
collection.create(attributes = {})
collection.create
메서드는 연관된 유형의 새 객체를 반환합니다. 이 객체는 전달된 속성에서 인스턴스화되고 조인 테이블을 통해 링크가 생성됩니다. 그리고 연관된 모델에 지정된 모든 유효성 검사를 통과하면 연관된 객체 도 저장됩니다.
@assembly = @part.assemblies.create({ assembly_name: "Transmission housing" })
collection.create!(attributes = {})
collection.create
와 동일하지만, 레코드가 유효하지 않은 경우 ActiveRecord::RecordInvalid
를 발생시킵니다.
collection.reload
collection.reload
메서드는 데이터베이스 읽기를 강제하여 연관된 모든 객체의 Relation을 반환합니다. 연관된 객체가 없는 경우 빈 Relation을 반환합니다.
@assemblies = @part.assemblies.reload
has_and_belongs_to_many
에 대한 옵션
Rails는 대부분의 상황에서 잘 작동하는 지능적인 기본값을 사용하지만, has_and_belongs_to_many
연관관계 참조의 동작을 사용자 정의해야 하는 경우도 있습니다. 이러한 사용자 정의는 연관관계를 생성할 때 옵션을 전달하여 쉽게 수행할 수 있습니다. 예를 들어 이 연관관계는 두 가지 옵션을 사용합니다:
class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, -> { readonly }, autosave: true end
has_and_belongs_to_many
연관관계는 다음과 같은 옵션을 지원합니다:
:association_foreign_key
:autosave
:class_name
:foreign_key
:join_table
:strict_loading
:validate
:association_foreign_key
관례상 Rails는 조인 테이블의 열을 사용하여 다른 모델에 대한 외래 키를 보유한다고 가정합니다. 이 열 이름은 해당 모델 이름에 _id
접미사가 추가된 것입니다. :association_foreign_key
옵션을 사용하면 외래 키의 이름을 직접 설정할 수 있습니다.
팁: :foreign_key
및 :association_foreign_key
옵션은 자기 조인 다대다 관계를 설정할 때 유용합니다. 예를 들어:
class User < ApplicationRecord has_and_belongs_to_many :friends, class_name: "User", foreign_key: "this_user_id", association_foreign_key: "other_user_id" end
:autosave
:autosave
옵션을 true
로 설정하면 Rails는 로드된 연관 멤버를 저장하고 삭제 표시된 멤버를 삭제할 것입니다. :autosave
옵션을 false
로 설정하는 것은 :autosave
옵션을 설정하지 않는 것과 같지 않습니다. :autosave
옵션이 없는 경우 새 연관 객체는 저장되지만 업데이트된 연관 객체는 저장되지 않습니다.
:class_name
다른 모델의 이름을 연관관계 이름에서 유추할 수 없는 경우 :class_name
옵션을 사용하여 모델 이름을 제공할 수 있습니다. 예를 들어 부품이 조립품을 가지고 있지만 실제 조립품 모델의 이름이 Gadget
인 경우 다음과 같이 설정할 수 있습니다:
class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, class_name: "Gadget" end
:foreign_key
관례상 Rails는 이 모델의 외래 키를 보유하는 조인 테이블의 열 이름이 이 모델 이름에 _id
접미사가 추가된 것이라고 가정합니다. :foreign_key
옵션을 사용하면 외래 키의 이름을 직접 설정할 수 있습니다:
class User < ApplicationRecord has_and_belongs_to_many :friends, class_name: "User", foreign_key: "this_user_id", association_foreign_key: "other_user_id" end
:join_table
기본 조인 테이블 이름이 어휘 순서에 따라 원하는 것이 아닌 경우 :join_table
옵션을 사용하여 기본값을 재정의할 수 있습니다.
:strict_loading
이 연관관계를 통해 연관된 레코드가 로드될 때마다 strict loading을 적용합니다.
:validate
:validate
옵션을 false
로 설정하면 이 객체를 저장할 때 새 연관 객체가 유효성 검사되지 않습니다. 기본적으로 true
입니다: 이 객체를 저장할 때 새 연관 객체가 유효성 검사됩니다.
has_and_belongs_to_many
에 대한 범위
때때로 has_and_belongs_to_many
에서 사용되는 쿼리를 사용자 정의하고 싶을 수 있습니다. 이러한 사용자 정의는 범위 블록을 통해 수행할 수 있습니다. 예를 들어:
class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, -> { where active: true } end
표준 쿼리 메서드를 범위 블록 내에서 사용할 수 있습니다. 아래에서 설명하는 메서드는 다음과 같습니다:
where
extending
group
includes
limit
offset
order
readonly
select
distinct
where
where
메서드를 사용하면 연관된 객체가 충족해야 하는 조건을 지정할 수 있습니다.
class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, -> { where "factory = 'Seattle'" } end
해시 스타일 where
를 사용할 수도 있습니다:
class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, -> { where factory: 'Seattle' } end
해시 스타일 where
를 사용하면 이 연관관계를 통한 레코드 생성이 자동으로 해시 범위에 의해 제한됩니다. 이 경우 @parts.assemblies.create
또는 @parts.assemblies.build
를 사용하면 factory
열이 "Seattle"인 조립품이 생성됩니다.
extending
extending
메서드는 연관관계 프록시를 확장할 명명된 모듈을 지정합니다. 연관관계 확장은 이 가이드의 뒷부분에서 자세히 다룹니다.
group
group
메서드는 결과 집합을 그룹화할 속성 이름을 제공합니다. SQL의 GROUP BY
절을 사용합니다.
class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, -> { group "factory" } end
includes
includes
메서드를 사용하면 이 연관관계가 사용될 때 미리 로드해야 하는 두 번째 수준의 연관관계를 지정할 수 있습니다.
limit
limit
메서드를 사용하면 연관관계를 통해 가져올 객체 수를 제한할 수 있습니다.
class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, -> { order("created_at DESC").limit(50) } end
offset
offset
메서드를 사용하면 연관관계를 통해 객체를 가져올 때 시작 오프셋을 지정할 수 있습니다. 예를 들어 offset(11)
을 설정하면 처음 11개 레코드를 건너뜁니다.
order
order
메서드는 연관된 객체가 수신되는 순서를 결정합니다(SQL ORDER BY
절 구문 사용).
class Parts < ApplicationRecord has_and_belongs_to_many :assemblies, -> { order "assembly_name ASC" } end
readonly
readonly
를 사용하면 연관관계를 통해 검색된 연관된 객체는 읽기 전용이 됩니다.
select
select
메서드를 사용하면 연관된 객체에 대한 데이터를 검색하는 SQL SELECT
절을 재정의할 수 있습니다. 기본적으로 Rails는 모든 열을 검색합니다.
distinct
distinct
메서드를 사용하면 컬렉션에서 중복을 제거할 수 있습니다.
객체가 언제 저장되는가?
has_and_belongs_to_many
연관관계에 객체를 할당하면 해당 객체가 자동으로 저장됩니다(조인 테이블을 업데이트하기 위해). 한 문장에서 여러 객체를 할당하면 모두 저장됩니다.
이러한 저장 중 하나라도 유효성 검사 오류로 인해 실패하면 할당 문이 false
를 반환하고 할당 자체가 취소됩니다.
부모 객체(has_and_belongs_to_many
연관관계를 선언한 객체)가 저장되지 않은 경우(new_record?
가 true
를 반환) 연관 객체는 추가될 때 저장되지 않습니다. 부모가 저장될 때 연관관계의 모든 저장되지 네, 계속해서 번역하겠습니다:
않은 멤버가 자동으로 저장됩니다.
has_and_belongs_to_many
연관관계에 객체를 할당하되 객체를 저장하지 않으려면 collection.build
메서드를 사용하세요.
연관관계 콜백
일반 콜백은 액티브 레코드 객체의 생명 주기에 연결되어 있어, 다양한 시점에서 해당 객체로 작업할 수 있습니다. 예를 들어 :before_save
콜백을 사용하여 객체가 저장되기 직전에 무언가를 발생시킬 수 있습니다.
연관관계 콜백은 일반 콜백과 유사하지만 컬렉션의 생명 주기 이벤트에 의해 트리거됩니다. 사용 가능한 연관관계 콜백은 다음과 같습니다:
before_add
after_add
before_remove
after_remove
연관관계 콜백은 연관관계 선언에 옵션을 추가하여 정의합니다. 예를 들어:
class Author < ApplicationRecord has_many :books, before_add: :check_credit_limit def check_credit_limit(book) # ... end end
연관관계 콜백에 대한 자세한 내용은 액티브 레코드 콜백 가이드를 참조하세요.
연관관계 확장
연관관계 프록시 객체에 자동으로 빌드된 기능으로 제한되지 않습니다. 익명 모듈을 통해 이러한 객체를 확장하여 새 파인더, 생성자 또는 기타 메서드를 추가할 수도 있습니다. 예를 들어:
class Author < ApplicationRecord has_many :books do def find_by_book_prefix(book_number) find_by(category_id: book_number[0..2]) end end end
여러 연관관계에서 공유해야 하는 확장이 있는 경우 명명된 확장 모듈을 사용할 수 있습니다. 예를 들어:
module FindRecentExtension def find_recent where("created_at > ?", 5.days.ago) end end class Author < ApplicationRecord has_many :books, -> { extending FindRecentExtension } end class Supplier < ApplicationRecord has_many :deliveries, -> { extending FindRecentExtension } end
연관관계 소유자를 사용한 연관관계 범위 지정
연관관계 소유자를 단일 인수로 범위 블록에 전달하면 연관관계 범위에 대해 더 많은 제어를 할 수 있습니다. 그러나 주의할 점은 연관관계 사전 로드가 더 이상 가능하지 않다는 것입니다.
class Supplier < ApplicationRecord has_one :account, ->(supplier) { where active: supplier.active? } end
단일 테이블 상속(STI)
때로는 다른 모델 간에 필드와 동작을 공유하고 싶을 수 있습니다.
예를 들어 Car, Motorcycle, Bicycle 모델이 있다고 가정해 보겠습니다. color
와 price
필드와 일부 메서드를 모두 공유하고 싶지만 각각의 고유한 동작과 별도의 컨트롤러도 필요합니다.
먼저 기본 Vehicle 모델을 생성해 보겠습니다:
$ bin/rails generate model vehicle type:string color:string price:decimal{10.2}
"type” 필드를 추가했다는 것을 알아챘나요? 모든 모델이 단일 데이터베이스 테이블에 저장되므로 Rails는 이 열에 저장되는 모델 이름을 저장합니다. 이 예에서는 “Car”, “Motorcycle” 또는 “Bicycle"일 수 있습니다. STI가 작동하려면 테이블에 "type” 필드가 있어야 합니다.
다음으로 Vehicle에서 상속받는 Car 모델을 생성해 보겠습니다. 이를 위해 --parent=PARENT
옵션을 사용할 수 있습니다. 이 옵션은 지정된 부모에서 상속받는 모델을 생성하고 해당 마이그레이션은 생성하지 않습니다(테이블이 이미 존재하므로).
예를 들어 Car 모델을 생성하려면:
$ bin/rails generate model car --parent=Vehicle
생성된 모델은 다음과 같습니다:
class Car < Vehicle end
이는 Vehicle에 추가된 모든 동작(연관관계, 공용 메서드 등)이 Car에서도 사용 가능함을 의미합니다.
자동차를 생성하면 vehicles
테이블에 “Car"로 type
필드가 저장됩니다:
Car.create(color: 'Red', price: 10000)
다음과 같은 SQL이 생성됩니다:
INSERT INTO "vehicles" ("type", "color", "price") VALUES ('Car', 'Red', 10000)
자동차 레코드를 쿼리하면 자동차만 검색됩니다:
Car.all
다음과 같은 쿼리가 실행됩니다:
SELECT "vehicles".* FROM "vehicles" WHERE "vehicles"."type" IN ('Car')
상속 열 재정의
(레거시 데이터베이스와 작업할 때와 같이) 상속 열의 이름을 재정의해야 하는 경우가 있습니다. 이는 inheritance_column을 사용하여 달성할 수 있습니다.
# Schema: vehicles[ id, kind, created_at, updated_at ] class Vehicle < ApplicationRecord self.inheritance_column = "kind" end class Car < Vehicle end Car.create # => #<Car kind: "Car">
상속 열 비활성화
(레거시 데이터베이스와 작업할 때와 같이) 단일 테이블 상속을 완전히 비활성화해야 하는 경우가 있습니다. 그렇지 않으면 ActiveRecord::SubclassNotFound
이 발생합니다.
inheritance_column을 nil
로 설정하여 이를 달성할 수 있습니다.
# Schema: vehicles[ id, type, created_at, updated_at ] class Vehicle < ApplicationRecord self.inheritance_column = nil end Vehicle.create!(type: "Car") # => #<Vehicle type: "Car">
위임된 유형
단일 테이블 상속(STI)
은 하위 클래스 간에 차이가 적고 모든 하위 클래스의 속성을 포함하는 단일 테이블을 만들 때 가장 잘 작동합니다.
이 접근 방식의 단점은 해당 테이블에 블로트가 발생한다는 것입니다. 다른 어떤 것에도 사용되지 않는 특정 하위 클래스의 속성까지 포함하게 됩니다.
다음 예에서는 "Entry” 클래스에서 상속받는 두 개의 Active Record 모델이 있습니다. 이 모델에는 subject
속성이 포함되어 있습니다.
# Schema: entries[ id, type, subject, created_at, updated_at] class Entry < ApplicationRecord end class Comment < Entry end class Message < Entry end
위임된 유형은 이 문제를 delegated_type
를 통해 해결합니다.
위임된 유형을 사용하려면 데이터를 특정 방식으로 모델링해야 합니다. 요구 사항은 다음과 같습니다:
- 공유 속성을 저장하는 수퍼클래스가 있습니다.
- 각 하위 클래스는 수퍼클래스에서 상속받아야 하며 추가 속성에 대한 별도의 테이블이 있어야 합니다.
이렇게 하면 모든 하위 클래스에 공통적으로 정의된 속성을 단일 테이블에 정의할 필요가 없습니다.
위의 예를 적용하려면 모델을 다시 생성해야 합니다.
먼저 수퍼클래스 Entry
모델을 생성해 보겠습니다:
$ bin/rails generate model entry entryable_type:string entryable_id:integer
그런 다음 위임을 위한 새 Message
및 Comment
모델을 생성해 보겠습니다:
$ bin/rails generate model message subject:string body:string $ bin/rails generate model comment content:string
생성기를 실행한 후에는 다음과 같은 모델이 있어야 합니다:
# Schema: entries[ id, entryable_type, entryable_id, created_at, updated_at ] class Entry < ApplicationRecord end # Schema: messages[ id, subject, body, created_at, updated_at ] class Message < ApplicationRecord end # Schema: comments[ id, content, created_at, updated_at ] class Comment < ApplicationRecord end
delegated_type
선언
먼저 수퍼클래스 Entry
에 delegated_type
을 선언합니다.
class Entry < ApplicationRecord delegated_type :entryable, types: %w[ Message Comment ], dependent: :destroy end
entryable
매개변수는 위임에 사용할 필드를 지정하며 Message
및 Comment
를 위임 클래스로 포함합니다.
Entry
클래스에는 entryable_type
및 entryable_id
필드가 있습니다. 이는 delegated_type
정의에서 entryable
이름에 _type
, _id
접미사가 추가된 필드입니다.
entryable_type
은 위임 대상의 하위 클래스 이름을 저장하고 entryable_id
는 위임 대상 하위 클래스 레코드의 ID를 저장합니다.
다음으로 as: :entryable
매개변수를 사용하여 위임된 유형을 구현하는 모듈을 정의해야 합니다.
module Entryable extend ActiveSupport::Concern included do has_one :entry, as: :entryable, touch: true end end
그런 다음 생성된 모듈을 하위 클래스에 포함시킵니다.
class Message < ApplicationRecord include Entryable end class Comment < ApplicationRecord include Entryable end
이 정의가 완료되면 Entry
위임자가 다음과 같은 메서드를 제공합니다:
메서드 | 반환 |
---|---|
Entry.entryable_types |
[“Message”, “Comment”] |
Entry#entryable_class |
Message 또는 Comment |
Entry#entryable_name |
“message” 또는 “comment” |
Entry.messages |
Entry.where(entryable_type: "Message") |
Entry#message? |
entryable_type == "Message" 일 때 true 반환 |
Entry#message |
entryable_type == "Message" 일 때 메시지 레코드 반환, 그렇지 않으면 nil |
Entry#message_id |
entryable_type == "Message" 일 때 entryable_id 반환, 그렇지 않으면 nil |
Entry.comments |
Entry.where(entryable_type: "Comment") |
Entry#comment? |
entryable_type == "Comment" 일 때 true 반환 |
Entry#comment |
entryable_type == "Comment" 일 때 댓글 레코드 반환, 그렇지 않으면 nil |
Entry#comment_id |
entryable_type == "Comment" 일 때 entryable_id 반환, 그렇지 않으면 nil |
객체 생성
새 Entry
객체를 생성할 때 entryable
하위 클래스를 동시에 지정할 수 있습니다.
Entry.create! entryable: Message.new(subject: "hello!")
추가 위임 추가
Entry
위임자를 확장하고 다형성을 사용하여 하위 클래스에서 추가 기능을 정의할 수 있습니다.
예를 들어 Entry
에서 title
메서드를 하위 클래스에 위임하려면:
class Entry < ApplicationRecord delegated_type :entryable, types: %w[ Message Comment ] delegate네, 계속해서 번역하겠습니다: :title, to: :entryable end class Message < ApplicationRecord include Entryable def title subject end end class Comment < ApplicationRecord include Entryable def title content.truncate(20) end end