티스토리 뷰

프로그래밍 공부/TIL : Rails Tutorial

Chapter 6 : Active Record Associations

마음 따뜻한 개발자, 나른한 하루 2020. 7. 7. 10:37
이 글은 Rails 5.0 Guide 기준으로 작성됩니다.
https://guides.rubyonrails.org/v5.0/association_basics.html

 

 

  • Active Record Associations Intro

게시글-댓글 관계에 있어 사람은 당연히 이 둘이 연관관계가 있다는 것을 '상식'으로 알고있습니다. 하지만 컴퓨터는 그런 상식 조차도 생각하지 않다 보니 따로 명시를 해둬야 할 필요가 있습니다.

 

이번 시간에는 두개 이상의 Model(테이블) 간 관계 정의법에 대해 살펴보겠습니다.

 

 

  • Why Associations?

Rails에서 연결은 두 개의 Active Record Model간의 연결입니다.

 

'그런데 왜 모델 사이에 연관이 필요할까요?'

 

이는 코드에서 작업을 더 간단하고 쉽게 할 수 있기 때문입니다. 예를 들어 author 모델과 book 모델을 포함하는 간단한 Rails 애플리케이션을 생각해보자면, 각 author는 많은 book을 가질 수 있습니다.

 

서로간의 연관이 없는 Model 정의는 아래와 같습니다 :

class Author < ApplicationRecord
end
 
class Book < ApplicationRecord
end

이제 기존 author를 위해 새 book을 추가하려고 한다면, 아래와 같이해야합니다.

@book = Book.create(published_at: Time.now, author_id: @author.id)

또는 author를 삭제하고 해당 book이 모두 삭제되게 할 수도 있습니다.

@books = Book.where(author_id: @author.id)
@books.each do |book|
  book.destroy
end
@author.destroy

Active Record Association을 사용하면 두 Model 사이에 관련이 있음을 Rails에 선언하여 다른 연계 Model간의 작업을 간소화 할 수 있습니다. 다음은 author와 book을 수정한 코드입니다.

class Author < ApplicationRecord
  has_many :books, dependent: :destroy
end
 
class Book < ApplicationRecord
  belongs_to :author
end

이제 특정 author을 위한 새 book을 만드는 작업이 더 쉬워집니다.

@book = @author.books.create(published_at: Time.now)

author와, author에 연관된 모든 book을 삭제하는 것 또한 훨씬 쉽습니다.

@author.destroy

다양한 유형의 연결에 대한 자세한 내용은 이 가이드의 다음 내용을 참조하십시오. 다음에 소개될 내용에는 Associations 작업을 위한 몇 가지 팁과 요령이 있으며 Rails의 연관 방법 및 옵션에 대한 완전한 참조가 이어집니다.

 

 

  • The Types of Associations

Rails에서는 총 6개의 연관 관계에 대한 정의를 가지고 있습니다.

belongs_to
has_one
has_many
has_many :through
has_one :through
has_and_belongs_to_many

연관(Associations)은 macro-style 호출을 사용하여 구현되므로 Model에 기능을 선언적으로 추가 할 수 있습니다. 예를 들어, 한 모델이 다른 모델에 속한다고 선언하면 Rails에게 두 Model의 instance 간에 기본 키-외래 키 정보를 유지하도록 지시하고 Model에 여러 가지 메소드를 추가할 수도 있습니다.

 

이 가이드의 나머지 부분에서는 다양한 형식의 Associations을 선언하고 사용하는 방법을 배웁니다. 그러나 먼저 각 연관 유형이 적합한 상황에 대한 빠른 소개입니다.

 

The belongs_to Association

belongs_to Association은 선언 Model의 각 Instance가 다른 Model의 한 Instance에 속하도록 다른 Model과 1:1 연결을 설정합니다. 예를 들어, Application에 author와 book이 포함되어 있고, 각 book을 정확히 한 명의 저자에게 할당 할 수있는 경우 다음과 같이 book Model을 선언합니다.

class Book < ApplicationRecord
  belongs_to :author
end

 

 참고  belong_to Association은 '단수(singular)' 표현을 사용해야합니다. book Model의 Author 연관에 대해 위의 예에서 '복수(plural)' 양식을 사용한 경우, "uninitialized constant Book::Authors" 에러를 표시합니다. 이는 Rails가 자동으로 Class 이름을 Association 이름에서 유추하기 때문입니다. Association model name을 복수형으로 잘못 할 경우, 유추 클래스 역시 (우리가 의도하지 않은) 복수화 이름을 찾아냅니다.

 

해당 마이그레이션 결과는 아래와 같습니다.

class CreateBooks < ActiveRecord::Migration[5.0]
  def change
    create_table :authors do |t|
      t.string :name
      t.timestamps
    end
 
    create_table :books do |t|
      t.belongs_to :author, index: true
      t.datetime :published_at
      t.timestamps
    end
  end
end

 

The has_one Association

has_one 연관(Association)은 다른 모델과 일대일 연결을 설정하지만 의미가 약간 다릅니다. 이 연관은 Model의 각 instance가 다른 Model의 한 instance를 포함하거나 소유 함을 나타냅니다. 예를 들어, 응용 프로그램의 각 공급 업체에 하나의 계정 만있는 경우 다음과 같이 공급 업체 Model을 선언합니다.

class Supplier < ApplicationRecord
  has_one :account
end

해당 마이그레이션 결과는 아래와 같습니다.

class CreateSuppliers < ActiveRecord::Migration[5.0]
  def change
    create_table :suppliers do |t|
      t.string :name
      t.timestamps
    end
 
    create_table :accounts do |t|
      t.belongs_to :supplier, index: true
      t.string :account_number
      t.timestamps
    end
  end
end

어떻게 쓰느냐에 따라 accounts 테이블의 supplier 열에 unique, index, foreign_key 제약 조건을 만들어야 할 수도 있습니다. 이 경우에 대한 열 정의는 아래와 같습니다.

create_table :accounts do |t|
  t.belongs_to :supplier, index: true, unique: true, foreign_key: true
  # ...
end

 

The has_many Association

has_many 연관은 다른 Model과의 1:M 연결을 나타냅니다. 종종 has_many 연관을 belongs_to 연관의 "다른 쪽"에서 찾을 수 있습니다. has_many은 Model의 각 인스턴스에 다른 Model의 인스턴스가 0개 이상 있음을 나타냅니다. 예를 들어, author와 book을 포함하는 Application에서 author Model은 아래와 같이 선언 될 수 있습니다.

class Author < ApplicationRecord
  has_many :books
end

 

 참고  다른 Model의 이름은 has_many 연관을 선언 할 때 복수형(plural)입니다.

 

해당 마이그레이션 결과는 아래와 같습니다.

class CreateAuthors < ActiveRecord::Migration[5.0]
  def change
    create_table :authors do |t|
      t.string :name
      t.timestamps
    end
 
    create_table :books do |t|
      t.belongs_to :author, index: true
      t.datetime :published_at
      t.timestamps
    end
  end
end

 

The has_many :through Association

has_many :through 연관은 종종 다른 모델과 다대 다(M:N) 연결을 설정하는 데 사용됩니다. 이 연관은 선언 Model이 세번 째 Model을 진행하여 0개 이상의 다른 모델 인스턴스와 일치 될 수 있음을 나타냅니다.

 

아래와 같이 patients가 physician를 만나기 위해 진료 예약(appointment)을 하는 예시가 있습니다. 이에 대한 association 선언은 아래와 같습니다.

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[5.0]
  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, index: true
      t.belongs_to :patient, index: true
      t.datetime :appointment_date
      t.timestamps
    end
  end
end

join Model 모음은 has_many association methods 를 통해 관리 할 수 ​​있습니다.

아래와 같이 활용해서 쓸 수 있습니다.

physician.patients = patients

 

새로 연결된 객체에 대해 새 join Model이 자동으로 생성됩니다. 이전에 존재했던 일부가 누락 된 경우 해당 join 행(row)은 자동으로 삭제됩니다.

 참고  join Model의 자동 삭제는 바로 이루어지며, destroy 콜백이 트리거되지 않습니다.

 

has_many :through 연관은 중첩된 has_many 연관을 통해 "shortcuts"을 설정하는 데에도 유용합니다. 예를 들어 document에 sections이 많고, section에 paragraphs이 많은 경우, document의 모든 paragraphs을 간단하게 수집 할 수 있습니다.

 

아래와 같은 예시로 설계를 할 수 있습니다.

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를 지정하면 이제 아래와 같이 코드 표현을 할 수 있습니다.

@document.paragraphs

 

The has_one :through Association

has_one :through 연관은 다른 Model과 1:1 연결을 설정합니다. 이 연관(Association)은 Model이 제 3자 Model을 거쳐서 다른 Model의 의 instance와 일치 될 수 있음을 나타냅니다. 예를 들어 각 supplier에 하나의 account이 있고 각 account가 하나의 account history와 연결된 경우 supllier Model은 다음과 같습니다.

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[5.0]
  def change
    create_table :suppliers do |t|
      t.string :name
      t.timestamps
    end
 
    create_table :accounts do |t|
      t.belongs_to :supplier, index: true
      t.string :account_number
      t.timestamps
    end
 
    create_table :account_histories do |t|
      t.belongs_to :account, index: true
      t.integer :credit_rating
      t.timestamps
    end
  end
end

 

The has_and_belongs_to_many Association

has_and_belongs_to_many 연관은 개입 Model 없이 다른 Model과 직접 다대 다(M:N) 연결을 작성합니다. 예를 들어 응용 프로그램에 assembly와 part가 포함되어 있고 각 assembly에 많은 부품이 있고 각 부품이 많은 assembly에 나타나는 경우 아래와 같이 모델을 선언 할 수 있습니다.

class Assembly < ApplicationRecord
  has_and_belongs_to_many :parts
end
 
class Part < ApplicationRecord
  has_and_belongs_to_many :assemblies
end

해당 마이그레이션 결과는 아래와 같습니다.

class CreateAssembliesAndParts < ActiveRecord::Migration[5.0]
  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, index: true
      t.belongs_to :part, index: true
    end
  end
end

 

Choosing Between belongs_to and has_one

두 Model 간 1:1 관계를 설정하려면 belongs_to를 하나에 추가하고, has_one을 다른 Model에 추가해야합니다.

그런데 belongs_to와 has_one에 대해선 어떻게 배치를 시켜야 할까요??

 

이 구분은 외래키가 존재하는 곳에 답이 있으며(belongs_to을 선언하는 Class의 테이블에서 진행), 데이터의 실제 의미에 대해서도 고려해야합니다.

 

has_one 관계는 무언가가 당신을 가리키고 있다고 말합니다. 예를 들어 'supplier 업체가 account을 소유 한 것' 보다 'supplier 업체를 소유하고 있다'고 말하는 것이 더 바른 표현입니다. 이에 대한 관계 표현은 아래와 같습니다.

class Supplier < ApplicationRecord
  has_one :account
end
 
class Account < ApplicationRecord
  belongs_to :supplier
end

해당 마이그레이션 결과는 아래와 같습니다.

class CreateSuppliers < ActiveRecord::Migration[5.0]
  def change
    create_table :suppliers do |t|
      t.string  :name
      t.timestamps
    end
 
    create_table :accounts do |t|
      t.integer :supplier_id
      t.string  :account_number
      t.timestamps
    end
 
    add_index :accounts, :supplier_id
  end
end

 참고  t.integer :supplier_id를 사용하면 외래키 이름이 명확하고 명시적입니다. 현재 Rails에서는 t.references :supplier를 대신 사용하여 이 구현에 대한 세부사항을 추상화 할 수 있습니다.

 

Choosing Between has_many :through and has_and_belongs_to_many

Rails는 모델 간 M:N 관계를 선언하는 두 가지 방법을 제공합니다.

일단 첫 째로 간단한 방법은 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

M:N 관계를 선언하는 두 번째 방법은 has_many :through를 사용하는 것입니다. 이 방법은 Join Model을 통해 간접적으로 연결을 만들어냅니다.

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 관계를 설정하면 됩니다. 관계 Model을 사용하여 작업을 수행 할 필요가 없는 경우 has_and_belongs_to_many 관계를 설정하는 것이 더 간단 할 수 있습니다, (허나, database에 join 테이블을 생성해야 합니다.)

 

Join model에서 유효성 검사, 콜백 또는 추가 속성이 필요한 경우 has_many :through 를 사용해야합니다.

 

Polymorphic Associations

기존의 Associations에서 더 나아간 변형은 polymorphic association 입니다. polymorphic association을 사용하면 Model이 단일 association에서 둘 이상의 다른 Model에 속할 수 있습니다. 예를 들어 employee Model 또는 product Model에 속하는 picture Model이 있을 수 있습니다.

 

이를 polymorphic 방식으로 선언하는 방법은 아래와 같습니다.

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

 

polymorphic에서 belongs_to 선언은 다른 Model이 사용할 수있는 인터페이스를 설정하는 것으로 생각할 수 있습니다. Employee Model의 instance에서 @employee.pictures 을 통해 Employee에 종속된 pictures들을 검색 할 수 있습니다.

 

마찬가지로 @product.pictures 를 통해서도 데이터를 검색할 수 있습니다.

 

Picture Model의 instance가 있는 경우 @picture.imageable을 통해 부모에게 접근할 수 있습니다. 이 작업을 수행하려면 Model에서 polymorphic 인터페이스를 선언하는 외래키 column과 type column을 모두 선언해야합니다.

class CreatePictures < ActiveRecord::Migration[5.0]
  def change
    create_table :pictures do |t|
      t.string  :name
      t.integer :imageable_id
      t.string  :imageable_type
      t.timestamps
    end
 
    add_index :pictures, [:imageable_type, :imageable_id]
  end
end

t.references 문법을 통해 위의 Migration을 아래와 같이 단순화 할 수 있습니다. :

class CreatePictures < ActiveRecord::Migration[5.0]
  def change
    create_table :pictures do |t|
      t.string :name
      t.references :imageable, polymorphic: true, index: true
      t.timestamps
    end
  end
end

Self Join

데이터 Model을 설계 할 때, 때때로 자신의 Model을 참고할 수 있습니다. 예를 들어 모든 직원을 단일 데이터베이스 Model로 저장하고, manager와 employee 간의 관계를 탐색할 수 있습니다. 이 상황은 self-joining associations을 사용하여 Modeling 할 수 있습니다.

class CreateEmployees < ActiveRecord::Migration[5.0]
  def change
    create_table :employees do |t|
      t.references :manager, index: true
      t.timestamps
    end
  end
end
class Employee < ApplicationRecord
  has_many :subordinates, class_name: "Employee",
                          foreign_key: "manager_id"
 
  belongs_to :manager, class_name: "Employee"
end

이 설정을 사용하면 @employee.subordinates@employee.manager를 검색 할 수 있습니다.

migrations/schema 에서 Model 자체를 참조하는 열을 추가합니다.

@employee = Employee.find(24)

@employee.subordinates
@employee.manager

 

 

  • Tips, Tricks, and Warnings

Rails 애플리케이션에서 Active Record Association을 효율적으로 사용하기 위해 알아야 할 몇 가지 사항이 있습니다.

 

Controlling Caching

어떤 SQL 작업에 있어 반복적으로 코드가 사용할 경우, Rails가 자체적으로 이전 작업에 대해 기억해 내는 'caching' 특징을 가지고 있습니다.

 

모든 연결 방법은 Caching을 중심으로 구축되어, 추가 작업에 사용할 수있는 최신 쿼리 결과를 유지합니다. 캐시는 여러 방법으로 공유됩니다. 예를 들면 아래와 같습니다.

author.books                 # retrieves books from the database
author.books.size            # uses the cached copy of books
author.books.empty?          # uses the cached copy of books

Application의 다른 부분에서 데이터가 변경될 수 있으므로 캐시를 다시 조회하려면 Association에서 reload를 호출하십시오.

author.books                 # retrieves books from the database

author.books.size            # uses the cached copy of books

author.books.reload.empty?   # discards the cached copy of books
                             # and goes back to the database

 

Avoiding Name Collisions

Association 이름은 아무렇게 사용해선 안됩니다. Association을 만들면 해당 이름의 method가 model에 추가되므로 ActiveRecord::Base의 instance method에 이미 사용하고 있는 method 이름을 쓰는 것은 좋지 않습니다. association method는 기존의 method를 ovveride를 하게 될겁니다. 예를 들어 attributes, connection와 같은 이름은 associations에 있어 잘못된 이름입니다.

 

Updating the Schema

Associations은 매우 유용하지만, 모든 행위를 자동으로 해결해주진 않습니다. associations와 일치하도록 database schema를 유지 관리해야합니다. 실제로 이것은 어떤 종류의 연결을 만드느냐에 따라 두 가지를 의미하는데, belong_to 연관의 경우 외래키를 작성해야하고, has_and_belongs_to_many 연관의 경우 join 테이블을 만들어야 합니다.

 

Creating Foreign Keys for belongs_to Associations

belongs_to 연관을 선언 할 때, 외래키를 만들어야합니다.

예를 들어 아래 Model과 같은 때 입니다.

class Book < ApplicationRecord
  belongs_to :author
end

이 선언은 books 테이블에서 적절한 외래키 선언으로 백업해야 합니다.

class CreateBooks < ActiveRecord::Migration[5.0]
  def change
    create_table :books do |t|
      t.datetime :published_at
      t.string   :book_number
      t.integer  :author_id
    end
 
    add_index :books, :author_id
  end
end

기본 Model을 빌드 후, 언젠가 Association을 작성하는 경우 필요한 외래키를 제공하기 위해 add_column 마이그레이션을 작성해야합니다.

 

Creating Join Tables for has_and_belongs_to_many Associations

has_and_belongs_to_many 연관을 사용하는 경우 join 테이블을 명시적으로 작성해야합니다. :join_table 옵션을 사용하여 조인 테이블의 이름을 명시적으로 지정하지 않으면 Active Record는 class 이름의 어휘집(lexical book; abc 순)을 사용하여 이름을 만듭니다. 따라서 author와 book Model 간의 조인은 "a"가 어휘 순서에서 "b"보다 높기 때문에 기본 join 테이블 이름에 "authors_books"를 부여합니다.

 

 참고  join 테이블 이름이 명명됨에 있어, Model 이름 사이의 우선 순위는 String의 <=> 연산자를 사용하여 계산됩니다. 이는 문자열의 길이가 다르고 문자열이 가장 짧은 길이와 비교할 때 동일하면 긴 문자열이 짧은 것보다 높은 어휘 우선 순위로 간주됩니다. 예를 들어, "paper_boxes"및 "papers" 테이블은 "paper_boxes"라는 이름의 길이로 인해 "papers_paper_boxes"의 조인 테이블 이름을 생성하지만 실제로는 "paper_boxes_papers"의 join 테이블 이름을 생성합니다. (밑줄 '_'이 사전 인코딩에서 일반적인 인코딩에서 's'보다 작기 때문)

 

예를 들어 아래와 같은 Association와 같이 이름이 무엇이든 적절한 마이그레이션으로 joiㅜ 테이블을 수동으로 생성해야합니다.

class Assembly < ApplicationRecord
  has_and_belongs_to_many :parts
end
 
class Part < ApplicationRecord
  has_and_belongs_to_many :assemblies
end

assemblies_parts 테이블을 작성하려면 마이그레이션으로 이를 백업해야합니다. 이 테이블은 기본키 없이(id: false) 작성됩니다.

class CreateAssembliesPartsJoinTable < ActiveRecord::Migration[5.0]
  def change
    create_table :assemblies_parts, id: false do |t|
      t.integer :assembly_id
      t.integer :part_id
    end
 
    add_index :assemblies_parts, :assembly_id
    add_index :assemblies_parts, :part_id
  end
end

create_join_table method 또한 사용할 수도 있습니다.

class CreateAssembliesPartsJoinTable < ActiveRecord::Migration[5.0]
  def change
    create_join_table :assemblies, :parts do |t|
      t.index :assembly_id
      t.index :part_id
    end
  end
end

 

Controlling Association Scope

기본적으로 Associations은 현재 Module 범위 내에서만 개체를 ​​찾습니다. 모듈 내에서 Active Record Model을 선언 할 때 중요 할 수 있습니다. 예를 들면 아래와 같습니다.

module MyApplication
  module Business
    class Supplier < ApplicationRecord
       has_one :account
    end
 
    class Account < ApplicationRecord
       belongs_to :supplier
    end
  end
end

위 Model 예제에서 supplier와 account Class가 모두 동일한 범위 내에 정의되어 있으므로 제대로 작동합니다. 그러나 supplier와 account가 서로 다른 범위로 정의되어 있으므로 아래 내용은 작동하지 않습니다.

module MyApplication
  module Business
    class Supplier < ApplicationRecord
       has_one :account
    end
  end
 
  module Billing
    class Account < ApplicationRecord
       belongs_to :supplier
    end
  end
end

만일 서로 다른 Module에 존재하는 class에 있어, 서로 연계 관계를 활용하고 싶을 경우, Namespace를 활용해내면 됩니다.

 부록  Namespace란? : 같은 공간에 있는 동일한 이름의 객체에 있어, 이름으로 인해 충돌될 것을 대비해서 영역을 분리시켜주는 역할

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

 

Bi-directional Associations

Association이 두 방향으로 작동하는 것에 있어, 두 가지 다른 Model에 대한 선언이 필요합니다.

class Author < ApplicationRecord
  has_many :books
end
 
class Book < ApplicationRecord
  belongs_to :author
end

 

기본적으로 Active Record는 이러한 연결 간의 관계(Association)에 대해 알지 못합니다. 이로 인해 객체 두개의 복사본이 동기화되지 않을 수 있습니다.

a = Author.first
b = a.books.first
a.first_name == b.author.first_name # => true
a.first_name = 'Manny'
a.first_name == b.author.first_name # => false

이는 ab.author가 데이터는 동일하나, 메모리를 공유해서 사용하지 않다보니 한 쪽의 내용 변경된다 할지라도, 다른 한 쪽에서는 수정 내용이 자동으로 반영되지 않습니다.

 

이러한 동기화 문제를 해결하기 위해 Active Record는 :inverse_of 옵션을 제공하여 아래와 같이 model 관계를 알릴 수 있습니다.

class Author < ApplicationRecord
  has_many :books, inverse_of: :author
end
 
class Book < ApplicationRecord
  belongs_to :author, inverse_of: :books
end

위와같이 inverse_of 옵션을 주면 Active Record는 author 객체 하나만 복사해내어, 이제 하나의 복사본을 기반으로 메모리를 공유를 해내어 내용 불일치를 방지하고 Application을 보다 효율적으로 만듭니다.

a = Author.first
b = a.books.first
a.first_name == b.author.first_name # => true
a.first_name = 'Manny'
a.first_name == b.author.first_name # => true

inverse_of 지원에는 몇 가지 제한 사항이 있습니다. :

  • :through associations 사용 불가
  • :polymorphic associations 사용 불가
  • :as associations 사용 불가
  • belong_to 연관의 경우 has_many inverse associations은 무시됩니다.

모든 연결은 자동으로 inverse association을 찾고, association 이름을 기반으로 :inverse_of 옵션 설정을 시도합니다. 표준 이름과의 대부분의 연결(associations)이 지원됩니다. 그러나 아래 옵션이 포함 된 associations에는 inverse가 자동으로 설정되지 않습니다. :

  • :conditions
  • :through
  • :polymorphic
  • :foreign_key

 

 

  • Detailed Association Reference

이번 섹션에서는 옵션을 추가하는 방법, Association을 선언 할 때 사용할 수있는 옵션을 포함하여 각 연결 유형에 대한 세부 정보를 알아보겠습니다.


belongs_to

belong_to은 다른 모델과의 1:1 매칭을 정의합니다. 데이터베이스 관점으로 이러한 연결은 해당 class에 외래키가 포함되어 있다고 말합니다. 다른 클래스에 외래키가 포함되어 있으면 has_one을 사용해야합니다.

 

Methods Added by belongs_to

belong_to를 선언하면 class는 연관과 관련된 다섯 가지 method를 사용할 수 있습니다.

association
association=(associate)
build_association(attributes = {})
create_association(attributes = {})
create_association!(attributes = {})
reload_association

 

이러한 모든 method에서 연관은 belongs_to의 첫 번째 인수(argument)로 전달 된 symbol로 대체됩니다.

예를 들어, 아래와 같이 선언해냅니다. :

class Book < ApplicationRecord
  belongs_to :author
end

위 Book Model의 각 instance에는 다음과 같은 method를 사용할 수 있습니다.

author
author=
build_author
create_author
create_author!
reload_author

 참고  새로운 has_one 또는 belongs_to을 초기화 할 때, has_many 또는 has_and_belongs_to_many 연관에 사용되는 association.build 메소드 대신 build_ prefix를 사용하여 association을 빌드해야합니다. 혹은 초기화 및 작성(INSERT)을 하려면 create_ prefix를 사용하세요.

 

1) association

association method는 연관된 객체가 존재할 경우, 이를 반환합니다. 연결된 객체가 없으면 nil을 반환합니다.

@author = @book.author

연관된 객체가 이 객체의 데이터베이스에서 이미 검색된 경우, cached된 버전이 리턴됩니다. 이 동작을 override하고 데이터베이스를 강제로 읽으려면 부모 개체에서 reload_association 메소드를 호출하세요.

@author = @book.reload_author

 

2) association=(associate)

association= 메소드는 연관된 객체를 이 객체에 지정합니다. 이는 연관된 객체에서 기본키를 추출하고 이 객체의 외래키를 동일한 값으로 설정하는 것을 의미합니다.

@book.author = @author

 

3) build_association(attributes = {})

build_association 메소드는 연관된 유형의 새 객체를 반환합니다. 이 객체는 전달 된 속성에서 인스턴스화 되고, 이 객체의 외래키를 통한 링크는 설정되지만 관련 객체는 아직 저장되지 않습니다.

@author = @book.build_author(author_number: 123,
                                  author_name: "John Doe")

 

4) create_association(attributes = {})

create_association method는 연관된 유형의 새 객체를 반환합니다. 이 객체는 전달된 속성에서 인스턴스화 되고, 이 객체의 외래키를 통한 링크가 설정되며 연결된 모델에 지정된 유효성 검사를 통과하면 관련 객체가 저장됩니다.

@author = @book.create_author(author_number: 123,
                                   author_name: "John Doe")

 

5) create_association!(attributes = {})

위의 create_association과 동일하지만 record가 유효하지 않으면 ActiveRecord::RecordInvalid raise 에러를 발생시킵니다.

 

Options for belongs_to

Rails는 대부분의 상황에서 잘 작동하는 기본값을 사용하지만, belongs_to 연관 참조의 동작에 대해 사용자가 정의를 하고 싶을때가 있습니다. 연관을 작성할 때 옵션 및 범위 블록을 전달하여 이러한 사용자 정의를 쉽게 수행할 수 있습니다. 예를 들어 아래 연관관계는 두 가지 옵션을 사용합니다. :

class Book < ApplicationRecord
  belongs_to :author, dependent: :destroy,
    counter_cache: true
end

belong_to은 아래 옵션을 지원합니다. : 

:autosave
:class_name
:counter_cache
:dependent
:foreign_key
:primary_key
:inverse_of
:polymorphic
:touch
:validate
:optional

 

1) :autosave

:autosave 옵션을 true로 설정하면 Rails는 조회된 모든 멤버를 저장하고, 부모 객체를 저장할 때마다 destroy 표시된 멤버를 삭제합니다.

 

2) :class_name

다른 Model의 이름을 association 이름에서 파생 할 수 없는 경우 :class_name 옵션을 사용하여 별도의 Model name을 제공 할 수 있습니다. 예를 들어, book이 author에 속하지만, author를 포함하는 Model의 실제 이름이 Patron 인 경우 다음과 같이 설정합니다.

class Book < ApplicationRecord
  belongs_to :author, class_name: "Patron"
end

 

3) :counter_cache

:counter_cache 옵션을 사용하면 속하는 객체 수를 보다 효율적으로 찾을 수 있습니다.

아래 Model 예시를 봅시다.

class Book < ApplicationRecord
  belongs_to :author
end

class Author < ApplicationRecord
  has_many :books
end

이러한 선언으로 @author.books.size 값을 요청하려면 COUNT (*) 쿼리를 수행하기 위해 데이터베이스를 호출해야 합니다. 이 호출을 피하기 위해 소속 모델에 counter cache를 추가 할 수 있습니다.

class Book < ApplicationRecord
  belongs_to :author, counter_cache: true
end

class Author < ApplicationRecord
  has_many :books
end

위와같은 선언으로 Rails는 캐시 값을 최신 상태로 유지 한 다음, size method에 대한 응답으로 해당 값을 반환합니다.

 

:counter_cache 옵션이 belongs_to 선언을 포함하는 Model에 지정되어 있지만 실제 열은 연관된 (has_many) Model에 추가되어야 합니다. 위의 경우, books_count라는 열을 Author Model에 추가해야합니다.

class Book < ApplicationRecord
  belongs_to :author, counter_cache: :count_of_books
end

class Author < ApplicationRecord
  has_many :books
end

 참고  Association의 belongs_to 측에 :counter_cache 옵션만 지정하면 됩니다.

 

Counter cache 컬럼은 attr_readonly를 통해 Model의 읽기전용 속성(read-only attributes) 목록에 추가됩니다.

 

4) :dependent

부모 데이터가 소멸 될 때 연관된 객체에 발생하는 상황을 제어합니다.

  • :destroy  하위 데이터가 삭제될 경우, 외래키로 연결된 상위객체 데이터도 함께 삭제, 하위 객체 삭제에 있어 상위 객체에 대한 콜백 검증을 함.
  • :delete  하위 데이터가 삭제될 경우, 외래키로 연결된 상위객체 데이터도 함께 삭제, 하위 객체 삭제에 있어 상위 객체에 대한 콜백 검증을 안함.

 참고  다른 Class의 has_many 연관과 연결된 belongs_to 연관에서 이 옵션을 지정해서는 안됩니다. 그렇게하면 데이터베이스에 Record가 분리 될 수 있습니다.

 

5) :foreign_key

일반적으로 Rails는 이 Model에서 외래키를 보유하는 데, 본래 외래키 이름은 외래키는 기본적으로  [singularized_table_name]_id  입니다. 그런데 :foreign_key 옵션을 사용하면 외래키 이름을 직접 설정할 수 있습니다.

class Book < ApplicationRecord
  belongs_to :author, class_name: "Patron",
                        foreign_key: "patron_id"
end

 참고  Rails는 외래키 열을 자동으로 생성하지 않습니다. 마이그레이션 파일에서 정의해야합니다.

 

6) :primary_key

일반적으로 Rails는 id 열이 테이블의 기본키를 보유하는 데 사용된다 가정합니다. :primary_key 옵션을 사용하면 다른 열을 지정할 수 있습니다. 예를 들어 guid를 기본키로 사용하는 users 테이블이 있다고 한다면, primary_key를 사용하여 아래와 같이 설정할 수 있습니다.

class User < ApplicationRecord
  self.primary_key = 'guid' # primary key is guid and not id
end
 
class Todo < ApplicationRecord
  belongs_to :user, primary_key: 'guid'
end

@user.todos.create를 실행하면 @todo 레코드는 기존의 user_id 값을 @user의 guid 값으로 갖게됩니다.

 

7) :inverse_of

:inverse_of 옵션은 Association의 inverse인 has_many 또는 has_one의 이름을 지정합니다. 하지만 :polymorphic 옵션과는 함께 사용할 수 없습니다.

class Author < ApplicationRecord
  has_many :books, inverse_of: :author
end
 
class Book < ApplicationRecord
  belongs_to :author, inverse_of: :books
end

 

8) :polymorphic

:polymorphic 옵션에 true를 전달하면 이것이 Polymorphic을 나타냅니다.

다형성 연관은 앞부분(공식 document / 과거 TIL 기록본)에 설명되어 있습니다.

 

9) :touch

:touch 옵션을 true로 설정하면 이 객체를 저장하거나 삭제할 때 마다 연결된 객체의 timestamp(updated_at 또는 updated_on)가 현재 시간으로 설정됩니다.

class Book < ApplicationRecord
  belongs_to :author, touch: true
end
 
class Author < ApplicationRecord
  has_many :books
end

이 경우 book을 저장하거나 삭제하면 관련 author의 timestamp가 업데이트됩니다. 업데이트 할 특정 timestamp 속성을 지정할 수도 있습니다.

class Book < ApplicationRecord
  belongs_to :author, touch: :books_updated_at
end

 

10) :validate

:validate 옵션을 true로 설정 시, 이 객체를 저장할 때 마다 연결된 객체의 유효성이 검사됩니다. 기본적으로는 false인데, 개체를 저장할 때 연결된 개체의 유효성은 검사되지 않습니다.

*  :validate => true  연결된 객체에 명시된 validate에 걸릴 경우, Rollback 처리

 

11) :optional

:optional 옵션을 true로 설정 시, 연결된 객체의 validate 검사를 하지 않습니다. 기본적으로 이 옵션은 false로 설정되어 있습니다.

 

Options for belongs_to

belong_to에서 사용하는 Query를 사용자가 정의하려는 경우가 있는데, 이는 범위 블록을 통해 할 수 있습니다.

예를 들면 아래와 같습니다.

class Book < ApplicationRecord
  belongs_to :author, -> { where active: true },
                        dependent: :destroy
end

scope block 내에서 표준 Query 방법을 사용할 수 있습니다.

where
includes
readonly
select

1) where

where 메소드를 사용하여 연관된 객체의 탐색에 있어 조건을 지정할 수 있습니다.

class book < ApplicationRecord
  belongs_to :author, -> { where active: true }
end

 

2) includes

include 메소드를 사용하여 이 Association을 사용할 때 eager-loaded 되어야 할 또 다른 연관 모델을 지정할 수 있습니다.

class LineItem < ApplicationRecord
  belongs_to :book
end
 
class Book < ApplicationRecord
  belongs_to :author
  has_many :line_items
end
 
class Author < ApplicationRecord
  has_many :books
end

line items(@line_item.book.author)에서 authors를 자주 조회(접근)할 경우, line items에서 book로 접근에 있어 author를 포함시켜 코드(SQL 성능)를 좀 더 효율적으로 만들 수 있습니다.

class LineItem < ApplicationRecord
  belongs_to :book, -> { includes :author }
end
 
class Book < ApplicationRecord
  belongs_to :author
  has_many :line_items
end
 
class Author < ApplicationRecord
  has_many :books
end

 참고  Book belongs_to :author가 있으면 author는 필요할 때 자동으로 eager_load 됩니다.

 

3) readonly

association을 통해 탐색 시, 연결된 개체가 readonly이 됩니다.

 

4) select

select 메소드를 사용하면 연관된 객체에 대한 데이터를 검색하는 데 사용되는 SQL SELECT 절을 (*)에서 특정 컬럼을 지목하는걸로 대체 할 수 있습니다.

기본적으로 Rails는 모든 열(*)을 검색합니다.

 참고  belong_to 연관에서 select 메소드를 사용하는 경우, 올바른 결과를 위해 :foreign_key 옵션도 설정해야합니다.

 

Do Any Associated Objects Exist?

association.nil? 을 사용하여 연결된 객체가 있는지 확인할 수 있습니다.

if @book.author.nil?
  @msg = "No author found for this book"
end

 

When are Objects Saved?

belong_to에 객체를 지정해도 객체 및 연관된 객체는 자동으로 저장되지 않습니다.


has_one Association Reference

has_one 연관은 다른 모델과 1:1 매칭을 합니다. 데이터베이스 용어로 이 연관은 다른 Class에 외래키가 포함되어 있다고 말합니다.

만일 해당 Class에 외래키가 포함되어 있으면 belongs_to를 대신 사용해야합니다.

 

Methods Added by has_one

has_one 연관을 선언하면 class는 연관과 관련된 다섯 가지 메소드를 사용할 수 있습니다. :

association
association=(associate)
build_association(attributes = {})
create_association(attributes = {})
create_association!(attributes = {})
reload_association

이러한 모든 방법에서 연관은 has_one에 대한 첫 번째 인수(argument)로 전달 된 symbol로 대체됩니다. :

class Supplier < ApplicationRecord
  has_one :account
end

Supplier 모델의 각 instance에는 다음과 같은 Method가 있습니다. :

account
account=
build_account
create_account
create_account!
reload_account

 참고  새로운 has_one 또는 belongs_to을 초기화 할 때 has_many 또는 has_and_belongs_to_many 연관에 사용되는 association.build 메소드 대신 build_ prefix를 사용하여 연관을 build해야 합니다. 혹은 초기화 및 작성(INSERT)을 하려면 create_ prefix를 사용하세요.

 

1) association

association method는 연관된 객체가 존재할 경우, 이를 반환합니다. 연결된 객체가 없으면 nil을 반환합니다.

@account = @supplier.account

연관된 객체가 객체의 데이터베이스에서 이미 검색된 경우 캐시 된 버전이 반환됩니다. 이 동작을 무시하고 데이터베이스를 강제로 읽으려면 부모 개체에서 reload_association 메소드를 사용하면 됩니다.

@account = @supplier.reload_account

 

2) association=(associate)

association= 메소드는 연관된 객체를 이 객체에 지정합니다. 이는 연관된 객체에서 기본키를 추출하고 이 객체의 외래키를 동일한 값으로 설정하는 것을 의미합니다.

@supplier.account = @account

 

3) build_association(attributes = {})

build_association 메소드는 연관된 유형의 새 객체를 반환합니다. 이 객체는 전달 된 속성에서 인스턴스화 되고, 이 객체의 외래키를 통한 링크는 설정되지만 관련 객체는 저장되지 않습니다.

@account = @supplier.build_account(terms: "Net 30")

 

4) create_association(attributes = {})

create_association method는 연관된 유형의 새 객체를 반환합니다. 이 객체는 전달된 속성에서 인스턴스화 되고, 이 객체의 외래키를 통한 링크가 설정되며 연결된 모델에 지정된 유효성 검사를 통과하면 관련 객체가 저장됩니다.

@account = @supplier.create_account(terms: "Net 30")

 

5) create_association!(attributes = {})

위의 create_association과 동일하지만 record가 유효하지 않으면 ActiveRecord::RecordInvalid raise 에러를 발생시킵니다.

 

Options for has_one

Rails는 대부분의 상황에서 잘 작동하는 기본값을 사용하지만, has_one 연관 참조의 동작에 대해 사용자가 정의를 하고 싶을때가 있습니다. 연관을 작성할 때 옵션 및 범위 블록을 전달하여 이러한 사용자 정의를 쉽게 수행할 수 있습니다. 예를 들어 아래 연관관계는 두 가지 옵션을 사용합니다. :

class Supplier < ApplicationRecord
  has_one :account, class_name: "Billing", dependent: :nullify
end

has_one 관계에서는 아래와 같은 옵션을 제공합니다.

:as
:autosave
:class_name
:dependent
:foreign_key
:primary_key
:inverse_of
:source
:source_type
:through
:validate

1) :as

:as 옵션을 설정하면 이것이 Polymorphic임을 정의합니다.

Polymorphic은 여기 문서(공식 document / 과거 TIL 기록본) 자세히 논의되었습니다.

 

2) :autosave

:autosave 옵션을 true로 설정하면 Rails는 조회된 모든 멤버를 저장하고, 부모 객체를 저장할 때마다 destroy 표시된 멤버를 삭제합니다.

 

3) :class_name

다른 Model의 이름을 연관 이름에서 파생 할 수 없는 경우 :class_name 옵션을 사용하여 별도의 Model name을 제공 할 수 있습니다. 예를 들어 supplier에 account이 있지만 account을 포함하는 Model의 실제 이름이 Billing 인 경우 다음과 같이 설정합니다.

class Supplier < ApplicationRecord
  has_one :account, class_name: "Billing"
end

 

4) :dependent

owner 객체가 삭제될 때, 연관된 객체에 발생하는 상황을 제어합니다.

  • :destroy  Associated 된 객체 데이터도 함께 삭제(destroy)
  • :delete  Associated 된 객체 데이터가 데이터베이스에서 직접 삭제(delete), Associated 된 객체에 있어 콜백 호출 안됨.
  • :nullify  자식 데이터의 외래키가 NULL로 수정(Update), 콜백이 호출 안됨.
  • :restrict_with_exception  연관데이터가 있을 경우, raise Exception error가 발생합니다.
  • :restrict_with_error  연관데이터가 있을 경우, owner에 에러가 추가됩니다.

 

NOT NULL 데이터베이스 제한 조건이있는 association에 대해 :nullify 옵션을 설정하거나 그대로 둔 것에 대해 dependent 옵션이 필요합니다. dependent을 설정하지 않으면 초기 연관된 객체의 외부키가 (기존의 DATABASE 제한조건에서 허용되지 않는) NULL 값으로 설정되므로 연관된 객체를 변경할 수 없습니다.

 

5) :foreign_key

일반적으로 Rails는 이 Model에서 외래키를 보유하는 데, 본래 외래키 이름은 외래키는 기본적으로  [singularized_table_name]_id  입니다. 그런데 :foreign_key 옵션을 사용하면 외래키 이름을 직접 설정할 수 있습니다.

class Supplier < ApplicationRecord
  has_one :account, foreign_key: "supp_id"
end

 참고  Rails는 외래키 열을 자동으로 생성하지 않습니다. 마이그레이션 파일에서 정의해야합니다.

 

6) :inverse_of

:inverse_of 옵션은 Association의 inverse인 belongs_to의 이름을 지정합니다. :through 또는 :as 옵션과 함께 작동하지 않습니다.

class Supplier < ApplicationRecord
  has_one :account, inverse_of: :supplier
end
 
class Account < ApplicationRecord
  belongs_to :supplier, inverse_of: :account
end

 

7) :primary_key

일반적으로 Rails는 id 열이 테이블의 기본키를 보유하는 데 사용된다 가정합니다. :primary_key 옵션을 사용하면 다른 열을 지정할 수 있습니다.

 

8) :source

:through 과 연관된 이름을 지정합니다.

 

이 옵션에 대한 자세한 설명은 stack overflow 글을 참고해주세요.

 부록  Understanding :source option of has_one/has_many through of Rails

 

9) :source_type

해당 옵션은 :through 옵션과 연계됨에 있어, polymorphic 관련 데이터 컬럼 중, polymorphic_type을 가리키는데 활용됩니다.

 

이 옵션에 대한 자세한 설명은 stack overflow 글을 참고해주세요.

 부록  Need help to understand :source_type option of has_one/has_many through of Rails

  ↳  이해자료  ERD 도식화

 

10) :through

Query를 수행하는 데 사용할 조인 모델을 지정합니다. :through Association에 대해선 document의 앞 부분에서 자세히 설명되어 있습니다.

 

11) :validate

:validate 옵션을 true로 설정 시, 이 객체를 저장할 때 마다 연결된 객체의 유효성이 검사됩니다. 기본적으로는 false인데, 개체를 저장할 때 연결된 개체의 유효성은 검사되지 않습니다.

*  :validate => true  연결된 객체에 명시된 validate에 걸릴 경우, Rollback 처리

 

Scopes for has_one

has_one에서 개별적으로 쿼리를 설정할 수 있습니다.

방법은 scope block을 활용하면 되는데, 예를들면 아래와 같습니다.

class Supplier < ApplicationRecord
  has_one :account, -> { where active: true }
end

scope block 내에서 표준 Query 방법을 사용할 수 있습니다.

where
includes
readonly
select

1) where

where 메소드를 사용하여 연관된 객체의 탐색에 있어 조건을 지정할 수 있습니다.

class Supplier < ApplicationRecord
  has_one :account, -> { where "confirmed = 1" }
end

 

2) includes

include 메소드를 사용하여 이 Association을 사용할 때 eager-loaded 되어야 할 또 다른 연관 모델을 지정할 수 있습니다.

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 (@supplier.account.representative)에서 직접 representative를 자주 검색하는 경우, supplier에서 account로 Association 함에 있어 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

 

3) readonly

association을 통해 탐색 시, 연결된 개체가 readonly이 됩니다.

 

4) select

select 메소드를 사용하면 연관된 객체에 대한 데이터를 검색하는 데 사용되는 SQL SELECT 절을 (*)에서 특정 컬럼을 지목하는걸로 대체 할 수 있습니다.

기본적으로 Rails는 모든 열(*)을 검색합니다.

 

Do Any Associated Objects Exist?

association.nil? 을 사용하여 연결된 객체가 있는지 확인할 수 있습니다.

if @supplier.account.nil?
  @msg = "No account found for this supplier"
end

 

When are Objects Saved?

has_one에 객체를 지정하면 해당 객체가 외래키를 업데이트하기 위해 자동으로 저장됩니다. 또한 외래키가 변경된다면 그에대한 객체들 또한 자동으로 저장됩니다.

 

유효성 검증 오류로 인해 이러한 저장 중 하나가 실패하면 지정 문이 false를 리턴하고 작업

자체가 취소됩니다.

 

부모 개체(has_one 연결을 선언한 개체)가 저장되지 않은 경우(즉, new_record?는 true를 반환), 자식 개체는 저장되지 않습니다. 부모 개체가 저장되면 자동으로 저장됩니다.

 

객체를 저장하지 않고 객체를 has_one 연결에 할당하려면 association.build 메소드를 사용하십시오.


has_many Association Reference

has_many 연관은 다른 모델과 일대다 관계를 정의합니다. 데이터베이스 관점에서 이 연관은 다른(the other) class에서는 이(this) class의 instance를 참조하는 외래키가있을 것이라고 이해합니다.

 

Methods Added by has_many

has_many 연관을 선언하면 선언 class는 Association과 관련된 16개의 메소드를 사용할 수 있습니다.

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

예를들어 위의 Model Association 정의를 토대로 Author Model의 각 instance에는 아래 Method가 존재합니다.

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

1) collection

collection 메소드는 연관된 모든 객체의 관계를 반환합니다.

연결된 객체가 없으면 빈 관계를 반환합니다.

@books = @author.books

 

2) collection<<(object, ...)

외래키를 호출 Model의 기본키로 설정하여 컬렉션에 하나 이상의 객체를 추가합니다.

@author.books << @book1

 

3) collection.delete(object, ...)

외래키를 NULL로 설정하여 collection에서 하나 이상의 객체를 제거합니다.

@author.books.delete(@book1)

 참고  Associatied된 객체가  dependent: :destroy와 연관되어 있으면 부모/자식 객체 둘 다 삭제(destroy)되고, dependent: :delete_all과 연관되어 있으면 부모객체만 삭제(delete)됩니다.

 

4) collection.destroy(object, ...)

각 객체에서 destroy 메소드를 실행하여 collection에서 하나 이상의 객체를 제거합니다.

@author.books.destroy(@book1)

 참고  개체는 :dependent 옵션을 무시하고 데이터베이스에서 제거됩니다.

 

5) collection=(objects)

추가 및 삭제되어 제공된 객체만 collection에 포함시킵니다.

 

6) collection_singular_ids

collection에 존재하는 객체의 ID를 배열 형태로 반환합니다.

@book_ids = @author.book_ids

 

7) collection_singular_ids=(ids)

추가 및 삭제하여 제공된 기본키 값으로 식별된 객체만 collection에 포함됩니다.

 

8) collection.clear

종속 옵션으로 지정된 전략에 따라 collection에서 모든 객체를 제거합니다. 옵션이 제공되지 않으면 기본 옵션(delete_all)을 따릅니다.

has_many :through 연관의 기본 옵션은 delete_all이며 has_many의 경우 외래키를 NULL로 설정하는 것입니다.

@author.books.clear

 참고  dependent: :destroy와 연결되어 있으면 객체가 삭제됩니다.

 

9) collection.empty?

collection에 연결된 개체가 없는 경우 true를 반환합니다.

<% if @author.books.empty? %>
  No Books Found
<% end %>

 

10) collection.size

collection의 객체 갯수를 반환합니다.

@book_count = @author.books.size

 

11) collection.find(...)

collection에서 객체를 찾습니다. ActiveRecord::Base.find와 동일한 구문 및 옵션을 사용합니다.

@available_books = @author.books.find(1)

 

12) collection.where(...)

탐색 조건에 따라 collection 내에서 객체를 찾지만, 객체가 느리게(lazily) 조회됩니다. 이는 객체에 접근할 때만 데이터베이스가 조회됨을 의미합니다.

@available_books = @author.books.where(available: true) # No query yet
@available_book = @available_books.first # Now the database will be queried

 

13) collection.exists?(...)

제공된 조건을 충족하는 객체가 collection에 존재하는지 확인합니다. ActiveRecord::Base.exists? 와 동일한 구문 및 옵션을 사용합니다.

 

14) collection.build(attributes = {}, ...)

연관된 단일 또는 새 객체 배열을 반환합니다. 전달된 속성에서 객체가 instance화 되고 외래키를 통한 링크가 생성되지만, 관련(Associated) 객체는 아직 저장되지 않습니다.

@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" }
])

 

15) collection.create(attributes = {})

연관된 단일 또는 새 객체 배열을 반환합니다. 전달 된 속성에서 객체가 instance화 되고 외래키를 통한 링크가 생성되며, 연결된 Model에 지정된 모든 유효성 검사를 통과하면 관련(Associated) 객체가 저장됩니다.

@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" }
])

 

16) collection.create!(attributes = {})

위의 collection.create와 동일하지만 레코드가 유효하지 않으면 ActiveRecord::RecordInvalid를 발생시킵니다. (raise Error 반환)

 

17) collection.reload

연관된 모든 객체의 관계를 반환하여 데이터베이스를 강제로 읽습니다.

연결된 객체가 없으면 빈 관계를 반환합니다.

@books = @author.books.reload

 

Options for has_many

Rails는 어떠한 상황에서든 잘 작동하게 하도록 하지만, has_many 동작에 대해 사용자가 개인적으로 정의하려는 때가 있습니다.

이러한 사용자 지정은 연결(Association)을 만들 때 옵션을 전달하여 쉽게 수행 할 수 있습니다.

 

예를 들어 아래 연결은 두 가지 옵션을 사용합니다.

class Author < ApplicationRecord
  has_many :books, dependent: :delete_all, validate: false
end

has_many 관계에서는 아래와 같은 옵션을 제공합니다.

:as
:autosave
:class_name
:counter_cache
:dependent
:foreign_key
:inverse_of
:primary_key
:source
:source_type
:through
:validate

1) :as

polymorphic 옵션을 활용 시 쓰이는 개념으로서, 자세한 내용은 이 전에 작성됐던 Document(공식 / 필자가 작성한 문서) 글을 참고해주세요.

 

2) :autosave

:autosave 옵션을 true로 설정하면 Rails는 조회된 모든 멤버를 저장하고, 부모 객체를 저장할 때마다 destroy 표시된 멤버를 삭제합니다.

 

3) :class_name

다른 Model의 이름을 association 이름에서 파생 할 수 없는 경우 :class_name 옵션을 사용하여 별도의 Model name을 제공 할 수 있습니다. 예를 들어, author은 많은 book을 가지고 있지만, book을 포함하는 Model의 실제 이름이 Transaction 인 경우 다음과 같이 설정합니다.

class Author < ApplicationRecord
  has_many :books, class_name: "Transaction"
end

 

4) :counter_cache

belong_to 연관에서 :counter_cache의 이름을 사용자 정의한 경우 해당 옵션이 필요합니다.

 부록  공식문서 / 필자가 작성한 문서

 

5) :dependent

부모 데이터가 소멸 될 때 연관된 객체에 발생하는 상황을 제어합니다.

  • :destroy  Associated 된 객체 데이터도 함께 삭제(destroy)
  • :delete_all  Associated 된 객체 데이터가 데이터베이스에서 직접 삭제(delete), Associated 된 객체에 있어 콜백 호출 안됨.
  • :nullify  자식 데이터의 외래키가 NULL로 수정(Update), 콜백 호출 안됨.
  • :restrict_with_exception  연관데이터가 있을 경우, raise Exception error가 발생합니다.
  • :restrict_with_error  연관데이터가 있을 경우, owner에 에러가 추가됩니다.

dependent 옵션에 따른 결과

6) :foreign_key

일반적으로 Rails는 이 Model에서 외래키를 보유하는 데, 본래 외래키 이름은 외래키는 기본적으로  [singularized_table_name]_id  입니다. 그런데 :foreign_key 옵션을 사용하면 외래키 이름을 직접 설정할 수 있습니다.

 참고  Rails는 외래키 열을 자동으로 생성하지 않습니다. 마이그레이션 파일에서 정의해야합니다.

 

7) :inverse_of

:inverse_of 옵션은 Association의 inverse인 has_many 또는 has_one의 이름을 지정합니다. :as 및 :through 옵션과는 함께 사용할 수 없습니다.

class Author < ApplicationRecord
  has_many :books, inverse_of: :author
end
 
class Book < ApplicationRecord
  belongs_to :author, inverse_of: :books
end

 

8) :primary_key

일반적으로 Rails는 id 열이 테이블의 기본키를 보유하는 데 사용된다 가정합니다. :primary_key 옵션을 사용하면 다른 열을 지정할 수 있습니다. 예를 들어 guid를 기본키로 사용하는 users 테이블이 있다고 한다면, primary_key를 사용하여 아래와 같이 설정할 수 있습니다.

class User < ApplicationRecord
  has_many :todos, primary_key: :guid
end

@todo = @user.todos.create를 실행하면 @todo 객체의 user_id 값 = @user 객체에서 guid 값이 됩니다.

 

9) :source

:through 과 연관된 이름을 지정합니다.

 

이 옵션에 대한 자세한 설명은 stack overflow 글을 참고해주세요.

 부록  Understanding :source option of has_one/has_many through of Rails

 

10) :source_type

해당 옵션은 :through 옵션과 연계됨에 있어, polymorphic 관련 데이터 컬럼 중, polymorphic_type을 가리키는데 활용됩니다.

 

이 옵션에 대한 자세한 설명은 stack overflow 글을 참고해주세요.

 부록  Need help to understand :source_type option of has_one/has_many through of Rails

  ↳  이해자료  ERD 도식화

 

11) :through

Query를 수행하는 데 사용할 조인 모델을 지정합니다. :through Association에 대해선 document의 앞 부분에서 자세히 설명되어 있습니다.

 

12) :validate

:validate 옵션을 true로 설정 시, 이 객체를 저장할 때 마다 연결된 객체의 유효성이 검사됩니다. 기본적으로는 false인데, 개체를 저장할 때 연결된 개체의 유효성은 검사되지 않습니다.

*  :validate => true  및 자식 Model에 명시된 validate에 걸릴 경우, Rollback 처리

 

Scopes for has_many

has_many에서 개별적으로 쿼리를 설정할 수 있습니다.

방법은 scope block을 활용하면 되는데, 예를들면 아래와 같습니다.

class Author < ApplicationRecord
  has_many :books, -> { where processed: true }
end

scope block 내에서 표준 Query 방법을 사용할 수 있습니다.

where
extending
group
includes
limit
offset
order
readonly
select
distinct

1) where

where 메소드를 사용하여 연관된 객체의 탐색에 있어 조건을 지정할 수 있습니다.

class Author < ApplicationRecord
  has_many :confirmed_books, -> { where "confirmed = 1" },
    class_name: "Book"
end

해시를 통해 조건 또한 설정할 수 있습니다.

class Author < ApplicationRecord
  has_many :confirmed_books, -> { where confirmed: true },
                              class_name: "Book"
end

hash-style의 where 옵션을 사용하는 경우, 이 Association을 통한 레코드 작성은 해시를 사용하여 자동으로 scope가 지정됩니다.

이 경우 @author.confirmed_books.create 또는 @author.confirmed_books.build를 사용 시 열의 값이 true 인 book이 생성됩니다.

 

2) extending

extending 메소드는 association proxy를 확장하도록 명명 된 Module을 지정합니다. Association extensions에 대해서는 다음 Rails Document 에 자세히 설명합니다.

 

3) group

SQL에서 나온 결과에 대해 attribute name 이름을 그룹화하는 기능입니다.

SQL의 GROUP BY절과 동일한 역할입니다.

class Author < ApplicationRecord
  has_many :line_items, -> { group 'books.id' },
                        through: :books
end

 

4) includes

include 메소드를 사용하여 이 Association을 사용할 때 eager-loaded 되어야 할 또 다른 연관 모델을 지정할 수 있습니다.

class Author < ApplicationRecord
  has_many :books
end
 
class Book < ApplicationRecord
  belongs_to :author
  has_many :line_items
end
 
class LineItem < ApplicationRecord
  belongs_to :book
end

authors(@author.books.line_items)에서 authors를 자주 조회(접근)할 경우, author에서 book에 이르기까지 association에 line item을 포함시켜 코드의 효율성을 높일 수 있습니다.

class Author < ApplicationRecord
  has_many :books, -> { includes :line_items }
end
 
class Book < ApplicationRecord
  belongs_to :author
  has_many :line_items
end
 
class LineItem < ApplicationRecord
  belongs_to :book
end

 

5) limit

association을 통해 탐색 후 반환될 개체 수를 제한할 수 있습니다.

class Author < ApplicationRecord
  has_many :recent_books,
    -> { order('published_at desc').limit(100) },
    class_name: "Book",
end

 

6) offsert

association을 통해 객체를 탐색함에 있어, 시작 offset을 지정할 수 있습니다.

예를 들어, 아래 코드는 처음 11개의 레코드를 건너 뜁니다.

-> {offset (11)}

 

7) order

association을 통해 탐색 시, 객체가 보여지는 순서를 정합니다. (SQL ORDER BY 절에서 사용되는 구문과 동일)

class Author < ApplicationRecord
  has_many :books, -> { order "date_confirmed DESC" }
end

 

8) readonly

association을 통해 탐색 시, 연결된 개체가 readonly이 됩니다.

 

9) select

select 메소드를 사용하면 연관된 객체에 대한 데이터를 검색하는 데 사용되는 SQL SELECT 절을 (*)에서 특정 컬럼을 지목하는걸로 대체 할 수 있습니다.

기본적으로 Rails는 모든 열(*)을 검색합니다.

 

10) distinct

distinct method를 사용하여 중복된 collection을 제거합니다. :through 옵션과 함께 사용할 수도 있습니다..

class Person < ApplicationRecord
  has_many :readings
  has_many :articles, through: :readings
end
 
person = Person.create(name: 'John')
article   = Article.create(name: 'a1')
person.articles << article
person.articles << article
person.articles.inspect # => [#<Article id: 5, name: "a1">, #<Article id: 5, name: "a1">]
Reading.all.inspect  # => [#<Reading id: 12, person_id: 5, article_id: 5>, #<Reading id: 13, person_id: 5, article_id: 5>]

위 예제에서 redings와 person Model이 있습니다. 위 데이터들은 동일한 article를 가리키더라도 article은 두 가지를 모두 가져옵니다.

여기서 distinct를 설정해 보면 아래와 같습니다 :

class Person
  has_many :readings
  has_many :articles, -> { distinct }, through: :readings
end
 
person = Person.create(name: 'Honda')
article   = Article.create(name: 'a1')
person.articles << article
person.articles << article
person.articles.inspect # => [#<Article id: 7, name: "a1">]
Reading.all.inspect  # => [#<Reading id: 16, person_id: 7, article_id: 7>, #<Reading id: 17, person_id: 7, article_id: 7>]

코드를 보완한 후, 디버깅을 하면 여전히 두 개의 데이터 값이 있다곤 하나, collection에는 중복이 제거된 채로 데이터가 보여집니다. 이는 person.articles에는 하나의 article만 표시됩니다.

 

Association를 포함한 모든 데이터에 중복이 없는지 확인하려면 테이블에 unique index(고유 색인)을 추가해야합니다. 이를테면 readings라는 이름의 테이블이 있고 article를 한 사람에게 한 번만 추가 할 수있게 하려면 아래와 같이 Migration을 작성하면 됩니다. :

add_index :readings, [:person_id, :article_id], unique: true

unique index에 있어 article를 person에 동일한 데이터를 두 번 추가하려고 할 시, ActiveRecord :: RecordNotUnique 오류가 발생합니다.

person = Person.create(name: 'Honda')
article = Article.create(name: 'a1')
person.articles << article
person.articles << article # => ActiveRecord::RecordNotUnique

include? 메소드를 통해 강제적으로 association 내에서 사용하지 마세요. 이는 include? 와 같은 메소드를 사용하여 uniqueness(중복 검증) 과정 중 race condition이 발생되기 때문입니다. 예를 들어, 위의 article 예제를 사용하면 여러 person이 동시에 접근을 시도 할 수 있기 때문에 이상한 결과가 나올 수 있습니다.

person.articles << article unless person.articles.include?(article)

 

When are Objects Saved?

has_many에 객체를 지정하면 해당 객체가 외래키를 업데이트하기 위해 자동으로 저장됩니다. 또한 외래키가 변경된다면 그에대한 객체들 또한 자동으로 저장됩니다.

 

유효성 검증 오류로 인해 이러한 저장 중 하나가 실패하면 지정 문이 false를 리턴하고 작업 자체가 취소됩니다.

 

부모 개체(has_many연결을 선언한 개체)가 저장되지 않은 경우(즉, new_record?는 true를 반환), 자식 개체는 저장되지 않습니다. 부모 개체가 저장되면 자동으로 저장됩니다.

 

객체를 저장하지 않고 객체를 has_many연결에 할당하려면 collection.build 메소드를 사용하십시오.


has_and_belongs_to_many Association Reference

has_and_belongs_to_many은 다른 모델과 M:N 관계를 작성합니다. 데이터베이스 관점으로는 각 Class를 참조하는 외래키를 포함하는 중간에 있는 Join 테이블을 통해 양쪽 Class를 연결합니다.

 

Methods Added by has_and_belongs_to_many

has_and_belongs_to_many을 선언 시, Class는 Association과 관련된 16개의 메소드를 사용할 수 있습니다.

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

모든 Method에서 collection은 has_and_belongs_to_many에 대한 첫 번째 인수(argument)로 전달 된 symbol로 대체되고, collection_singular는 해당 symbol의 단일화 된 버전(singularized version)으로 대체됩니다.

 

예를 들어, 아래와 같은 코드가 있다고 칩시다. :

class Part < ApplicationRecord
  has_and_belongs_to_many :assemblies
end

Part Model의 각 instance에는 아래와 같은 method를 사용할 수 있습니다.

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

1) Additional Column Methods

has_and_belongs_to_many 연관에 대한 join 테이블에 두 개의 외래키 외 추가 컬럼이있는 경우, 해당 컬럼은 해당 association을 통해 검색된 데이터의 attribute으로 추가됩니다. Rails가 해당 attribute에 대한 변경 사항을 저장할 수 없는데, 이는 데이터는 항상 읽기전용(read-only) 입니다.

 참고  has_and_belongs_to_many Association에서 Join 테이블에 추가 attribute을 사용하는 것은 더 이상 지원되지 않습니다. 이와같이 복잡한 동작이 필요한 경우 has_and_belongs_to_many 대신 has_many :through을 사용해야합니다.

 

2) collection

collection 메소드는 연관된 모든 객체의 관계를 반환합니다.

연결된 객체가 없으면 빈 관계를 반환합니다.

@assemblies = @part.assemblies

 

3) collection<<(object, ...)

join 테이블에 record를 작성하여 collection에 하나 이상의 객체를 추가합니다.

@part.assemblies << @assembly1

 참고  해당 method는 concat 혹은 push로 alias 처리되었습니다.

 

4) collection.delete(object, ...)

join 테이블에서 record를 삭제하여 collection에서 하나 이상의 객체를 제거합니다.

This does not destroy the objects.

@part.assemblies.delete(@assembly1)

 참고  콜백을 호출하지 않습니다.

 

5) collection.destroy(object, ...)

콜백 실행을 포함하여 join 테이블의 각 record에서 destroy를 실행하여 collection에서 하나 이상의 객체를 제거합니다.

This does not destroy the objects.

@part.assemblies.destroy(@assembly1)

 

6) collection=(objects)

추가 및 삭제되어 제공된 객체만 collection에 포함시킵니다.

 

7) collection_singular_ids

collection에 존재하는 객체의 ID를 배열 형태로 반환합니다.

@assembly_ids = @part.assembly_ids

 

8) collection_singular_ids=(ids)

추가 및 삭제하여 제공된 기본키 값으로 식별된 객체만 collection에 포함됩니다.

 

9) collection.clear

join 테이블에서 행(row)을 삭제하여 collection에서 모든 객체를 제거합니다. 연관된 객체에는 영향을 끼치지 않습니다.

 

10) collection.empty?

collection에 연결된 개체가 없는 경우 true를 반환합니다.

<% if @part.assemblies.empty? %>
  This part is not used in any assemblies
<% end %>

 

11) collection.size

collection의 객체 갯수를 반환합니다.

@assembly_count = @part.assemblies.size

 

12) collection.find(...)

collection에서 객체를 찾습니다. ActiveRecord::Base.find와 동일한 구문 및 옵션을 사용합니다.

또한 개체가 collection에 있어야한다는 조건을 추가합니다.

@assembly = @part.assemblies.find(1)

 

13) collection.where(...)

탐색 조건에 따라 collection 내에서 객체를 찾지만, 객체가 느리게(lazily) 조회됩니다. 이는 객체에 접근할 때만 데이터베이스가 조회됨을 의미합니다. 또한 개체가 collection에 있어야 한다는 조건을이 추가됩니다.

@new_assemblies = @part.assemblies.where("created_at > ?", 2.days.ago)

 

14) collection.exists?(...)

제공된 조건을 충족하는 객체가 collection에 존재하는지 확인합니다. ActiveRecord::Base.exists? 와 동일한 구문 및 옵션을 사용합니다.

 

15) collection.build(attributes = {})

연관된 새 객체를 반환합니다. 전달된 속성에서 객체가 instance화 되고 join 테이블을 통한 링크가 생성되지만, 관련(Associated) 객체는 아직 저장되지 않습니다.

@assembly = @part.assemblies.build({assembly_name: "Transmission housing"})

 

16) collection.create(attributes = {})

연관된 새 객체를 반환합니다. 전달 된 속성에서 객체가 instance화 되고 외래키를 통한 링크가 생성되며, 연결된 Model에 지정된 모든 유효성 검사를 통과하면 관련(Associated) 객체가 저장됩니다.

@assembly = @part.assemblies.create({assembly_name: "Transmission housing"})

 

17) collection.create!(attributes = {})

위의 collection.create와 동일하지만 레코드가 유효하지 않으면 ActiveRecord::RecordInvalid를 발생시킵니다. (raise Error 반환)

 

18) collection.reload

연관된 모든 객체의 관계를 반환하여 데이터베이스를 강제로 읽습니다.

연결된 객체가 없으면 빈 관계를 반환합니다.

@assemblies = @part.assemblies.reload

 

Options for 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
:validate

1) :association_foreign_key, :foreign_key

일반적으로 Rails는 다른 Model을 가리키는 외래키를 보유하는 데 사용되는 join 테이블의 column이  _id  글자가 추가된 Model의 이름이라고 가정합니다.

:association_foreign_key 옵션을 사용하면 외래 키의 이름을 직접 설정할 수 있습니다.

 참고  :foreign_key 및 :association_foreign_key 옵션은 다대다 self join을 설정할 때 효율적입니다. 예를 들면 아래와 같습니다.

class User < ApplicationRecord
  has_and_belongs_to_many :friends,
      class_name: "User",
      foreign_key: "this_user_id",
      association_foreign_key: "other_user_id"
end

 

2) :autosave

:autosave 옵션을 true로 설정하면 Rails는 조회된 모든 멤버를 저장하고, 부모 객체를 저장할 때마다 destroy 표시된 멤버를 삭제합니다.

 

3) :class_name

다른 Model의 이름을 association 이름에서 파생 할 수 없는 경우 :class_name 옵션을 사용하여 별도의 Model name을 제공 할 수 있습니다. 예를 들어, part에 많은 assembly가 있지만, assembly를 포함하는 Model의 실제 이름이 Gadget 인 경우 아래와 같이 설정합니다.

class Parts < ApplicationRecord
  has_and_belongs_to_many :assemblies, class_name: "Gadget"
end

 

4) :foreign_key

일반적으로 Rails는 이 Model에서 외래키를 보유하는 데, 본래 외래키 이름은 외래키는 기본적으로  [singularized_table_name]_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

 

5) :join_table

어휘 순서를 기반으로 하는 join 테이블의 default name이 원하는 이름이 아닌 경우, :join_table 옵션을 사용하여 이름을 재정의 할 수 있습니다.

 

6) :validate

:validate 옵션을 true로 설정 시, 이 객체를 저장할 때 마다 연결된 객체의 유효성이 검사됩니다. 기본적으로는 false인데, 개체를 저장할 때 연결된 개체의 유효성은 검사되지 않습니다.

*  :validate => true  연결된 객체에 명시된 validate에 걸릴 경우, Rollback 처리

 

Scopes for has_and_belongs_to_many

has_and_belongs_to_many에서 개별적으로 쿼리를 설정할 수 있습니다.

방법은 scope block을 활용하면 되는데, 예를들면 아래와 같습니다.

class Parts < ApplicationRecord
  has_and_belongs_to_many :assemblies, -> { where active: true }
end

scope block 내에서 표준 Query 방법을 사용할 수 있습니다.

where
extending
group
includes
limit
offset
order
readonly
select
distinct

1) where

where 메소드를 사용하여 연관된 객체의 탐색에 있어 조건을 지정할 수 있습니다.

class Parts < ApplicationRecord
  has_and_belongs_to_many :assemblies,
    -> { where "factory = 'Seattle'" }
end

해시를 통해 조건 또한 설정할 수 있습니다.

class Parts < ApplicationRecord
  has_and_belongs_to_many :assemblies,
    -> { where factory: 'Seattle' }
end

 

hash-style의 where 옵션을 사용하는 경우, 이 Association을 통한 레코드 작성은 해시를 사용하여 자동으로 scope가 지정됩니다.

이 경우 @parts.assemblies.create 또는 @parts.assemblies.build를 사용 시 factory 모델에서 column의 값이 "Seattle" 인 orders이 생성됩니다.

 

2) extending

extending 메소드는 association proxy를 확장하도록 명명 된 Module을 지정합니다. Association extensions에 대해서는 다음 Rails Document 에 자세히 설명합니다.

 

3) group

SQL에서 나온 결과에 대해 attribute name 이름을 그룹화하는 기능입니다.

SQL의 GROUP BY절과 동일한 역할입니다.

class Parts < ApplicationRecord
  has_and_belongs_to_many :assemblies, -> { group "factory" }
end

 

4) includes

include 메소드를 사용하여 이 Association을 사용할 때 eager-loaded 되어야 할 또 다른 연관 모델을 지정할 수 있습니다.

 

5) limit

association을 통해 탐색 후 반환될 개체 수를 제한할 수 있습니다.

class Parts < ApplicationRecord
  has_and_belongs_to_many :assemblies,
    -> { order("created_at DESC").limit(50) }
end

 

6) offset

association을 통해 객체를 탐색함에 있어, 시작 offset을 지정할 수 있습니다.

예를 들어, 아래 코드는 처음 11개의 레코드를 건너 뜁니다.

class Author < ApplicationRecord
  has_many :recent_books,
    -> {offset (11)}
end

 

7) order

association을 통해 탐색 시, 객체가 보여지는 순서를 정합니다. (SQL ORDER BY 절에서 사용되는 구문과 동일)

class Parts < ApplicationRecord
  has_and_belongs_to_many :assemblies,
    -> { order "assembly_name ASC" }
end

 

8) readonly

association을 통해 탐색 시, 연결된 개체가 readonly이 됩니다.

 

9) select

select 메소드를 사용하면 연관된 객체에 대한 데이터를 검색하는 데 사용되는 SQL SELECT 절을 (*)에서 특정 컬럼을 지목하는걸로 대체 할 수 있습니다.

기본적으로 Rails는 모든 열(*)을 검색합니다.

 

10) distinct

collection이 가진 중복 데이터를 제거 후 결과가 보여집니다.

 

When are Objects Saved?

has_and_belongs_to_many에 객체를 지정하면 해당 객체가 join 테이블을 Update 하기 위해 자동으로 저장됩니다. 하나의 statement에 여러 객체를 지정하면 모두 저장됩니다.

 

유효성 검증 오류로 인해 이러한 저장 중 하나가 실패하면 지정 문이 false를 리턴하고 작업 자체가 취소됩니다.

 

부모 객체(has_and_belongs_to_many 연관이 정의된 객체)가 저장되지 않은 경우(즉, new_record?는 true를 반환), 자식 개체는 저장되지 않습니다. 부모 개체가 저장되면 자동으로 저장됩니다.

 

객체를 저장하지 않고 객체를 has_and_belongs_to_many 연결에 할당하려면 collection.build 메소드를 사용하십시오.

 

Association Extensions : block + Method

일반 콜백은 Active Record 객체의 life cycle에 연결되므로 다양한 시점에서 해당 객체로 작업 할 수 있습니다. 예를 들어, :before_save 콜백을 사용하면 객체가 저장되기 직전에 특정 이벤트가 발생되게 할 수 있습니다.

 

Association callbacks are similar to normal callbacks, but they are triggered by events in the life cycle of a collection. There are four available association callbacks:

 

Association 콜백은 일반 콜백과 비슷하지만 collection life cycle의 이벤트에 의해 trigger됩니다.

사용 가능한 Association 콜백은 4개입니다.

before_add
after_add
before_remove
after_remove

Association 선언에 옵션을 추가하여 Association callback을 정의합니다.

예를 들면 아래와 같습니다.

class Author < ApplicationRecord
  has_many :books, before_add: :check_credit_limit
 
  def check_credit_limit(book)
    ...
  end
end

Rails는 콜백에 추가 또는 제거되는 객체를 전달합니다.

콜백을 배열 형태로 여러개 전달하여 단일 이벤트에 콜백을 쌓을 수 있습니다.

class Author < ApplicationRecord
  has_many :books,
    before_add: [:check_credit_limit, :calculate_shipping_charges]
 
  def check_credit_limit(book)
    ...
  end
 
  def calculate_shipping_charges(book)
    ...
  end
end

before_add, before_remove 콜백에서 예외가 발생하면 객체가 collection에 추가되지 않습니다. 

 

Association Extensions : Module

Rails가 자동으로 association proxy 객체에 구축하는 기능에만 국한되지는 않습니다. 익명 모듈(anonymous module)을 통해 객체를 extension하여 새로운 finder, creators 또는 기타 methd를 추가 할 수도 있습니다.

 

예를 들면 아래와 같습니다.

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

 

여러 연관(associations)에서 공유해야 하는 extension이 있으면 명명된 extension module을 사용할 수 있습니다.

예를 들면 아래와 같습니다.

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

Extension은 proxy_association 접근자(accessor)의 세 가지 속성을 사용하여 association proxy의 내부를 참조 할 수 있습니다.

  • proxy_association.owner : 연관(association)이 속하는 객체
  • proxy_association.reflection : 연관을 설명하는 reflection object(반영 객체)
  • proxy_association.target : belong_to 또는 has_one에 대한 연관된(associated) ​​오브젝트 또는 has_many 또는 has_and_belongs_to_many에 대한 연관된(associated) ​​오브젝트의 collection

 부록  Module 파일은 어디에 저장시키는게 좋을까?

 

 

  • Single Table Inheritance

Sometimes, you may want to share fields and behavior between different models. Let's say we have Car, Motorcycle and Bicycle models. We will want to share the color and price fields and some methods for all of them, but having some specific behavior for each, and separated controllers too.

 

때로는 다른 Model간에 필드(fields)와 동작(behavior)을 공유하려 할 수 있습니다. Car, Motorcycle, Bicycle 모델이 있다고 가정 해 봅시다. color 및 price field와 모든 method에 대한 몇 가지 method를 공유하고 각각에 대해 특정 동작을 수행하고 Controller를 분리하려고합니다.

 

Rails는 이를 아주 쉽게 생성합니다. 먼저 기본 Vehicle 모델을 생성 해 봅시다 :

rails generate model vehicle type:string color:string price:decimal{10.2}

위의 Generate 코드를 보면 "type" 필드가 보일겁니다. 모든 모델은 단일 데이터베이스 테이블에 저장되므로 Rails는 cokumn에 저장중인 모델의 이름을 저장합니다. 이 예시상에선 "Car", "Motorcycle", "Bicycle"이 될 수 있습니다. STI(Single Table Inheritance)는 테이블에 "type"필드가 없으면 작동하지 않습니다.

 

그 후, Vehicle에서 상속 된 세 가지 모델을 생성합니다. 이를 위해 --parent = PARENT 옵션을 사용할 수 있습니다. 이 옵션은 지정된 상위 객체에서 상속되며 테이블이 이미 존재하므로 동등한 마이그레이션 없이 모델을 바로 생성합니다.

 

vehicle의 하위 객체가 될 car 객체를 생성한다면 아래와 같습니다. :

rails g model car --parent=Vehicle

 

위의 과정을 끝나고, 각 Model 파일들을 살펴보면 아래와 같이, Car은 Vehicle을 상속하고 있는것을 볼 수 있습니다.

## app/models/car.rb

class Car < Vehicle
end
## app/models/vehicle.rb

class Vehicle < ApplicationRecord
end

Vehicle에 추가 된 모든 동작은 Association, 공용 method 등으로 Car에서도 사용할 수 있습니다.

Car를 만들면 type field에 "Car"인 자동차 테이블에 저장됩니다.

Car.create(color: 'Red', price: 10000)

아래 Car record에 대한 Query 조회는 Vehicle 종류가 'car'인 것에 대해서만 검색이 진행됩니다.

Car.all

#=> SELECT "vehicles".* FROM "vehicles" WHERE "vehicles"."type" IN ('Car')

 

 

  • 자료 참고

1. belongs_to 문법은 references로 alias 되는 표현법 입니다.

2. Rails has_and_belongs_to_many conveniences

3. HABTM to has_many through

4. Namespace란?

5. Autosave 옵션 개념

 

 

TAG
댓글
댓글쓰기 폼
공지사항
Total
37,630
Today
22
Yesterday
231
링크
«   2020/08   »
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          
글 보관함