Rails 7.0 출시 내용 정리

Rails

작년 말, 레일즈 7이 출시되었다. 출시가 된지 꽤 되었지만 아직 레일즈 7을 회사에서 적용하지 않아, 미리 정리도 할 겸 레일즈7 에서 새로 업데이트 된 내용들을 정리해본다.


Node, Webpack 제거

레일즈 7의 JavaScript에서는 노드 JS 또는 Webpack이 더 이상 필요하지 않다. (그래도 npm 패키지는 계속 사용할 수 있다.)

ES6를 Babel로 트랜스파일하고, Webpack으로 번들링을 하려면 많은 설정이 필요하다. 이전에 레일즈는 Webpacker gem 으로 트랜스파일링을 지원했지만, 많은 용량을 차지하고, 코드를 이해 및 변경하기도 어려웠다.

  • **트랜스파일(transpile)**이란 어떤 특정 언어로 작성된 소스 코드를 다른 소스 코드로 변환하는 것을 말합니다.
  • **번들링(Bundling)**이란 기본적으로 여러 개로 흩어져 있는 파일들을 압축, 난독화 등을 하여 하나의 파일로 모아주는 역할을 합니다. 주로 JavaScript를 위한 번들러지만 플러그인 등을 통해 HTML, CSS, 심지어 이미지까지 압축하거나 최적화를 합니다. 출처: 트랜스파일이란?

이제 rails new 메서드로 만든 새 앱의 기본값은 importmaps-rails gem을 통해 import map을 사용한다. 기존 package.json 와 npm, yarn으로 패키지를 설치하는 방법 대신 ./bin/importmap CLI를 사용하여 패키지를 관리 할 수 있다.

예를 들어, 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"

아래와 같이 import 해서 사용할 수 있다.

import { formatDistance, subDays } from 'date-fns'

formatDistance(subDays(new Date(), 3), new Date(), { addSuffix: true })
//=> "3 days ago"

다만, 유의해야 할 점은 ./bin/importmap CLI트랜스파일링 과정 없이 브라우저에서 파일을 가져온다는 것이다. 따라서, .js로 트랜스파일링이 필요한 TypeScriptJSX에서는 사용할 수 없다.

따라서 JSX와 함께 React를 사용하려면 다른 설정(webpack/rollup/esbuild) 으로 설정해야 한다.

레일즈7 에서는 아래 명령어로 webpack/rollup/esbuild 설정 할 수 있다.

$ ./bin/rails javascript:install:[esbuild|rollup|webpack]

Turbolinks와 UJS 대신 ‘Turbo와 Stimulus’ 사용

레일즈 7은 프론트엔드에서 터보링크(TurboLinks) 및 레일즈 UJS를 **핫와이어(Hotwire)의 터보(Turbo) 및 스티뮬러스(Stimulus)**로 대체한다.

각 용어에 대한 개념을 간략하게 찾아보았다.

**터보링크란? ** 애플리케이션의 응답 속도를 높이기 위한 노력의 일환으로 등장한 것이 바로 **터보링크(Turbolinks)**다. 터보링크는 이름에서도 짐작할 수 있듯이 웹페이지에 있는 링크를 클릭했을 때 현재 페이지를 유지한 채, 브라우저에서 전체 웹페이지를 재컴파일하지 않고 BODY 태그와 헤드 부분의 ‘TITLE’ 값만 불러오도록 함으로써 전체적인 응답속도를 향상시키도록 한다. 출처: 터보링크(Turbolinks) 이해하기

Hotwire는 최신 웹페이지를 JavaScript와 JSON 전송대신, HTML을 직접 전송해서 DOM에 대한 빠른 업데이트를 제공하는 새로운 접근 방식이다.

Hotwire란?

  • 최신 웹페이지를 JavaScript와 JSON 전송대신, HTML을 직접 전송해서 만드는 방식 (이메일 서비스 Hey의 프론트엔드에 사용)
  • 빠른 페이지로딩, 서버 렌더링, SPA의 속도와 반응성을 희생하지 않으면서 서버쪽에서 다양한 언어 사용 가능 출처: GeekNews(긱뉴스)

Turbo, Stimulus 란?

  • Turbo : Hotwire의 핵심. 빠른 웹앱을 만들수 있도록 기술의 모음 ㅤ→ Turbo Drive : 모든 a 링크 클릭 및 form submit 을 fetch 로 변환해서 서버에서 읽어와 body를 교체. SPA처럼 동작하게 변경 ㅤ→ Turbo Frames : 복잡한 페이지를 프레임으로 분리해서 각각 로딩 및 렌더링. iframe과 비슷하지만 한개의 DOM에서 처리되는 가상 frame ㅤ→ Turbo Streams : 페이지 변경사항을 Websocket으로 스트림 전송 ㅤ→ Turbo Native : Turbo의 방식을 iOS/Android 하이브리드 앱에 사용 가능하게 지원
  • Stimulus : Turbo가 80%를 처리하고, 나머지 부분을 처리. 최소한의 JS프레임워크

데이터베이스 암호화

Rails 7에서는 ActiveRecord:Base의 암호화 메서드을 사용하여 특정 데이터베이스 필드를 암호화하여 표시할 수 있다. 즉, 초기 설정 후 아래와 같은 코드로 암호화를 할 수 있다.

class Message < ApplicationRecord
  encrypts :text
end

Rails 7 에서 자동으로 암호화 및 해독을 하기 때문에 암호화된 필드는 다른 필드처럼 동일하게 사용하면 된다.

주의해야할 점은, 암호화 방법에 deterministic: true 옵션을 주지 않으면 데이터베이스에서 암호화된 필드를 쿼리할 수 없다는 것이다. deterministic 모드는 기본 모드보다 보안이 낮으므로 반드시 필요한 경우에만 사용해야한다.

비동기 쿼리

레일즈7 에는 백그라운드에서 쿼리를 날릴 수 있는 load_async 메서드가 있다. 이 메서드는 특히 컨트롤러에서 관련 없는 쿼리를 여러 개 로드해야 할 때 중요하다. 코드는 아래와 같이 실행할 수 있다.

def PostsController
  def index
    @posts = Post.load_async
    @categories = Category.load_async
  end
end

위 코드를 실행 하면 Post.load_asyncCategory.load_async 두 쿼리가 동시에 백그라운드에서 실행된다. 따라서, 만약 각 쿼리 실행시간이 200ms 라고 가정할 때, load_async 를 사용한다면 400ms 대신 200ms 이하의 실행시간으로 데이터를 가져올 수 있다.

Zeitwerk 로더 사용

레일즈 7 부터는 auto loader을 classic 모드 대신에 Zeitwerk 모드를 사용해야 한다.

Zeitwerk? Classic?

  • 레일즈 5까지는 Active Support에 구현된 auto loader를 사용했다. 이 auto loader를 :Classic라고 부르며, 레일즈 6.x까지 사용할 수 있다. 다만, 이번에 출시된 레일즈 7에는 더 이상 기본 classic auto loader가 포함되지 않는다.
  • 레일즈 6 부터는 업데이트 된 :Zeitwerk auto loader를 기본으로 사용한다. 특히, 레일즈 7에서는 :Zeitwerk 모드만 사용할 수 있다. 출처: [공식 도큐](https://edgeguides.rubyonrails.org/classic_to_zeitwerk_howto.html)

변경 방법은 매우 간단한데, 레일즈 6 이상은application.rb파일에서 config.autoloader 부분을 변경해주면 된다.

# config/application.rb
config.load_defaults 6.0
config.autoloader = :classic # DELETE THIS LINE

레일즈 5 이하는 config.autoloader = :zeitwerk로 zeitwerk 모드를 설정해주면 된다.

# config/application.rb
config.load_defaults 5.2
config.autoloader = :zeitwerk

Zeitwerk에 대한 자세한 설명은 공식 도큐를 참고하길 바란다.


그 외 레일즈 7 업데이트

Jobs 재시도 횟수 제한 없음

이제 ActiveJob은 retry_on의 attempts 매개 변수로 :unlimited를 설정할 수 있다. attempts: :unlimited 로 설정하면 레일즈은 제한 없이 jobs을 계속 실행할 수 있다.

class MyJob < ActiveJob::Base
  retry_on(AlwaysRetryException, attempts: :unlimited)

  def perform
    raise "KABOOM"
  end
end

Variants 이름 지정

ActiveStorage 에서 모든 액세스 크기를 지정하는 대신 Variants 이름을 지정할 수 있다.

class User < ApplicationRecord
  has_one_attached :avatar do |attachable|
    attachable.variant :thumb, resize: "100x100"
  end
end

# Call avatar.variant(:thumb) to get a thumb variant of an avatar:
<%= image_tag user.avatar.variant(:thumb) %>

tag.attributes 메서드

해시를 HTML 속성으로 변환해 뷰에 사용할 수 있는 새로운 tag.attributes 메서드가 있다.

<input <%= tag.attributes(type: :text, aria: { label: "Search" }) %>>

위 코드는 아래와 같은 html 태그를 생성한다.

<input type="text" aria-label="Search" />

Ruby 디버그

디버깅을 위한 기본값이 byebug에서 debug gem으로 변경되었다. byebug를 호출하는 대신 debugger를 호출하여 디버깅 세션을 시작해야 한다.

Sole 쿼리

단일 레코드를 쿼리할 때 (first, find_by 대신) sole이나 find_sole_by 를 호출할 수 있다.

sole이나 find_sole_by는 해당 레코드가 unique 한지 아닌지 확인하여 단일 레코드일때만 해당 데이터를 리턴한다. 만약 두개 이상의 레코드가 있다면 ActiveRecord::SoleRecordExceeded 에러를 반환한다.

Product.where(["price = %?", price]).sole
# => ActiveRecord::RecordNotFound      (if no Product with given price)
# => #<Product ...>                    (if one Product with given price)
# => ActiveRecord::SoleRecordExceeded  (if more than one Product with given price)

user.api_keys.find_sole_by(key: key)
# as above

Association 확인

:join을 통해 ID가 있는지 없는지 확인하는 대신, where.association(:association) 을 사용해 레코드에 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

이렇게 하면 사용자에게 즉각적인 (partial) 응답을 제공하므로, 사용자가 어떤 일이 일어나고 있는지 알 수 있으며 Heroku에 배포할 경우 추가적인 이점이 있다.

파일이 즉시 스트리밍되기 때문에 헤로쿠는 연결을 종료하지 않는다. 즉, 30초 이상 걸리는 일회성 파일을 생성하기 위해 백그라운드 작업에 의존할 필요가 없다.

Rails 7로 업그레이드

이전 버전의 Rails와 마찬가지로 업그레이드는 간단하다. 아직 공식 업그레이드 가이드가 없지만 단계는 아래와 같다.

  1. Gemfile에서 Rails 버전을 7.0.0.rc1 (혹은 그 이상)로 변경하고 bundle update를 실행한다.
  2. bundle exec rails app:update를 실행한다. CLI에 나온 설명에 따라 필요에 따라 파일을 추가/교체/수정한다.
  3. 테스트를 실행하고 모든 것이 잘 작동하는지 확인한다.

그외


출처




Profile picture
@김하연
4년차 프론트엔드 개발자 입니다. 사용자 경험 개선, 코드의 재사용성, 읽기 쉬운 코드에 집중하여 개발합니다.
AboutGithub LinkedinResume
Loading script...