rails5.1에서 N:N 관계형 모델 만들기

Posted by negabaro kim on Thursday, March 15, 2018 Tags: rails handson   4 minute read

image

하나의 상품은 복수의 카테고리에 속하고 하나의 카테고리는 복수의 상품이 존재하므로 상품과 카테고리의 관계는 N:N이라고 할 수 있습니다.

rails N:N을 구현하는 방법을 알아봅시다.

레일즈에서 N:N방식을 구현하는 방법은 ActiveRecord의has_manyhas_and_belongs_to_many를 사용하는 방법이 있습니다.

has_many와 has_and_belongs_to_many의 차이

Option name Description
has_many 중간테이블을 표현하는 클래스를 작성해야함, 확장성이 용이
has_and_belongs_to_many 중간테이블을 표현하는 클래스를 작성하지 않아도됨, 확장성이 떨어짐

has_many를 쓰든 has_and_belongs_to_many를 쓰든 레일즈에서 N:N구현을 위해서 중간테이블 작성은 필수 입니다. 둘의차이는 중간테이블을 표현하는 클래스를 만드냐 안만드냐의 차이입니다.

특별한 이유가 없는한 확장성이 용이하는 has_many를 선택하는게 좋을듯 합니다.

프로젝트 작성

has_many용 프로젝트 작성

rails new rails-relation-n-n-has_many
cd rails-relation-n-n-has_many

has_and_belongs_to_many용 프로젝트 작성

rails new rails-relation-n-n-has_and_belongs_to_many
cd rails-relation-n-n-has_and_belongs_to_many

모델&테이블 작성

Category모델과Categories테이블을 작성해줍니다.

rails g model Category name:string
rake db:migrate

Product모델과Products테이블을 작성해줍니다.

rails g model Product name:string price:integer
rake db:migrate

has_many인경우,CategoryProduct모델과 categories_products테이블을 작성해줍니다.

rails g model CategoryProduct category_id:integer product_id:integer
rake db:migrate

has_and_belongs_to_many를 사용시에는 categories_products테이블만 작성해줍니다.

rails g migration create_categories_products category_id:integer product_id:integer
rake db:migrate

모델에has_many와belongs_to을 추가

has_many의 경우

# app/models/category.rb
class Category < ActiveRecord::Base
  has_many :category_products
  has_many :products, through: :category_products

  # through 옵션에 의해 category_products경유해서 products에 접근이 가능해짐
  # 정확히는 category.products로 상품 접근이 가능해짐
end

# app/models/category_product.rb
class CategoryProduct < ActiveRecord::Base
  belongs_to :category
  belongs_to :product
end

# app/models/product.rb
class Product < ActiveRecord::Base
  has_many :category_products
  has_many :categories, through: :category_products


  # through 옵션에 의해 category_products경유해서 categories에 접근이 가능해짐
  # 정확히는 product.categories로 카테고리 접근이 가능해짐
end

has_and_belongs_to_many인 경우

# app/models/category.rb
class Category < ActiveRecord::Base
  has_and_belongs_to_many :products
end

# app/models/product.rb
class Product < ActiveRecord::Base
  has_and_belongs_to_many :categories
end

사용가능해지는 커맨드(has_many/has_and_belongs_to_many 공통)

오브젝트 작성/db저장 관련

category1 = Category.create(name: "카테고리1") # category작성/DB저장
category1.products.create(name: "상품2", price: 200) # category1에 관련된 상품 작성/DB저장
실행결과
   (0.1ms)  begin transaction
  SQL (0.6ms)  INSERT INTO "products" ("name", "price", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["name", "상품2"], ["price", 200], ["created_at", "2018-03-22 09:27:54.376770"], ["updated_at", "2018-03-22 09:27:54.376770"]]
  SQL (0.3ms)  INSERT INTO "category_products" ("category_id", "product_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["category_id", 1], ["product_id", 2], ["created_at", "2018-03-22 09:27:54.393183"], ["updated_at", "2018-03-22 09:27:54.393183"]]
   (138.1ms)  commit transaction
=> #<Product id: 2, name: "상품2", price: 200, created_at: "2018-03-22 09:27:54", updated_at: "2018-03-22 09:27:54">

릴레이션 관련

product3 = Product.create(name: "상품3", price: 1000) # product작성/DB저장
category1.products << product3 # product3을 category1에 관계설정 
실행결과
(0.3ms)  begin transaction
  SQL (1.1ms)  INSERT INTO "category_products" ("category_id", "product_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["category_id", 1], ["product_id", 3], ["created_at", "2018-03-22 09:36:57.731918"], ["updated_at", "2018-03-22 09:36:57.731918"]]
   (117.9ms)  commit transaction
  Product Load (0.4ms)  SELECT  "products".* FROM "products" INNER JOIN "category_products" ON "products"."id" = "category_products"."product_id" WHERE "category_products"."category_id" = ? LIMIT ?  [["category_id", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Product id: 2, name: "상품2", price: 200, created_at: "2018-03-22 09:27:54", updated_at: "2018-03-22 09:27:54">, #<Product id: 3, name: "상품3", price: 1000, created_at: "2018-03-22 09:32:22", updated_at: "2018-03-22 09:32:22">]>

has_many의 경우 사용가능한 커맨드

※has_and_belongs_to_many는 모델이 없으므로 사용불가

중간테이블

CategoryProduct.all # category_products의 전레코드 표시
# CategoryProduct모델이 존재함으로 인해, 중간 테이블의 validation이나 속성 추가가 가능해짐

메모

habtm

has_and_belongs_to_many는 길어서 habtm이라고 줄여서 사용함