Hanami for Rails 개발자를 위한 가이드: 연관 관계(Associations) 설정하기

Hanami for Rails Developers: Part 4: Associations

작성자
발행일
2025년 10월 13일

핵심 요약

  • 1 Hanami 애플리케이션에서 리뷰(reviews) 테이블을 생성하고, 책(books)과의 `has_many` 및 `belongs_to` 연관 관계를 설정하는 방법을 설명합니다.
  • 2 N+1 쿼리 문제를 방지하기 위해 Hanami의 `combine` 메서드를 사용하여 책과 리뷰 데이터를 함께 로드하고, 이를 Repository를 통해 애플리케이션에 노출하는 과정을 다룹니다.
  • 3 복잡한 데이터 조회(예: 인기 있는 책, 평점 높은 책)를 위해 관계(relation)에 사용자 정의 메서드를 추가하고, `Sequel`의 Ruby 구문을 활용하여 SQL 쿼리를 구성하는 방법을 제시합니다.

도입

본 가이드는 Hanami 애플리케이션에서 `books` 테이블에 대한 레코드 표시, 생성 및 편집 기능을 구축하는 이전 단계를 기반으로, `books`와 `reviews` 간의 연관 관계를 설정하는 방법에 초점을 맞춥니다. 새로운 `reviews` 테이블을 생성하고, 이 리뷰들을 `books.show` 페이지에 효과적으로 표시하며, 복잡한 데이터 조회 요구사항을 Hanami의 관계(relations) 및 저장소(repositories) 패턴을 통해 어떻게 구현하는지 상세히 다룰 예정입니다. 특히 Rails 개발자에게 익숙한 연관 관계 개념이 Hanami에서 어떻게 다르게 구현되는지 이해를 돕고자 합니다.

리뷰 테이블 및 관계 생성

먼저 reviews 테이블을 생성하기 위해 hanami g migration create_reviews 명령어를 사용합니다. 생성된 마이그레이션 파일에 book_id 외래 키, content, rating, created_at 컬럼을 정의합니다. 이후 hanami db migrate로 마이그레이션을 실행합니다.

다음으로 hanami g relation reviews 명령어를 통해 Reviews 관계 클래스를 생성합니다. Hanami 콘솔에서 app["relations.reviews"]를 통해 관계를 로드하고, insertwhere 메서드를 사용하여 리뷰를 생성하고 조회하는 기본 작업을 수행할 수 있습니다.

Hanami의 연관 관계와 N+1 쿼리 방지

Rails와 달리 Hanami에서는 book.reviews와 같은 직접적인 연관 관계 메서드를 객체에 정의하지 않습니다. 이는 N+1 쿼리 문제를 근본적으로 방지하기 위한 설계 철학입니다. Hanami에서는 필요한 모든 데이터를 사전에 로드하여 쿼리 발생 시점을 명확히 합니다.

연관 관계 정의 및 데이터 로딩

app/relations/books.rb 파일의 schema 블록 내에 associations를 정의하여 has_many :reviews 연관 관계를 설정합니다. 마찬가지로 app/relations/reviews.rb에는 belongs_to :book을 정의합니다.

콘솔에서 books.by_pk(1).combine(:reviews).first와 같이 combine 메서드를 사용하여 단일 쿼리로 책과 해당 리뷰 데이터를 함께 로드할 수 있습니다. 이 방식은 책 쿼리 한 번, 리뷰 쿼리 한 번으로 데이터를 가져와 N+1 쿼리를 회피합니다.

Repository를 통한 연관 관계 노출

BookRepo 클래스(app/repos/book_repo.rb)에 find_with_reviews(id) 메서드를 정의하여 books.by_pk(id).combine(:reviews).one! 호출을 캡슐화합니다. 이를 통해 뷰(app/views/books/show.rb)에서 book_repo.find_with_reviews(id)를 사용하여 책과 리뷰 데이터를 쉽게 가져올 수 있으며, 템플릿(app/templates/books/show.html.erb)에서 reviews.each를 통해 반복하여 표시할 수 있습니다.

복잡한 쿼리 구현

“리뷰가 많은 책(>= 10개)”, “평균 평점이 3점 이상인 책”과 같은 복잡한 쿼리 요구사항을 처리하기 위해 app/relations/books.rb에 사용자 정의 메서드를 추가합니다.

  • 인기 있는 책 (popular): join(:reviews).group(:id).having { count(reviews[:id]) >= 10 }

  • 좋아요 받은 책 (liked): join(:reviews).group(:id).having { avg(reviews.rating) >= 3 }

  • 싫어요 받은 책 (disliked): join(:reviews).group(:id).having { avg(reviews[:rating]) <= 2 }

Sequel gem이 제공하는 Ruby 구문을 사용하여 SQL의 HAVING 절을 깔끔하게 작성할 수 있습니다. 여러 메서드를 체인으로 연결할 때 발생하는 중복 JOIN 문제를 해결하기 위해 with_reviews와 같은 공통 JOIN 메서드를 먼저 호출하도록 패턴을 개선합니다.

최종적으로 이러한 관계 메서드들을 BookRepopopular, popular_and_liked 등으로 캡슐화하여 애플리케이션에 더 깔끔한 인터페이스를 제공합니다. 관계는 복잡한 SQL 로직을 처리하고, 저장소는 이 관계의 메서드를 활용하여 애플리케이션에 명확한 파사드(facade)를 제공하는 역할 분담이 이루어집니다.

결론

이 가이드를 통해 Hanami에서 `reviews` 테이블을 생성하고, `books`와의 연관 관계를 설정하는 방법을 심층적으로 살펴보았습니다. 특히 Hanami가 N+1 쿼리 문제를 설계 단계에서부터 방지하는 접근 방식과 `combine` 메서드를 통한 효율적인 데이터 로딩을 이해하는 것이 중요합니다. 또한, `Sequel`의 강력한 Ruby 구문을 활용하여 복잡한 데이터 조회 로직을 관계(relations)에 구현하고, 이를 저장소(repositories)를 통해 애플리케이션에 깔끔하게 노출하는 패턴을 확립할 수 있었습니다. 이러한 관계와 저장소의 명확한 역할 분담은 Hanami 애플리케이션의 유지보수성과 확장성을 크게 향상시킵니다.

댓글 0

댓글 작성

0/1000
정중하고 건설적인 댓글을 작성해 주세요.

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!