Rails 7의 출시가 눈앞으로 다가왔습니다. 확정된 릴리스 날짜는 아직 발표되지 않았지만, 크리스마스 전에 공개될 것으로 예상되므로 그리 멀지 않았습니다. 이 글을 작성하는 시점의 최신 버전은 첫 번째 릴리스 후보(release candidate)인 7.0.0.rc1입니다. Basecamp, HEY, GitHub, Shopify 등이 이미 프로덕션 환경에서 Rails 7 알파 버전을 운영하고 있으므로, 릴리스 후보 단계에서도 상당히 안정적일 것으로 기대할 수 있습니다.
이 글에서는 Rails 7이 가져올 주요 새 기능과 변경 사항을 하나씩 살펴보겠습니다.
Node.js와 Webpack이 더 이상 필요 없습니다
놀라시겠지만 사실입니다! Rails 7에서는 더 이상 NodeJS나 Webpack 없이 JavaScript를 사용할 수 있으며, 여전히 npm 패키지도 자유롭게 활용할 수 있습니다.
기존에는 Babel로 ES6를 트랜스파일링하고 Webpack으로 번들링하려면 상당한 설정 작업이 필요했습니다. Webpacker 젬이 이를 꽤 잘 지원했지만, 많은 부담을 안겨주었고 구조를 이해하거나 수정하기 어려웠으며, 특히 업그레이드 호환성을 유지하는 것이 쉽지 않았습니다.
이제 rails new로 생성하는 새 애플리케이션의 기본값은 importmaps-rails 젬을 통한 import map입니다. package.json을 작성하고 npm이나 yarn으로 의존성을 설치하는 대신, ./bin/importmap CLI로 의존성을 고정(pin), 해제(unpin), 갱신(update)합니다.
예를 들어 date-fns를 설치하려면 다음과 같이 입력합니다:
$ ./bin/importmap pin date-fns그러면 config/importmap.rb에 다음과 같은 줄이 추가됩니다:
pin "date-fns", to: "https://ga.jspm.io/npm:date-fns@2.27.0/esm/index.js"JavaScript 코드에서는 기존과 동일하게 사용하면 됩니다:
import { formatDistance, subDays } from "date-fns";
formatDistance(subDays(new Date(), 3), new Date(), { addSuffix: true });
//=> "3 days ago"이 설정에서 유의할 점은 작성한 코드와 브라우저가 받는 코드 사이에 트랜스파일링이 일어나지 않는다는 것입니다. 다행히 요즘 주요 브라우저는 대부분 ES6를 기본 지원하므로 대부분의 경우 문제가 되지 않습니다.
다만 TypeScript나 JSX처럼 트랜스파일링이 필수인 기술은 사용할 수 없습니다. 따라서 JSX를 사용하는 React를 쓰려면 여전히 다른 설정(webpack/rollup/esbuild)에 의존해야 합니다.
Rails 7은 이 과정마저 자동화해 줍니다. 원하는 전략을 골라 명령어 하나만 실행하면 됩니다:
$ ./bin/rails javascript:install:[esbuild|rollup|webpack]Turbolinks와 UJS가 Turbo와 Stimulus로 대체
Rails 7로 생성된 애플리케이션은 기본적으로 Turbolinks와 UJS 대신 Hotwire의 Turbo와 Stimulus를 사용합니다. Hotwire는 HTML을 네트워크를 통해 그대로 전송하여 DOM을 빠르게 갱신하는 새로운 접근 방식입니다.
데이터베이스 계층의 암호화
Rails 7에서는 ActiveRecord::Base의 encrypts 메서드를 사용해 특정 데이터베이스 필드를 암호화 대상으로 지정할 수 있습니다. 초기 설정 이후에는 다음과 같이 코드를 작성하기만 하면 됩니다:
class Message < ApplicationRecord
encrypts :text
end암호화된 속성도 일반 속성과 똑같이 사용할 수 있습니다. Rails 7이 데이터베이스와 애플리케이션 사이에서 자동으로 암호화 및 복호화를 처리해 줍니다.
단, 한 가지 주의할 점이 있습니다. 해당 필드로 데이터베이스를 조회하려면 encrypts 메서드에 deterministic: true 옵션을 전달해야 합니다. 결정적(deterministic) 모드는 기본 비결정적 모드보다 보안성이 낮으므로, 반드시 조회가 필요한 속성에만 사용하는 것이 좋습니다.
비동기 쿼리
이제 데이터를 조회할 때 load_async 메서드를 사용해 결과를 백그라운드에서 미리 가져올 수 있습니다. 컨트롤러 액션에서 서로 연관 없는 여러 쿼리를 로드해야 할 때 특히 유용합니다. 다음과 같이 실행할 수 있습니다:
def PostsController
def index
@posts = Post.load_async
@categories = Category.load_async
end
end이렇게 하면 두 쿼리가 동시에 백그라운드에서 실행됩니다. 각 쿼리가 200ms씩 걸린다면, 직렬로 실행할 때 400ms가 걸릴 작업을 약 200ms 만에 완료할 수 있습니다.
Zeitwerk 모드 필수화
여전히 클래식(classic) 로더를 사용하는 기존 애플리케이션에게는 주요 변경 사항(breaking change)입니다. 모든 Rails 7 애플리케이션은 Zeitwerk 모드를 사용해야 하지만, 전환 자체는 어렵지 않습니다. 자세한 내용은 Zeitwerk 업그레이드 가이드를 참고하세요.
그 외 Rails 7 업데이트
작업 무제한 재시도
ActiveJob에서 이제 retry_on의 attempts 매개변수에 :unlimited를 전달할 수 있습니다. Rails는 최대 시도 횟수 제한 없이 작업을 계속 재시도합니다.
class MyJob < ActiveJob::Base
retry_on(AlwaysRetryException, attempts: :unlimited)
def perform
raise "KABOOM"
end
end네임드 변형(Named Variants)
이제 ActiveStorage에서 변형(variant)에 이름을 붙일 수 있어, 접근할 때마다 크기를 지정할 필요가 없습니다.
class User < ApplicationRecord
has_one_attached :avatar do |attachable|
attachable.variant :thumb, resize: "100x100"
end
end
# avatar.variant(:thumb)를 호출해 썸네일 변형을 가져옵니다:
<%= image_tag user.avatar.variant(:thumb) %>해시를 HTML 속성으로 변환
뷰에서 사용할 수 있는 새로운 tag.attributes 메서드는 해시를 HTML 속성으로 변환해 줍니다:
<input <%= tag.attributes(type: :text, aria: { label: "Search" }) %>>위 코드는 다음을 생성합니다:
<input type="text" aria-label="Search" />Ruby debug 젬
디버깅 기본 도구가 byebug에서 debug 젬으로 변경되었습니다.
이제 코드에서 byebug 대신 debugger를 호출해 디버깅 세션에 진입합니다.
sole로 단일 레코드 검증
레코드를 조회할 때, 쿼리 결과가 반드시 하나의 레코드여야 한다고 보장하고 싶다면 first나 find_by 대신 sole 또는 find_sole_by를 호출할 수 있습니다.
Product.where(["price = %?", price]).sole
# => ActiveRecord::RecordNotFound (해당 가격의 Product가 없는 경우)
# => #<Product ...> (해당 가격의 Product가 하나인 경우)
# => ActiveRecord::SoleRecordExceeded (해당 가격의 Product가 둘 이상인 경우)
user.api_keys.find_sole_by(key: key)
# 위와 동일하게 동작연관 관계 존재 여부 확인
이제 조인 후 id 존재 여부를 확인하는 대신, where.associated(:association)을 사용해 레코드에 연관 관계가 있는지 확인할 수 있습니다.
# Before:
account.users.joins(:contact).where.not(contact_id: nil)
# After:
account.users.where.associated(:contact)컨트롤러 액션에서 생성 중인 파일 스트리밍
이제 컨트롤러 액션 안에서 send_stream을 사용해 실시간으로 생성 중인 파일을 스트리밍할 수 있습니다.
send_stream(filename: "subscribers.csv") do |stream|
stream.write "email_address,updated_at\n"
@subscribers.find_each do |subscriber|
stream.write "#{subscriber.email_address},#{subscriber.updated_at}\n"
end
end이렇게 하면 사용자에게 즉각적인(부분) 응답이 전달되어 무언가 진행 중이라는 것을 알 수 있으며, Heroku에 배포한 경우 추가적인 이점도 있습니다.
파일이 즉시 스트리밍되기 시작하므로 Heroku가 연결을 종료하지 않습니다. 즉, 30초 이상 걸리는 일회성 파일 생성을 위해 굳이 백그라운드 작업을 사용할 필요가 없습니다.
Rails 7로 업그레이드하기
이전 버전들과 마찬가지로 업그레이드는 간단합니다. 아직 공식 업그레이드 가이드는 나오지 않았지만, 절차는 동일합니다:
- Gemfile에서 Rails 버전 번호를 변경합니다(발행일 기준
7.0.0.rc1)하고bundle update를 실행합니다. bundle exec rails app:update를 실행합니다. 인터랙티브 CLI를 따라 필요한 파일을 추가/교체/수정합니다.- 테스트를 실행하고 모든 것이 예상대로 동작하는지 확인합니다.
마무리
Rails 7 릴리스 노트에서 전체 버그 수정, 기능, 변경 사항 목록을 확인할 수 있습니다. 현재는 완전하지 않지만, 곧 업데이트될 것으로 예상됩니다.
아직 Rails 6 이하를 사용 중이라면 주의하세요. Rails 7 정식 출시와 함께 Rails 6.1은 "보안 이슈만 수정" 모드로 전환되어 더 이상 버그 수정을 받지 못하게 됩니다. 또한 이는 Rails 5.2의 EOL(서비스 종료)이기도 하며, 어떠한 수정도 받을 수 없게 됩니다.
즐거운 코딩 되세요!
P.S. Ruby Magic 글을 발행 즉시 읽고 싶다면 Ruby Magic 뉴스레터를 구독하고 어떤 글도 놓치지 마세요!