Rails 시작하기

이 가이드는 Ruby on Rails 시작하는 방법을 다룹니다.

이 가이드를 읽고 나면 다음을 알게 될 것입니다:

  • Rails를 설치하고, 새로운 Rails 애플리케이션을 만들며, 애플리케이션을 데이터베이스에 연결하는 방법.
  • Rails 애플리케이션의 일반적인 구조.
  • MVC(Model, View, Controller) 및 RESTful 디자인의 기본 원칙.
  • Rails 애플리케이션의 기본 구성 요소를 빠르게 생성하는 방법.

가이드 전제 조건

이 가이드는 처음부터 Rails 애플리케이션을 만들고 싶은 초보자를 대상으로 합니다. Rails 경험이 없다고 가정합니다.

Rails는 Ruby 프로그래밍 언어로 작성된 웹 애플리케이션 프레임워크입니다. Ruby 경험이 없다면 Rails에 뛰어들기에 매우 가파른 학습 곡선을 겪을 것입니다. Ruby 학습을 위한 온라인 리소스 목록이 있습니다:

일부 리소스는 Ruby의 오래된 버전을 다루고 있어 일상적인 Rails 개발에서 볼 수 있는 구문이 포함되어 있지 않을 수 있다는 점에 유의하세요.

Rails란 무엇인가?

Rails는 Ruby 프로그래밍 언어로 작성된 웹 애플리케이션 개발 프레임워크입니다. 모든 개발자가 시작하는 데 필요한 것을 가정하여 웹 애플리케이션 개발을 더 쉽게 만드는 것이 목적입니다. 다른 많은 언어와 프레임워크보다 더 적은 코드로 더 많은 작업을 수행할 수 있습니다. 경험 많은 Rails 개발자들은 웹 애플리케이션 개발이 더 재미있다고 보고합니다.

Rails는 고집스러운 소프트웨어입니다. 모든 개발자에게 “최선의” 방법이 있다고 가정하고 그 방법을 장려하며, 때로는 대안을 억제합니다. “Rails 방식"을 배우면 생산성이 크게 향상될 것입니다. 다른 언어의 오래된 습관을 Rails 개발에 가져오고 다른 곳에서 배운 패턴을 사용하려고 한다면 즐겁지 않은 경험을 할 수 있습니다.

Rails 철학에는 두 가지 주요 지침 원칙이 있습니다:

  • 반복하지 말 것(Don’t Repeat Yourself, DRY): DRY는 소프트웨어 개발의 원칙으로, "시스템 내에서 지식의 각 조각은 단일, 명확, 권위 있는 표현을 가져야 한다"고 말합니다. 같은 정보를 반복해서 작성하지 않으면 코드가 더 유지보수 가능하고, 확장 가능하며, 버그가 적습니다.
  • 설정보다 규약(Convention Over Configuration): Rails는 웹 애플리케이션에서 많은 일을 수행하는 가장 좋은 방법에 대한 의견을 가지고 있으며, 끝없는 구성 파일 대신 이 일련의 규약을 기본값으로 사용합니다.

새 Rails 프로젝트 만들기

이 가이드를 단계별로 따라가는 것이 가장 좋습니다. 모든 단계는 이 예제 애플리케이션을 실행하는 데 필수적이며 추가 코드나 단계는 필요하지 않습니다.

이 가이드를 따라하면서 blog라는 이름의 Rails 프로젝트를 만들 것입니다. 애플리케이션을 시작하기 전에 Rails 자체가 설치되어 있는지 확인해야 합니다.

참고: 아래 예제에서는 UNIX 계열 OS의 터미널 프롬프트를 나타내기 위해 $를 사용했지만, 사용자 환경에 따라 다르게 표시될 수 있습니다. Windows를 사용하는 경우 프롬프트는 C:\source_code>와 같이 표시될 것입니다.

Rails 설치하기

Rails를 설치하기 전에 시스템에 필요한 전제 조건이 설치되어 있는지 확인해야 합니다. 여기에는 다음이 포함됩니다:

  • Ruby
  • SQLite3

Ruby 설치하기

명령줄 프롬프트를 열어 봅시다. macOS에서는 Terminal.app을, Windows에서는 시작 메뉴에서 "실행"을 선택하고 cmd.exe를 입력하세요. 달러 기호 $로 시작하는 모든 명령은 명령줄에서 실행해야 합니다. 현재 설치된 Ruby 버전을 확인합니다:

$ ruby --version
ruby 3.1.0

Rails는 Ruby 버전 3.1.0 이상을 요구합니다. 최신 Ruby 버전을 사용하는 것이 좋습니다. 반환된 버전 번호가 이보다 낮다면(예: 2.3.7 또는 1.8.7), 새 Ruby 버전을 설치해야 합니다.

Windows에 Rails를 설치하려면 먼저 Ruby Installer를 설치해야 합니다.

대부분의 운영 체제에 대한 설치 방법은 ruby-lang.org를 참고하세요.

SQLite3 설치하기

SQLite3 데이터베이스 설치도 필요합니다. 많은 UNIX 계열 OS에는 적절한 버전의 SQLite3가 포함되어 있습니다. 그렇지 않은 경우 SQLite3 웹사이트에서 설치 지침을 찾을 수 있습니다.

올바르게 설치되었고 PATH에 포함되어 있는지 확인합니다:

$ sqlite3 --version

프로그램이 버전을 보고해야 합니다.

Rails 설치하기

Rails를 설치하려면 RubyGems에서 제공하는 gem install 명령을 사용합니다:

$ gem install rails

모든 것이 올바르게 설치되었는지 확인하려면 새 터미널에서 다음을 실행할 수 있습니다:

$ rails --version
Rails 7.2.0

"Rails 7.2.0"이라고 표시되면 계속 진행할 준비가 된 것입니다.

블로그 애플리케이션 만들기

Rails에는 개발 작업을 더 쉽게 만들기 위해 설계된 여러 스크립트 생성기가 있습니다. 그 중 하나가 새 애플리케이션 생성기로, 직접 작성할 필요 없이 새로운 Rails 애플리케이션의 기반을 제공합니다.

이 생성기를 사용하려면 터미널을 열고, 파일을 만들 수 있는 디렉토리로 이동한 다음 다음을 실행합니다:

$ rails new blog

이 명령은 blog라는 이름의 Rails 애플리케이션을 blog 디렉토리에 만들고 Gemfile에 언급된 gem 종속성을 bundle install을 사용하여 설치합니다.

팁: rails new --help를 실행하면 Rails 애플리케이션 생성기가 허용하는 모든 명령줄 옵션을 볼 수 있습니다.

블로그 애플리케이션을 만든 후 해당 폴더로 이동합니다:

$ cd blog

blog 디렉토리에는 Rails 애플리케이션의 구조를 구성하는 여러 생성된 파일과 폴더가 있습니다. 이 자습서의 대부분의 작업은 app 폴더에서 이루어지지만, Rails가 기본적으로 생성하는 각 파일과 폴더의 기능은 다음과 같습니다:

파일/폴더 목적
app/ 애플리케이션의 컨트롤러, 모델, 뷰, 헬퍼, 메일러, 채널, 작업 및 자산이 포함되어 있습니다. 이 가이드의 나머지 부분에서 이 폴더에 중점을 둘 것입니다.
bin/ 애플리케이션을 시작하는 rails 스크립트와 다른 스크립트가 포함되어 있습니다.
config/ 애플리케이션의 라우팅, 데이터베이스 및 기타 구성이 포함되어 있습니다. 이에 대한 자세한 내용은 Rails 애플리케이션 구성을 참조하세요.
config.ru Rack 기반 서버를 사용하여 애플리케이션을 시작하기 위한 Rack 구성입니다. Rack에 대한 자세한 내용은 Rack 웹사이트를 참조하세요.
db/ 현재 데이터베이스 스키마와 데이터베이스 마이그레이션이 포함되어 있습니다.
Dockerfile Docker에 대한 구성 파일입니다.
Gemfile
Gemfile.lock
이 파일에서 Rails 애플리케이션에 필요한 gem 종속성을 지정할 수 있습니다. 이 파일은 Bundler gem에 의해 사용됩니다. Bundler에 대한 자세한 내용은 Bundler 웹사이트를 참조하세요.
lib/ 애플리케이션에 대한 확장 모듈이 포함되어 있습니다.
log/ 애플리케이션 로그 파일이 포함되어 있습니다.
public/ 정적 파일과 컴파일된 자산이 포함되어 있습니다. 애플리케이션이 실행 중일 때 이 디렉토리가 그대로 노출됩니다.
Rakefile 이 파일은 명령줄에서 실행할 수 있는 작업을 찾고 로드합니다. 작업 정의는 Rails 구성 요소 전체에 걸쳐 있습니다. Rakefile을 변경하는 대신 lib/tasks 디렉토리에 새 작업 파일을 추가해야 합니다.
README.md 이것은 애플리케이션에 대한 간단한 지침 설명서입니다. 다른 사람들에게 애플리케이션이 무엇을 하는지, 어떻게 설정하는지 등을 알려주도록 이 파일을 편집해야 합니다.
storage/ 디스크 서비스에 대한 Active Storage 파일입니다. 이에 대한 자세한 내용은 Active Storage 개요를 참조하세요.
test/ 단위 테스트, 픽스처 및 기타 테스트 도구가 포함되어 있습니다. 이에 대한 자세한 내용은 Rails 애플리케이션 테스트를 참조하세요.
tmp/ 캐시 및 pid 파일과 같은 임시 파일이 포함되어 있습니다.
vendor/ 모든 타사 코드가 포함되어 있습니다. 일반적인 Rails 애플리케이션에서는 벤더 gem이 여기에 포함됩니다.
.devcontainer/ 이 폴더에는 개발 컨테이너에 대한 구성이 포함되어 있습니다.
.dockerignore 이 파일은 Docker가 컨테이너에 복사하지 않아야 할 파일을 알려줍니다.
.gitattributes 이 파일은 git 리포지토리의 특정 경로에 대한 메타데이터를 정의합니다. 이 메타데계속해서 번역 결과:

데이터는 git과 다른 도구에 의해 향상된 동작에 사용될 수 있습니다. gitattributes 문서에서 자세한 내용을 확인할 수 있습니다.

|.github/|GitHub 관련 파일이 포함되어 있습니다.| |.gitignore|이 파일은 git이 무시해야 할 파일(또는 패턴)을 알려줍니다. GitHub - Ignoring files에서 파일 무시에 대한 자세한 정보를 확인할 수 있습니다.| |.rubocop.yml|이 파일에는 RuboCop에 대한 구성이 포함되어 있습니다.| |.ruby-version|이 파일에는 기본 Ruby 버전이 포함되어 있습니다.|

안녕, Rails!

빨리 화면에 텍스트를 표시해 보겠습니다. 이를 위해 Rails 애플리케이션 서버를 실행해야 합니다.

웹 서버 시작하기

이미 기능적인 Rails 애플리케이션이 있습니다. 이를 확인하려면 개발 머신에서 웹 서버를 시작해야 합니다. blog 디렉토리에서 다음 명령을 실행하여 이를 수행할 수 있습니다:

$ bin/rails server

팁: Windows를 사용하는 경우 bin 폴더의 스크립트를 직접 Ruby 인터프리터에 전달해야 합니다. 예: ruby bin\rails server.

팁: JavaScript 자산 압축에는 시스템에 JavaScript 런타임이 필요합니다. 런타임이 없는 경우 자산 압축 중에 execjs 오류가 발생합니다. 일반적으로 macOS와 Windows에는 JavaScript 런타임이 설치되어 있습니다. therubyrhino는 JRuby 사용자에게 권장되는 런타임이며 JRuby로 생성된 앱의 Gemfile에 기본적으로 추가됩니다. 지원되는 모든 런타임은 ExecJS에서 확인할 수 있습니다.

이렇게 하면 Puma 웹 서버가 시작됩니다. 애플리케이션을 확인하려면 브라우저 창을 열고 http://localhost:3000으로 이동하세요. Rails 기본 정보 페이지가 표시되어야 합니다:

Rails 시작 페이지 스크린샷

웹 서버를 중지하려면 실행 중인 터미널 창에서 Ctrl+C를 누르세요. 개발 환경에서는 일반적으로 서버를 다시 시작할 필요가 없습니다. 파일에 대한 변경 사항은 자동으로 서버에 의해 감지됩니다.

Rails 시작 페이지는 새 Rails 애플리케이션에 대한 "연기 테스트"입니다. 이는 구성이 올바르게 되어 있어 페이지를 제공할 수 있음을 확인합니다.

"안녕하세요, Rails"라고 말하기

Rails에게 "안녕하세요"라고 말하려면 최소한 라우트, 컨트롤러 액션, 가 필요합니다. 라우트는 요청을 컨트롤러 액션에 매핑합니다. 컨트롤러 액션은 요청을 처리하고 뷰에 대한 데이터를 준비합니다. 뷰는 데이터를 원하는 형식으로 표시합니다.

구현 측면에서: 라우트는 Ruby DSL(Domain-Specific Language)로 작성된 규칙입니다. 컨트롤러는 Ruby 클래스이며, 공개 메서드가 액션입니다. 뷰는 HTML과 Ruby의 혼합으로 작성된 템플릿입니다.

먼저 config/routes.rbRails.application.routes.draw 블록 맨 위에 다음과 같은 라우트를 추가해 보겠습니다:

Rails.application.routes.draw do
  get "/articles", to: "articles#index"

  # 이 파일의 DSL에 대한 자세한 내용은 https://guides.rubyonrails.org/routing.html를 참조하세요.
end

위의 라우트는 GET /articles 요청이 ArticlesControllerindex 액션에 매핑된다고 선언합니다.

ArticlesControllerindex 액션을 만들려면 컨트롤러 생성기를 실행합니다(라우트를 이미 가지고 있으므로 --skip-routes 옵션을 사용):

$ bin/rails generate controller Articles index --skip-routes

Rails는 다음과 같은 여러 파일을 생성합니다:

create  app/controllers/articles_controller.rb
invoke  erb
create    app/views/articles
create    app/views/articles/index.html.erb
invoke  test_unit
create    test/controllers/articles_controller_test.rb
invoke  helper
create    app/helpers/articles_helper.rb
invoke    test_unit

가장 중요한 것은 컨트롤러 파일 app/controllers/articles_controller.rb입니다. 살펴보겠습니다:

class ArticlesController < ApplicationController
  def index
  end
end

index 액션은 비어 있습니다. 액션이 뷰를 명시적으로 렌더링하거나 다른 방식으로 HTTP 응답을 트리거하지 않으면 Rails는 자동으로 컨트롤러와 액션 이름이 일치하는 뷰를 렌더링합니다. 규약 우선 구성! 뷰는 app/views 디렉토리에 있습니다. 따라서 index 액션은 기본적으로 app/views/articles/index.html.erb를 렌더링합니다.

app/views/articles/index.html.erb를 열고 내용을 다음으로 바꿔봅시다:

<h1>안녕하세요, Rails!</h1>

이전에 컨트롤러 생성기를 실행하기 위해 웹 서버를 중지했다면 bin/rails server로 다시 시작하세요. 이제 http://localhost:3000/articles를 방문하면 텍스트가 표시됩니다!

애플리케이션 홈 페이지 설정하기

현재 http://localhost:3000에는 여전히 Ruby on Rails 로고가 있는 페이지가 표시됩니다. http://localhost:3000에서도 "안녕하세요, Rails!” 텍스트를 표시해 보겠습니다. 이를 위해 애플리케이션의 루트 경로를 적절한 컨트롤러와 액션에 매핑하는 라우트를 추가할 것입니다.

config/routes.rb를 열고 Rails.application.routes.draw 블록 맨 위에 다음과 같은 root 라우트를 추가합니다:

Rails.application.routes.draw do
  root "articles#index"

  get "/articles", to: "articles#index"
end

이제 http://localhost:3000을 방문하면 “안녕하세요, Rails!” 텍스트를 볼 수 있습니다. 이는 root 라우트도 ArticlesControllerindex 액션에 매핑되기 때문입니다.

팁: 라우팅에 대해 자세히 알아보려면 Rails 외부에서 라우팅을 참조하세요.

자동 로드

Rails 애플리케이션에서는 require를 사용하여 애플리케이션 코드를 로드하지 않습니다.

ArticlesControllerApplicationController를 상속한다는 것을 알아차렸을 것입니다. 그러나 app/controllers/articles_controller.rb에는 다음과 같은 것이 없습니다:

require "application_controller" # 이렇게 하지 마세요.

애플리케이션 클래스와 모듈은 어디에서나 사용할 수 있으며, app 아래의 파일을 require할 필요가 없고 해서는 안 됩니다. 이 기능을 자동 로드라고 하며, 자동 로드 및 상수 다시 로드에서 자세히 알아볼 수 있습니다.

require가 필요한 경우는 두 가지뿐입니다:

  • lib 디렉토리의 파일을 로드할 때.
  • Gemfilerequire: false가 있는 gem 종속성을 로드할 때.

MVC와 당신

지금까지 라우트, 컨트롤러, 액션 및 뷰에 대해 논의했습니다. 이 모든 것은 MVC(Model-View-Controller) 패턴을 따르는 웹 애플리케이션의 일반적인 구성 요소입니다. MVC는 애플리케이션의 책임을 나누어 더 쉽게 이해할 수 있게 하는 설계 패턴입니다. Rails는 규약에 따라 이 설계 패턴을 따릅니다.

컨트롤러와 뷰가 있으므로 다음 구성 요소인 모델을 생성해 보겠습니다.

모델 생성하기

모델은 데이터를 나타내는 Ruby 클래스입니다. 또한 모델은 Rails의 Active Record라는 기능을 통해 애플리케이션의 데이터베이스와 상호 작용할 수 있습니다.

모델을 정의하려면 모델 생성기를 사용합니다:

$ bin/rails generate model Article title:string body:text

참고: 모델 이름은 단수여야 합니다. 왜냐하면 인스턴스화된 모델은 단일 데이터 레코드를 나타내기 때문입니다. 이 규칙을 기억하는 데 도움이 되도록 모델의 생성자를 생각해 보세요. Article.new(...)와 같이 작성하고 Articles.new(…) 와 같이 작성하지 않습니다.

이 명령은 다음과 같은 여러 파일을 생성합니다:

invoke  active_record
create    db/migrate/<timestamp>_create_articles.rb
create    app/models/article.rb
invoke    test_unit
create      test/models/article_test.rb
create      test/fixtures/articles.yml

우리가 집중할 두 개의 파일은 마이그레이션 파일(db/migrate/<timestamp>_create_articles.rb)과 모델 파일(app/models/article.rb)입니다.

데이터베이스 마이그레이션

마이그레이션은 애플리케이션 데이터베이스의 구조를 변경하는 데 사용됩니다. Rails 애플리케이션에서 마이그레이션은 데이터베이스 독립적으로 작성된 Ruby로 작성됩니다.

새 마이그레이션 파일의 내용을 살펴보겠습니다:

class CreateArticles < ActiveRecord::Migration[7.2]
  def change
    create_table :articles do |t|
      t.string :title
      t.text :body

      t.timestamps
    end
  end
end

create_table 호출은 articles 테이블의 구조를 지정합니다. 기본적으로 create_table 메서드는 기본 키로 사용되는 id 열을 추가합니다. 따라서 테이블의 첫 번째 레코드는 id가 1이 되고, 다음 레코드는 id가 2가 되는 식입니다.

create_table 블록 내에서 titlebody 두 개의 열이 정의됩니다. 이 열들은 생성기 명령(bin/rails generate model Article title:string body:text)에 포함되었기 때문에 추가되었습니다.

블록의 마지막 줄에는 t.timestamps 호출이 있습니다. 이 메서드는 created_atupdated_at이라는 두 개의 추가 열을 정의합니다. 나중에 보겠지만 Rails가 이 값을 자동으로 관리할 것입니다.

다음 명령을 실행하여 마이그레이션을 실행해계속해서 번역 결과:

다음 명령을 실행하여 마이그레이션을 실행해 보겠습니다:

$ bin/rails db:migrate

이 명령은 테이블이 생성되었음을 나타내는 다음과 같은 출력을 표시합니다:

==  CreateArticles: migrating ===================================
-- create_table(:articles)
   -> 0.0018s
==  CreateArticles: migrated (0.0018s) ==========================

팁: 마이그레이션에 대해 자세히 알아보려면 Active Record 마이그레이션을 참조하세요.

이제 모델을 사용하여 데이터베이스와 상호 작용할 수 있습니다.

모델을 사용하여 데이터베이스와 상호 작용하기

모델을 약간 실험해 보려면 Rails의 콘솔 기능을 사용할 것입니다. 콘솔은 irb와 같은 대화형 코딩 환경이지만 Rails와 애플리케이션 코드도 자동으로 로드합니다.

다음 명령으로 콘솔을 실행해 보겠습니다:

$ bin/rails console

다음과 같은 irb 프롬프트가 표시됩니다:

Loading development environment (Rails 7.2.0)
irb(main):001:0>

이 프롬프트에서 새 Article 객체를 초기화할 수 있습니다:

irb> article = Article.new(title: "Hello Rails", body: "I am on Rails!")

이 객체는 아직 데이터베이스에 저장되지 않았다는 점에 유의하세요. 현재 콘솔에서만 사용할 수 있습니다. 객체를 데이터베이스에 저장하려면 save를 호출해야 합니다:

irb> article.save
(0.1ms)  begin transaction
Article Create (0.4ms)  INSERT INTO "articles" ("title", "body", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["title", "Hello Rails"], ["body", "I am on Rails!"], ["created_at", "2020-01-18 23:47:30.734416"], ["updated_at", "2020-01-18 23:47:30.734416"]]
(0.9ms)  commit transaction
=> true

위의 출력에는 INSERT INTO "articles" ...라는 데이터베이스 쿼리가 표시됩니다. 이는 해당 기사가 테이블에 삽입되었음을 나타냅니다. article 객체를 다시 살펴보면 흥미로운 일이 일어났음을 알 수 있습니다:

irb> article
=> #<Article id: 1, title: "Hello Rails", body: "I am on Rails!", created_at: "2020-01-18 23:47:30", updated_at: "2020-01-18 23:47:30">

id, created_atupdated_at 속성이 이제 설정되어 있습니다. Rails가 객체를 저장할 때 이를 자동으로 처리했습니다.

데이터베이스에서 이 기사를 가져오려면 모델의 find 메서드를 호출하고 id를 인수로 전달하면 됩니다:

irb> Article.find(1)
=> #<Article id: 1, title: "Hello Rails", body: "I am on Rails!", created_at: "2020-01-18 23:47:30", updated_at: "2020-01-18 23:47:30">

데이터베이스에서 모든 기사를 가져오려면 모델의 all 메서드를 호출하면 됩니다:

irb> Article.all
=> #<ActiveRecord::Relation [#<Article id: 1, title: "Hello Rails", body: "I am on Rails!", created_at: "2020-01-18 23:47:30", updated_at: "2020-01-18 23:47:30">]>

이 메서드는 ActiveRecord::Relation 객체를 반환하는데, 이는 강화된 배열로 생각할 수 있습니다.

팁: 모델에 대해 자세히 알아보려면 Active Record 기본 사항Active Record 쿼리 인터페이스를 참조하세요.

모델은 MVC 퍼즐의 마지막 조각입니다. 이제 모든 조각을 연결해 보겠습니다.

기사 목록 표시하기

app/controllers/articles_controller.rb의 컨트롤러로 돌아가서 index 액션을 수정하여 데이터베이스에서 모든 기사를 가져오겠습니다:

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end
end

컨트롤러의 인스턴스 변수는 뷰에서 액세스할 수 있습니다. 따라서 app/views/articles/index.html.erb에서 @articles를 참조할 수 있습니다. 해당 파일을 열고 내용을 다음으로 바꿔봅시다:

<h1>기사</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
      <%= article.title %>
    </li>
  <% end %>
</ul>

위의 코드는 HTML과 ERB의 혼합입니다. ERB(Embedded Ruby)는 문서에 포함된 Ruby 코드를 평가하는 템플릿 시스템입니다. 여기에서 두 가지 유형의 ERB 태그를 볼 수 있습니다: <% %><%= %>. <% %> 태그는 “포함된 Ruby 코드를 평가하라"는 의미입니다. <%= %> 태그는 "포함된 Ruby 코드를 평가하고 반환된 값을 출력하라"는 의미입니다. 일반 Ruby 프로그램에서 작성할 수 있는 모든 것을 이러한 ERB 태그 내에 넣을 수 있지만, 가독성을 위해 ERB 태그 내용은 짧게 유지하는 것이 좋습니다.

@articles.each는 값을 출력하지 않으므로 <% %> 태그로 감쌌습니다. 그러나 article.title의 값은 출력하려고 하므로 <%= %> 태그로 감쌌습니다.

http://localhost:3000을 방문하면 결과를 볼 수 있습니다. (bin/rails server가 실행 중이어야 합니다!) 다음과 같은 일이 일어납니다:

  1. 브라우저가 GET http://localhost:3000 요청을 합니다.
  2. Rails 애플리케이션이 이 요청을 받습니다.
  3. Rails 라우터가 루트 경로를 ArticlesControllerindex 액션에 매핑합니다.
  4. index 액션은 Article 모델을 사용하여 데이터베이스의 모든 기사를 가져옵니다.
  5. Rails는 자동으로 app/views/articles/index.html.erb 뷰를 렌더링합니다.
  6. 뷰의 ERB 코드가 평가되어 HTML이 출력됩니다.
  7. 서버가 브라우저에 HTML 응답을 보냅니다.

MVC 조각을 모두 연결했고 첫 번째 컨트롤러 액션도 완성했습니다! 다음으로 두 번째 액션을 만들어 보겠습니다.

CRUDit Where CRUDit Is Due

거의 모든 웹 애플리케이션에는 CRUD(Create, Read, Update, Delete) 작업이 포함됩니다. 애플리케이션의 대부분의 작업이 CRUD일 수도 있습니다. Rails는 이를 인정하고 CRUD 코드를 단순화하는 많은 기능을 제공합니다.

애플리케이션에 더 많은 기능을 추가하면서 이러한 기능을 탐색해 보겠습니다.

단일 기사 표시하기

현재 데이터베이스의 모든 기사를 나열하는 뷰가 있습니다. 기사의 제목과 본문을 표시하는 새 뷰를 추가해 보겠습니다.

새 컨트롤러 액션(다음에 추가할)에 매핑되는 새 라우트를 추가하는 것부터 시작합니다. config/routes.rb를 열고 여기에 표시된 마지막 라우트를 삽입합니다:

Rails.application.routes.draw do
  root "articles#index"

  get "/articles", to: "articles#index"
  get "/articles/:id", to: "articles#show"
end

새 라우트는 또 다른 get 라우트이지만 경로에 :id가 추가되어 있습니다. 이는 라우트 매개변수를 나타냅니다. 라우트 매개변수는 요청 경로의 세그먼트를 캡처하고 해당 값을 params 해시에 넣습니다. 예를 들어 GET http://localhost:3000/articles/1을 처리할 때 1:id의 값으로 캡처되어 params[:id]에 저장됩니다.

이제 ArticlesControllershow 액션을 index 액션 아래에 추가해 보겠습니다:

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end
end

show 액션은 라우트 매개변수에 의해 캡처된 ID를 사용하여 Article.find(이전에 언급됨)를 호출합니다. 반환된 기사는 @article 인스턴스 변수에 저장되어 뷰에서 사용할 수 있습니다. 기본적으로 show 액션은 app/views/articles/show.html.erb를 렌더링합니다.

app/views/articles/show.html.erb를 만들고 다음 내용을 추가해 보겠습니다:

<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

이제 http://localhost:3000/articles/1을 방문하면 기사를 볼 수 있습니다!

마지막으로 기사 페이지로 쉽게 이동할 수 있도록 app/views/articles/index.html.erb의 각 기사 제목에 링크를 추가해 보겠습니다:

<h1>기사</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
      <a href="/articles/<%= article.id %>">
        <%= article.title %>
      </a>
    </li>
  <% end %>
</ul>

리소스 라우팅

지금까지 "R”(Read)에 대해 다뤘습니다. 이제 “C”(Create), “U”(Update), “D”(Delete)를 다루어 보겠습니다. 이러한 CRUD 작업을 수행하는 라우트, 컨트롤러 액션 및 뷰의 조합을 리소스라고 합니다. 예를 들어 우리 애플리케이션에서는 기사가 리소스입니다.

Rails는 resources 라우트 메서드를 제공하여 리소스 컬렉션에 대한 모든 표준 라우트를 매핑합니다. 따라서 “C”, “U”, “D” 섹션으로 진행하기 전에 config/routes.rb의 두 get 라우트를 resources로 바꿔보겠습니다:

Rails.application.routes.draw do
  root "articles#index"

  resources :articles
end

bin/rails routes 명령을 실행하여 매핑된 라우트를 확인할 수 있습니다:

$ bin/rails routes
      Prefix Verb   URI Pattern                  Controller#Action
        root GET    /                            articles#index
    articles GET    /articles(.:format)          articles#index
 new_article GET    /articles/new(.:format)계속해서 번역 결과:

      Prefix Verb   URI Pattern                  Controller#Action
     article GET    /articles/:id(.:format)      articles#show
             POST   /articles(.:format)          articles#create
    edit_article GET    /articles/:id/edit(.:format) articles#edit
             PATCH  /articles/:id(.:format)      articles#update
             DELETE /articles/:id(.:format)      articles#destroy

resources 메서드는 또한 코드가 특정 라우트 구성에 의존하지 않도록 하는 URL 및 경로 헬퍼 메서드를 설정합니다. “Prefix” 열의 값에 _url 또는 _path 접미사를 붙이면 이러한 헬퍼의 이름이 됩니다. 예를 들어 article_path 헬퍼는 기사에 대해 "/articles/#{article.id}"를 반환합니다. 우리는 이를 사용하여 app/views/articles/index.html.erb의 링크를 정리할 수 있습니다:

<h1>기사</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
      <a href="<%= article_path(article) %>">
        <%= article.title %>
      </a>
    </li>
  <% end %>
</ul>

그러나 link_to 헬퍼를 사용하여 한 단계 더 나아가겠습니다. link_to 헬퍼는 첫 번째 인수를 링크 텍스트로, 두 번째 인수를 링크 대상으로 사용하여 링크를 렌더링합니다. 두 번째 인수로 모델 객체를 전달하면 link_to가 적절한 경로 헬퍼를 호출하여 객체를 경로로 변환합니다. 따라서 app/views/articles/index.html.erb는 다음과 같이 됩니다:

<h1>기사</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
      <%= link_to article.title, article %>
    </li>
  <% end %>
</ul>

멋지네요!

팁: 라우팅에 대해 자세히 알아보려면 Rails 외부에서 라우팅을 참조하세요.

새 기사 만들기

이제 CRUD의 “C”(Create)로 넘어가 보겠습니다. 일반적으로 웹 애플리케이션에서 새 리소스를 만드는 것은 여러 단계 프로세스입니다. 먼저 사용자가 작성할 폼을 요청합니다. 그런 다음 사용자가 폼을 제출합니다. 오류가 없으면 리소스가 생성되고 확인 메시지가 표시됩니다. 그렇지 않으면 오류 메시지와 함께 폼이 다시 표시되고 프로세스가 반복됩니다.

Rails 애플리케이션에서 이러한 단계는 일반적으로 컨트롤러의 newcreate 액션에 의해 처리됩니다. app/controllers/articles_controller.rb에 이러한 액션의 일반적인 구현을 추가해 보겠습니다:

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(title: "...", body: "...")

    if @article.save
      redirect_to @article
    else
      render :new, status: :unprocessable_entity
    end
  end
end

new 액션은 새 기사를 인스턴스화하지만 저장하지는 않습니다. 이 기사는 폼을 작성할 때 뷰에서 사용됩니다. 기본적으로 new 액션은 app/views/articles/new.html.erb를 렌더링합니다. 이 파일을 다음에 만들 것입니다.

create 액션은 제목과 본문에 대한 값을 사용하여 새 기사를 인스턴스화하고 저장을 시도합니다. 기사가 성공적으로 저장되면 브라우저를 "http://localhost:3000/articles/#{@article.id}"로 리디렉션합니다. 그렇지 않으면 app/views/articles/new.html.erb422 Unprocessable Entity 상태 코드로 다시 렌더링하여 폼을 표시합니다. 여기의 제목과 본문은 더미 값입니다. 폼을 만든 후 이를 변경할 것입니다.

참고: redirect_to는 브라우저가 새 요청을 하도록 하지만 render는 현재 요청에 대해 지정된 뷰를 렌더링합니다. 데이터베이스 또는 애플리케이션 상태를 변경한 후에는 redirect_to를 사용하는 것이 중요합니다. 그렇지 않으면 사용자가 페이지를 새로 고치면 동일한 요청이 이루어지고 변경 사항이 반복됩니다.

폼 빌더 사용하기

Rails의 폼 빌더 기능을 사용하여 폼을 만들 것입니다. 폼 빌더를 사용하면 완전히 구성되고 Rails 규칙을 따르는 폼을 출력하는 데 필요한 코드를 최소화할 수 있습니다.

app/views/articles/new.html.erb를 만들고 다음 내용을 추가해 보겠습니다:

<h1>새 기사</h1>

<%= form_with model: @article do |form| %>
  <div>
    <%= form.label :title %><br>
    <%= form.text_field :title %>
  </div>

  <div>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </div>

  <div>
    <%= form.submit %>
  </div>
<% end %>

form_with 헬퍼 메서드는 폼 빌더를 인스턴스화합니다. form_with 블록 내에서 labeltext_field와 같은 폼 빌더 메서드를 호출하여 적절한 폼 요소를 출력할 수 있습니다.

form_with 호출의 결과는 다음과 같은 출력이 될 것입니다:

<form action="/articles" accept-charset="UTF-8" method="post">
  <input type="hidden" name="authenticity_token" value="...">

  <div>
    <label for="article_title">Title</label><br>
    <input type="text" name="article[title]" id="article_title">
  </div>

  <div>
    <label for="article_body">Body</label><br>
    <textarea name="article[body]" id="article_body"></textarea>
  </div>

  <div>
    <input type="submit" name="commit" value="Create Article" data-disable-with="Create Article">
  </div>
</form>

팁: 폼 빌더에 대해 자세히 알아보려면 Action View 폼 헬퍼를 참조하세요.

강력한 매개변수 사용하기

제출된 폼 데이터는 라우트 매개변수와 함께 params 해시에 포함됩니다. 따라서 create 액션은 params[:article][:title]을 통해 제출된 제목에 액세스하고 params[:article][:body]를 통해 제출된 본문에 액세스할 수 있습니다. 이러한 값을 개별적으로 Article.new에 전달할 수 있지만 장황하고 오류가 발생할 수 있습니다. 그리고 필드가 더 많아지면 더 나빠질 것입니다.

대신 단일 해시를 전달할 것입니다. 그러나 해시에 포함된 값을 지정해야 합니다. 그렇지 않으면 악의적인 사용자가 추가 폼 필드를 제출하고 개인 데이터를 덮어쓸 수 있습니다. 실제로 필터링되지 않은 params[:article] 해시를 Article.new에 직접 전달하면 Rails가 ForbiddenAttributesError를 발생시켜 문제를 알려줍니다. 따라서 Rails의 강력한 매개변수 기능을 사용하여 params를 필터링할 것입니다. 이를 params에 대한 강력한 타이핑으로 생각할 수 있습니다.

app/controllers/articles_controller.rb의 맨 아래에 article_params라는 이름의 비공개 메서드를 추가하고 create를 변경하여 이를 사용해 보겠습니다:

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to @article
    else
      render :new, status: :unprocessable_entity
    end
  end

  private
    def article_params
      params.require(:article).permit(:title, :body)
    end
end

팁: 강력한 매개변수에 대해 자세히 알아보려면 Action Controller 개요 § 강력한 매개변수를 참조하세요.

유효성 검사 및 오류 메시지 표시하기

보았듯이 리소스를 만드는 것은 여러 단계 프로세스입니다. 잘못된 사용자 입력을 처리하는 것도 이 프로세스의 또 다른 단계입니다. Rails는 유효성 검사라는 기능을 제공하여 잘못된 사용자 입력을 처리할 수 있습니다. 유효성 검사는 모델 객체가 저장되기 전에 확인되는 규칙입니다. 검사 중 하나라도 실패하면 저장이 중단되고 모델 객체의 errors 속성에 적절한 오류 메시지가 추가됩니다.

app/models/article.rb에 일부 유효성 검사를 추가해 보겠습니다:

class Article < ApplicationRecord
  validates :title, presence: true
  validates :body, presence: true, length: { minimum: 10 }
end

첫 번째 유효성 검사는 title 값이 반드시 존재해야 한다고 선언합니다. title이 문자열이므로 이는 title 값에 최소 하나의 비공백 문자가 포함되어야 함을 의미합니다.

두 번째 유효성 검사는 body 값도 반드시 존재해야 하며 최소 10자 이상이어야 한다고 선언합니다.

참고: titlebody 속성이 어디에 정의되는지 궁금할 수 있습니다. Active Record는 자동으로 테이블 열에 대한 모델 속성을 정의하므로 모델 파일에 이러한 속성을 선언할 필요가 없습니다.

유효성 검사가 준비되었으므로 app/views/articles/new.html.erb를 수정하여 titlebody에 대한 오류 메시지를 표시해 보겠습니다:

<h1>새 기사</h1>

<%= form_with model: @article do |form| %>
  <div>
    <%= form.label :title %><br>
    <%= form.text_field :title %>
    <% @article.errors.full_messages_for(:title).each do |message| %>
      <div><%= message %></div>
    <% end %>
  </div>

  <div>
    <%= form.label :body %><br>
    <%= form.text_area :body %><br>
    <% @article.errors.full_messages_for(:body).each do |message| %>
      <div><%= message %></div>
    <% end %>
  </div>

  <div>
    <%= form.submit %>
  </div>
<% end %>계속해서 번역 결과:

[`full_messages_for`](https://api.rubyonrails.org/classes/ActiveModel/Errors.html#method-i-full_messages_for) 메서드는 지정된 속성에 대한 사용자 친화적인 오류 메시지 배열을 반환합니다. 해당 속성에 오류가 없으면 배열이 비어 있습니다.

`new` 및 `create` 컨트롤러 액션이 어떻게 작동하는지 다시 살펴보겠습니다:

```ruby
  def new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to @article
    else
      render :new, status: :unprocessable_entity
    end
  end

http://localhost:3000/articles/new을 방문하면 GET /articles/new 요청이 new 액션에 매핑됩니다. new 액션은 @article을 저장하려고 하지 않으므로 유효성 검사가 수행되지 않으며 오류 메시지가 없습니다.

폼을 제출하면 POST /articles 요청이 create 액션에 매핑됩니다. create 액션은 @article을 저장하려고 합니다. 따라서 유효성 검사가 수행되며, 유효성 검사에 실패하면 @article이 저장되지 않고 app/views/articles/new.html.erb가 오류 메시지와 함께 렌더링됩니다.

팁: 유효성 검사에 대해 자세히 알아보려면 Active Record 유효성 검사를 참조하세요. 유효성 검사 오류 메시지에 대해 자세히 알아보려면 Active Record 유효성 검사 § 유효성 검사 오류 작업을 참조하세요.

마무리

이제 http://localhost:3000/articles/new를 방문하여 기사를 만들 수 있습니다. 마지막으로 app/views/articles/index.html.erb의 맨 아래에서 해당 페이지로 링크를 추가해 보겠습니다:

<h1>기사</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
      <%= link_to article.title, article %>
    </li>
  <% end %>
</ul>

<%= link_to "새 기사", new_article_path %>

기사 업데이트하기

CRUD의 “CR"을 다뤘습니다. 이제 "U”(Update)로 넘어가 보겠습니다. 리소스 업데이트는 리소스 생성과 매우 유사합니다. 둘 다 여러 단계 프로세스입니다. 먼저 사용자가 데이터를 편집할 폼을 요청합니다. 그런 다음 사용자가 폼을 제출합니다. 오류가 없으면 리소스가 업데이트됩니다. 그렇지 않으면 오류 메시지와 함께 폼이 다시 표시되고 프로세스가 반복됩니다.

이러한 단계는 일반적으로 컨트롤러의 editupdate 액션에 의해 처리됩니다. app/controllers/articles_controller.rb에 이러한 액션의 일반적인 구현을 추가해 보겠습니다:

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to @article
    else
      render :new, status: :unprocessable_entity
    end
  end

  def edit
    @article = Article.find(params[:id])
  end

  def update
    @article = Article.find(params[:id])

    if @article.update(article_params)
      redirect_to @article
    else
      render :edit, status: :unprocessable_entity
    end
  end

  private
    def article_params
      params.require(:article).permit(:title, :body)
    end
end

editupdate 액션이 newcreate 액션과 어떻게 유사한지 주목하세요.

edit 액션은 데이터베이스에서 기사를 가져와 @article에 저장합니다. 이렇게 하면 폼을 작성할 때 사용할 수 있습니다. 기본적으로 edit 액션은 app/views/articles/edit.html.erb를 렌더링합니다.

update 액션은 (다시) 데이터베이스에서 기사를 가져와 제출된 폼 데이터로 업데이트하려고 시도합니다. 유효성 검사에 실패하지 않고 업데이트가 성공하면 액션이 기사 페이지로 리디렉션합니다. 그렇지 않으면 오류 메시지와 함께 폼을 다시 렌더링하기 위해 app/views/articles/edit.html.erb를 렌더링합니다.

부분 템플릿을 사용하여 뷰 코드 공유하기

edit 폼은 new 폼과 동일할 것입니다. 코드도 동일할 것입니다. 이는 Rails 폼 빌더와 리소스 라우팅 덕분입니다. 폼 빌더는 모델 객체가 이전에 저장되었는지 여부에 따라 폼이 적절한 요청을 하도록 자동으로 구성합니다.

코드가 동일하므로 이를 부분 템플릿이라는 공유 뷰로 추출하겠습니다. app/views/articles/_form.html.erb를 만들고 다음 내용을 추가해 보겠습니다:

<%= form_with model: article do |form| %>
  <div>
    <%= form.label :title %><br>
    <%= form.text_field :title %>
    <% article.errors.full_messages_for(:title).each do |message| %>
      <div><%= message %></div>
    <% end %>
  </div>

  <div>
    <%= form.label :body %><br>
    <%= form.text_area :body %><br>
    <% article.errors.full_messages_for(:body).each do |message| %>
      <div><%= message %></div>
    <% end %>
  </div>

  <div>
    <%= form.submit %>
  </div>
<% end %>

위의 코드는 app/views/articles/new.html.erb의 폼과 동일하지만, @article의 모든 인스턴스가 article으로 바뀌었습니다. 부분 템플릿은 공유 코드이므로 특정 컨트롤러 액션에 의해 설정된 인스턴스 변수에 의존하지 않는 것이 가장 좋습니다. 대신 부분 템플릿에 기사를 로컬 변수로 전달할 것입니다.

app/views/articles/new.html.erb를 업데이트하여 render 헬퍼를 사용하여 부분 템플릿을 사용해 보겠습니다:

<h1>새 기사</h1>

<%= render "form", article: @article %>

참고: 부분 템플릿의 파일 이름은 반드시 밑줄로 시작해야 합니다. 예: _form.html.erb. 그러나 렌더링할 때는 밑줄 없이 참조합니다. 예: render "form".

그리고 이제 매우 유사한 app/views/articles/edit.html.erb를 만들어 보겠습니다:

<h1>기사 편집</h1>

<%= render "form", article: @article %>

팁: 부분 템플릿에 대해 자세히 알아보려면 Rails의 레이아웃 및 렌더링 § 부분 템플릿 사용하기를 참조하세요.

마무리

이제 http://localhost:3000/articles/1/edit를 방문하여 기사를 업데이트할 수 있습니다. 마지막으로 app/views/articles/show.html.erb의 맨 아래에서 편집 페이지로 링크를 추가해 보겠습니다:

<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

<ul>
  <li><%= link_to "편집", edit_article_path(@article) %></li>
</ul>

기사 삭제하기

마지막으로 CRUD의 “D”(Delete)에 도달했습니다. 리소스 삭제는 생성이나 업데이트보다 더 간단한 프로세스입니다. 라우트와 컨트롤러 액션만 필요합니다. 그리고 우리의 리소스 라우팅(resources :articles)은 이미 DELETE /articles/:id 요청을 ArticlesControllerdestroy 액션에 매핑하고 있습니다.

따라서 app/controllers/articles_controller.rbupdate 액션 아래에 destroy 액션을 추가해 보겠습니다:

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to @article
    else
      render :new, status: :unprocessable_entity
    end
  end

  def edit
    @article = Article.find(params[:id])
  end

  def update
    @article = Article.find(params[:id])

    if @article.update(article_params)
      redirect_to @article
    else
      render :edit, status: :unprocessable_entity
    end
  end

  def destroy
    @article = Article.find(params[:id])
    @article.destroy

    redirect_to root_path, status: :see_other
  end

  private
    def article_params
      params.require(:article).permit(:title, :body)
    end
end

destroy 액션은 데이터베이스에서 기사를 가져온 다음 destroy를 호출하여 삭제합니다. 그런 다음 303 See Other 상태 코드로 루트 경로로 리디렉션합니다.

루트 경로로 리디렉션하기로 선택했습니다. 이는 기사에 대한 주요 진입점이기 때문입니다. 다른 상황에서는 articles_path로 리디렉션하는 것이 더 적절할 수 있습니다.

이제 app/views/articles/show.html.erb의 맨 아래에 기사를 삭제할 수 있는 링크를 추가해 보겠습니다:

<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

<ul>
  <li><%= link_to "편집", edit_article_path(@article) %></li>
  <li><%= link_to "삭제", article_path(@article), data: {
                    turbo_method: :delete,
                    turbo_confirm: "정말 삭제하시겠습니까?"
                  } %></li>
</ul>

위의 코드에서는 data 옵션을 사용하여 “삭제” 링크의 data-turbo-methoddata-turbo-confirm HTML 속성을 설정합니다. 이 두 속성은 기본적으로 포함된 Turbo에 연결됩니다. data-turbo-method="delete"는 링크가 GET 요청 대신 DELETE 요청을 하도록 합니다. data-turbo-confirm="정말 삭제하시겠습니까?"는 링크를 클릭할 때 확인 대화 상자가 나타나도록 합니다. 사용자가 대화 상자를 취소하면 요청이 중단됩니다.

이것으로 기사를 나열, 표시, 생성, 업데이트 및 삭제할 수 있습니다! 정말 멋지네요!

두 번째 모델 추가하기

이제 두 번째 모델을 애플리케이션에 추가할 시간입니다. 두 번째 모델은 기사에 대한 댓글을 처리할 것입니다.

모델 생성하기

이전에 Article 모델을 만들 때 사용했던 동일한 생성기를 사용할 것계속해서 번역 결과:

이전에 Article 모델을 만들 때 사용했던 동일한 생성기를 사용할 것입니다. 이번에는 기사에 대한 참조를 보유할 Comment 모델을 만들 것입니다. 터미널에서 다음 명령을 실행하세요:

$ bin/rails generate model Comment commenter:string body:text article:references

이 명령은 네 개의 파일을 생성합니다:

파일 목적
db/migrate/20140120201010createcomments.rb 데이터베이스의 comments 테이블을 생성하는 마이그레이션(타임스탬프는 다를 수 있음)
app/models/comment.rb Comment 모델
test/models/comment_test.rb 댓글 모델에 대한 테스트 도구
test/fixtures/comments.yml 테스트에 사용되는 샘플 댓글

먼저 app/models/comment.rb를 살펴보겠습니다:

class Comment < ApplicationRecord
  belongs_to :article
end

이는 이전에 보았던 Article 모델과 매우 유사합니다. 차이점은 belongs_to :article 줄로, 이는 Active Record 연관관계를 설정합니다. 다음 섹션에서 연관관계에 대해 조금 더 배울 것입니다.

셸 명령에 사용된 (:references) 키워드는 모델에 대한 특수 데이터 유형입니다. 이는 제공된 모델 이름에 _id가 추가된 정수 값을 보유할 수 있는 새 데이터베이스 열을 만듭니다. 이해를 돕기 위해 마이그레이션 실행 후 db/schema.rb 파일을 분석해 보세요.

모델 외에도 Rails는 데이터베이스 테이블을 생성하는 마이그레이션을 만들었습니다:

class CreateComments < ActiveRecord::Migration[7.2]
  def change
    create_table :comments do |t|
      t.string :commenter
      t.text :body
      t.references :article, null: false, foreign_key: true

      t.timestamps
    end
  end
end

t.references 줄은 article_id라는 정수 열을 만들고, 인덱스를 생성하며, articles 테이블의 id 열을 가리키는 외래 키 제약 조건을 추가합니다. 이제 마이그레이션을 실행해 보겠습니다:

$ bin/rails db:migrate

Rails는 아직 실행되지 않은 마이그레이션만 실행하므로 이 경우 다음과 같은 출력만 볼 수 있습니다:

==  CreateComments: migrating =================================================
-- create_table(:comments)
   -> 0.0115s
==  CreateComments: migrated (0.0119s) ========================================

모델 연결하기

Active Record 연관관계를 사용하면 두 모델 간의 관계를 쉽게 선언할 수 있습니다. 댓글과 기사의 경우 다음과 같이 관계를 설명할 수 있습니다:

  • 각 댓글은 하나의 기사에 속합니다.
  • 하나의 기사는 여러 개의 댓글을 가질 수 있습니다.

사실 이는 Rails에서 이 연관관계를 선언하는 구문과 매우 유사합니다. 이미 Comment 모델(app/models/comment.rb)에서 각 댓글이 기사에 속한다는 것을 보았습니다:

class Comment < ApplicationRecord
  belongs_to :article
end

이제 app/models/article.rb에서 연관관계의 다른 쪽을 편집해야 합니다:

class Article < ApplicationRecord
  has_many :comments

  validates :title, presence: true
  validates :body, presence: true, length: { minimum: 10 }
end

이 두 선언을 통해 상당한 자동 동작이 가능해집니다. 예를 들어 @article 인스턴스 변수에 기사가 포함되어 있다면 @article.comments를 사용하여 해당 기사에 속한 모든 댓글을 배열로 가져올 수 있습니다.

팁: Active Record 연관관계에 대한 자세한 내용은 Active Record 연관관계 가이드를 참조하세요.

댓글에 대한 라우트 추가하기

articles 컨트롤러와 마찬가지로 Rails에서 comments를 볼 수 있는 위치를 알려주는 라우트가 필요합니다. config/routes.rb 파일을 다시 열고 다음과 같이 편집하세요:

Rails.application.routes.draw do
  root "articles#index"

  resources :articles do
    resources :comments
  end
end

이렇게 하면 commentsarticles중첩된 리소스로 생성됩니다. 이는 기사와 댓글 간의 계층적 관계를 캡처하는 또 다른 부분입니다.

팁: 라우팅에 대한 자세한 내용은 Rails 라우팅 가이드를 참조하세요.

컨트롤러 생성하기

모델이 준비되었으므로 이에 맞는 컨트롤러를 만들 수 있습니다. 이전에 사용했던 동일한 생성기를 사용할 것입니다:

$ bin/rails generate controller Comments

이 명령은 세 개의 파일과 하나의 빈 디렉토리를 생성합니다:

파일/디렉토리 목적
app/controllers/comments_controller.rb Comments 컨트롤러
app/views/comments/ 컨트롤러의 뷰가 저장되는 곳
test/controllers/commentscontrollertest.rb 컨트롤러에 대한 테스트
app/helpers/comments_helper.rb 뷰 헬퍼 파일

블로그와 마찬가지로 독자들이 기사를 읽은 직후 댓글을 직접 작성할 것이며, 댓글을 작성하고 나면 기사 페이지로 다시 보내져 자신의 댓글이 나열되는 것을 볼 것입니다. 따라서 CommentsController는 댓글을 생성하고 스팸 댓글을 삭제하는 메서드를 제공하는 역할이 있습니다.

먼저 기사 표시 템플릿(app/views/articles/show.html.erb)에 새 댓글을 작성할 수 있도록 연결해 보겠습니다:

<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

<ul>
  <li><%= link_to "편집", edit_article_path(@article) %></li>
  <li><%= link_to "삭제", article_path(@article), data: {
                    turbo_method: :delete,
                    turbo_confirm: "정말 삭제하시겠습니까?"
                  } %></li>
</ul>

<h2>댓글 추가:</h2>
<%= form_with model: [ @article, @article.comments.build ] do |form| %>
  <p>
    <%= form.label :commenter %><br>
    <%= form.text_field :commenter %>
  </p>
  <p>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>

이 코드는 Article 표시 페이지에 새 댓글을 생성하는 폼을 추가합니다. form_with 호출은 배열을 사용하여 중첩된 경로 /articles/1/comments를 생성합니다.

이제 app/controllers/comments_controller.rbcreate 액션을 연결해 보겠습니다:

class CommentsController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.create(comment_params)
    redirect_to article_path(@article)
  end

  private
    def comment_params
      params.require(:comment).permit(:commenter, :body)
    end
end

여기에는 이전 컨트롤러에 비해 약간 더 복잡한 부분이 있습니다. 이는 설정한 중첩 때문입니다. 각 댓글 요청은 댓글이 연결된 기사를 추적해야 하므로, Article 모델의 find 메서드를 호출하여 해당 기사를 가져오는 초기 호출이 필요합니다.

또한 코드는 연관관계에 사용할 수 있는 메서드 중 일부를 활용합니다. @article.commentscreate 메서드를 사용하여 댓글을 생성하고 저장합니다. 이렇게 하면 댓글이 자동으로 해당 기사에 연결됩니다.

새 댓글을 만든 후에는 article_path(@article) 헬퍼를 사용하여 원래 기사로 사용자를 보냅니다. 이미 보았듯이 이는 ArticlesControllershow 액션을 호출하여 show.html.erb 템플릿을 렌더링합니다. 댓글이 표시되어야 하는 곳이 바로 여기이므로 app/views/articles/show.html.erb에 추가해 보겠습니다.

<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

<ul>
  <li><%= link_to "편집", edit_article_path(@article) %></li>
  <li><%= link_to "삭제", article_path(@article), data: {
                    turbo_method: :delete,
                    turbo_confirm: "정말 삭제하시겠습니까?"
                  } %></li>
</ul>

<h2>댓글</h2>
<% @article.comments.each do |comment| %>
  <p>
    <strong>작성자:</strong>
    <%= comment.commenter %>
  </p>

  <p>
    <strong>댓글:</strong>
    <%= comment.body %>
  </p>
<% end %>

<h2>댓글 추가:</h2>
<%= form_with model: [ @article, @article.comments.build ] do |form| %>
  <p>
    <%= form.label :commenter %><br>
    <%= form.text_field :commenter %>
  </p>
  <p>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>

이제 기사와 댓글을 블로그에 추가하고 올바른 위치에 표시할 수 있습니다.

댓글이 있는 기사

리팩토링

이제 기사와 댓글이 작동하므로 app/views/articles/show.html.erb 템플릿을 살펴보겠습니다. 길어지고 어색해졌습니다. 부분 템플릿을 사용하여 정리할 수 있습니다.

부분 템플릿 컬렉션 렌더링하기

먼저 기사에 대한 모든 댓글을 표시하는 댓글 부분 템플릿을 만들겠습니다. app/views/comments/_comment.html.erb 파일을 만들고 다음 내용을 추가하세요:

<p>
  <strong>작성자:</strong>
  <%= comment.commenter %>
</p>

<p>
  <strong>댓글:</strong>
  <%= comment.body %>
</p>

그런 다음 app/views/articles/show.html.erb를 다음과 같이 변경할 수 있습니다:

<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

<ul>
  <li><%= link_to "편집", edit_article_path(@article) %></li>
  <li><%= link_to "삭제", article_path(@article), data: {
                    turbo_method: :delete,
                    turbo_confirm: "정말 삭제하시겠습니까?"
                  } %></li>
</ul>

<h2>댓글</h2>
<%= render @article.comments %>

<h2>댓글 추가:</h2>
<%= form_with model: [ @article, @article.comments.build ] do |form| %>
  <p>
    <%= form.label :commenter %><br>
    <%= form.text_field :commenter %>
  </p>
  <p>
    <%= form.label :body %><br>
    <%= form.text_area :body %>계속해서 번역 결과:

  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>

이렇게 하면 app/views/comments/_comment.html.erb 부분 템플릿이 @article.comments 컬렉션의 각 댓글에 대해 한 번씩 렌더링됩니다. render 메서드가 @article.comments 컬렉션을 반복하면서 각 댓글을 comment라는 로컬 변수에 할당하고, 이 변수는 부분 템플릿에서 사용할 수 있습니다.

부분 템플릿 폼 렌더링하기

새 댓글 섹션도 별도의 부분 템플릿으로 이동해 보겠습니다. 다시 app/views/comments/_form.html.erb 파일을 만들고 다음 내용을 추가하세요:

<%= form_with model: [ @article, @article.comments.build ] do |form| %>
  <p>
    <%= form.label :commenter %><br>
    <%= form.text_field :commenter %>
  </p>
  <p>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>

그런 다음 app/views/articles/show.html.erb를 다음과 같이 변경하세요:

<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

<ul>
  <li><%= link_to "편집", edit_article_path(@article) %></li>
  <li><%= link_to "삭제", article_path(@article), data: {
                    turbo_method: :delete,
                    turbo_confirm: "정말 삭제하시겠습니까?"
                  } %></li>
</ul>

<h2>댓글</h2>
<%= render @article.comments %>

<h2>댓글 추가:</h2>
<%= render 'comments/form' %>

두 번째 render는 렌더링할 부분 템플릿 comments/form을 정의합니다. Rails는 문자열의 슬래시를 감지하고 app/views/comments 디렉토리에 있는 _form.html.erb 파일을 렌더링하려는 것을 알아챕니다.

@article 객체는 부분 템플릿에서 사용할 수 있는 인스턴스 변수로 정의되어 있습니다.

관심사 사용하기

관심사는 큰 컨트롤러 또는 모델을 더 쉽게 이해하고 관리할 수 있게 해줍니다. 이는 또한 여러 모델(또는 컨트롤러)이 동일한 관심사를 공유할 때 재사용성의 이점이 있습니다. 관심사는 모델 또는 컨트롤러의 책임을 나타내는 메서드가 포함된 모듈로 구현됩니다. 다른 언어에서는 이러한 모듈을 믹스인이라고 합니다.

컨트롤러 또는 모델에서 모듈을 사용하는 것과 동일한 방식으로 관심사를 사용할 수 있습니다. rails new blog로 앱을 처음 만들 때 app/ 내에 두 개의 폴더가 생성되었습니다:

app/controllers/concerns
app/models/concerns

아래 예에서는 블로그에 관심사를 사용하여 구현할 수 있는 새로운 기능을 소개하겠습니다. 그런 다음 관심사를 만들고 코드를 리팩토링하여 DRY(Don’t Repeat Yourself)하고 유지 관리 가능한 코드를 만들 것입니다.

블로그 기사는 다양한 상태를 가질 수 있습니다. 예를 들어 모든 사람이 볼 수 있는 public 상태, 작성자만 볼 수 있는 private 상태, 숨겨져 있지만 여전히 검색할 수 있는 archived 상태 등이 있을 수 있습니다. 댓글도 숨겨져 있거나 표시될 수 있습니다. 이는 각 모델의 status 열로 나타낼 수 있습니다.

먼저 ArticlesCommentsstatus를 추가하는 다음 마이그레이션을 실행해 보겠습니다:

$ bin/rails generate migration AddStatusToArticles status:string
$ bin/rails generate migration AddStatusToComments status:string

그리고 생성된 마이그레이션을 데이터베이스에 적용합니다:

$ bin/rails db:migrate

기존 기사와 댓글의 상태를 선택하려면 생성된 마이그레이션 파일에 default: "public" 옵션을 추가하여 기본값을 설정하고 마이그레이션을 다시 실행하거나, rails 콘솔에서 Article.update_all(status: "public")Comment.update_all(status: "public")을 호출할 수 있습니다.

팁: 마이그레이션에 대한 자세한 내용은 Active Record 마이그레이션을 참조하세요.

또한 강력한 매개변수에 :status 키를 허용해야 합니다. app/controllers/articles_controller.rb에서:

  private
    def article_params
      params.require(:article).permit(:title, :body, :status)
    end

그리고 app/controllers/comments_controller.rb에서:

  private
    def comment_params
      params.require(:comment).permit(:commenter, :body, :status)
    end

Article 모델에서 bin/rails db:migrate 명령을 실행하여 status 열을 추가한 후에는 다음을 추가할 수 있습니다:

class Article < ApplicationRecord
  has_many :comments

  validates :title, presence: true
  validates :body, presence: true, length: { minimum: 10 }

  VALID_STATUSES = ['public', 'private', 'archived']

  validates :status, inclusion: { in: VALID_STATUSES }

  def archived?
    status == 'archived'
  end
end

그리고 Comment 모델에서:

class Comment < ApplicationRecord
  belongs_to :article

  VALID_STATUSES = ['public', 'private', 'archived']

  validates :status, inclusion: { in: VALID_STATUSES }

  def archived?
    status == 'archived'
  end
end

그런 다음 app/views/articles/index.html.erbindex 액션 템플릿에서 archived? 메서드를 사용하여 보관된 기사가 표시되지 않도록 할 수 있습니다:

<h1>기사</h1>

<ul>
  <% @articles.each do |article| %>
    <% unless article.archived? %>
      <li>
        <%= link_to article.title, article %>
      </li>
    <% end %>
  <% end %>
</ul>

<%= link_to "새 기사", new_article_path %>

마찬가지로 댓글 부분 템플릿(app/views/comments/_comment.html.erb)에서 archived? 메서드를 사용하여 보관된 댓글이 표시되지 않도록 할 수 있습니다:

<% unless comment.archived? %>
  <p>
    <strong>작성자:</strong>
    <%= comment.commenter %>
  </p>

  <p>
    <strong>댓글:</strong>
    <%= comment.body %>
  </p>
<% end %>

그러나 모델을 다시 살펴보면 로직이 중복되어 있음을 알 수 있습니다. 향후 블로그 기능을 확장하여 비공개 메시지를 포함하는 경우 이 로직을 다시 중복해야 할 수 있습니다. 이 경우 관심사가 유용합니다.

관심사는 모델의 책임 중 초점이 맞춰진 부분만 담당합니다. 관심사의 메서드는 모두 모델의 가시성과 관련됩니다. 새 관심사(모듈)를 Visible이라고 부르겠습니다. app/models/concerns 내에 visible.rb라는 새 파일을 만들고 모델에 중복된 상태 메서드를 저장할 수 있습니다.

app/models/concerns/visible.rb

module Visible
  def archived?
    status == 'archived'
  end
end

관심사에 상태 유효성 검사를 추가할 수 있지만, 이는 약간 더 복잡합니다. 유효성 검사는 클래스 수준에서 호출되는 메서드이기 때문입니다. ActiveSupport::Concern(API 가이드)을 사용하면 이를 더 쉽게 포함할 수 있습니다:

module Visible
  extend ActiveSupport::Concern

  VALID_STATUSES = ['public', 'private', 'archived']

  included do
    validates :status, inclusion: { in: VALID_STATUSES }
  end

  def archived?
    status == 'archived'
  end
end

이제 각 모델에서 중복된 로직을 제거하고 새 Visible 모듈을 대신 포함할 수 있습니다:

app/models/article.rb에서:

class Article < ApplicationRecord
  include Visible

  has_many :comments

  validates :title, presence: true
  validates :body, presence: true, length: { minimum: 10 }
end

그리고 app/models/comment.rb에서:

class Comment < ApplicationRecord
  include Visible

  belongs_to :article
end

클래스 메서드도 관심사에 추가할 수 있습니다. 메인 페이지에 공개 기사 또는 댓글 수를 표시하려면 Visible 모듈에 클래스 메서드를 추가할 수 있습니다:

module Visible
  extend ActiveSupport::Concern

  VALID_STATUSES = ['public', 'private', 'archived']

  included do
    validates :status, inclusion: { in: VALID_STATUSES }
  end

  class_methods do
    def public_count
      where(status: 'public').count
    end
  end

  def archived?
    status == 'archived'
  end
end

그런 다음 뷰에서 클래스 메서드처럼 호출할 수 있습니다:

<h1>기사</h1>

우리 블로그에는 <%= Article.public_count %> 개의 공개 기사가 있습니다!

<ul>
  <% @articles.each do |article| %>
    <% unless article.archived? %>
      <li>
        <%= link_to article.title, article %>
      </li>
    <% end %>
  <% end %>
</ul>

<%= link_to "새 기사", new_article_path %>

마지막으로 폼에 선택 상자를 추가하고 사용자가 새 기사를 만들거나 새 댓글을 작성할 때 상태를 선택할 수 있도록 하겠습니다. 또한 객체의 상태를 선택하거나 아직 설정되지 않은 경우 기본값을 public으로 선택할 수 있습니다. app/views/articles/_form.html.erb에서:

<div>
  <%= form.label :status %><br>
  <%= form.select :status, Visible::VALID_STATUSES, selected: article.status || 'public' %>
</div>

그리고 app/views/comments/_form.html.erb에서:

<p>
  <%= form.label :status %><br>
  <%= form.select :status, Visible::VALID_STATUSES, selected: 'public' %>
</p>

댓글 삭제

블로그의 또 다른 중요한 기능은 스팸 댓글을 삭제할 수 있는 기능입니다. 이를 위해 뷰에 링크를 추가하고 CommentsControllerdestroy 액션을 구현해야 합니다.

먼저 app/views/comments/_comment.html.erb 부분 템플릿에 삭제 링크를 추가해 보겠습니다:

<% unless comment.archived? %>
  <p>
    <strong>작성자:</strong>
    <%= comment.commenter %>
  </p>

  <p>
    <strong>댓글:</strong>
    <%= comment.body %>
  </p>

  <p>
    <%= link_to "댓글 삭제", [comment.article, comment], data: {
                  turbo_method: :delete,
                  turbo_confirm: "정말 삭제하시겠습니까?"
                } %>
  </p>
<% end %>

“댓글 삭제” 링크를 클릭하면 DELETE /articles/:article_id/comments/:idCommentsController로 전송되며, 여기서 삭제할 계속해서 번역 결과:

“댓글 삭제” 링크를 클릭하면 DELETE /articles/:article_id/comments/:idCommentsController로 전송되며, 여기서 삭제할 댓글을 찾아 삭제할 수 있습니다. 따라서 app/controllers/comments_controller.rbdestroy 액션을 추가해 보겠습니다:

class CommentsController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.create(comment_params)
    redirect_to article_path(@article)
  end

  def destroy
    @article = Article.find(params[:article_id])
    @comment = @article.comments.find(params[:id])
    @comment.destroy
    redirect_to article_path(@article), status: :see_other
  end

  private
    def comment_params
      params.require(:comment).permit(:commenter, :body, :status)
    end
end

destroy 액션은 현재 보고 있는 기사를 찾고, @article.comments 컬렉션에서 댓글을 찾은 다음 이를 데이터베이스에서 삭제하고 기사 표시 페이지로 리디렉션합니다.

관련 객체 삭제하기

기사를 삭제하면 관련 댓글도 삭제해야 합니다. 그렇지 않으면 데이터베이스에 그대로 남아 있을 것입니다. Rails에서는 연관관계의 dependent 옵션을 사용하여 이를 달성할 수 있습니다. app/models/article.rbArticle 모델을 다음과 같이 수정하세요:

class Article < ApplicationRecord
  include Visible

  has_many :comments, dependent: :destroy

  validates :title, presence: true
  validates :body, presence: true, length: { minimum: 10 }
end

보안

기본 인증

블로그를 온라인에 게시하면 누구나 기사를 추가, 편집, 삭제하거나 댓글을 삭제할 수 있습니다.

Rails는 이 상황에 잘 맞는 HTTP 인증 시스템을 제공합니다.

ArticlesController에서 사용자가 인증되지 않은 경우 다양한 액션에 대한 액세스를 차단하는 방법이 필요합니다. 여기에서 Rails의 http_basic_authenticate_with 메서드를 사용할 수 있습니다. 이 메서드를 통해 요청된 액션에 대한 액세스를 허용할 수 있습니다.

인증 시스템을 사용하려면 app/controllers/articles_controller.rbArticlesController 맨 위에 지정해야 합니다. 이 경우 indexshow 액션을 제외한 모든 액션에 대해 사용자를 인증하려고 합니다:

class ArticlesController < ApplicationController
  http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]

  def index
    @articles = Article.all
  end

  # 간단히 표시하기 위해 생략
end

또한 인증된 사용자만 댓글을 삭제할 수 있도록 하려면 app/controllers/comments_controller.rbCommentsController에서 다음과 같이 작성합니다:

class CommentsController < ApplicationController
  http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy

  def create
    @article = Article.find(params[:article_id])
    # ...
  end

  # 간단히 표시하기 위해 생략
end

이제 새 기사를 만들려고 하면 기본 HTTP 인증 챌린지가 표시됩니다:

기본 HTTP 인증 챌린지

올바른 사용자 이름과 비밀번호를 입력하면 다른 사용자 이름과 비밀번호가 요구되거나 브라우저가 닫힐 때까지 인증된 상태로 유지됩니다.

Rails 애플리케이션에는 다른 인증 방법도 사용할 수 있습니다. 두 가지 인기 있는 Rails 인증 추가 기능은 Devise Rails 엔진과 Authlogic gem입니다.

기타 보안 고려 사항

보안, 특히 웹 애플리케이션에서의 보안은 광범위하고 자세한 영역입니다. Rails 애플리케이션의 보안에 대한 자세한 내용은 Ruby on Rails 보안 가이드에서 다룹니다.

다음은 무엇인가요?

이제 첫 번째 Rails 애플리케이션을 보았으니 자유롭게 업데이트하고 실험해 볼 수 있습니다.

도움이 필요할 때 주저 없이 이러한 지원 리소스를 참조하세요:

구성 문제

Rails로 작업할 때 가장 쉬운 방법은 모든 외부 데이터를 UTF-8로 저장하는 것입니다. 그렇지 않으면 Ruby 라이브러리와 Rails가 네이티브 데이터를 UTF-8로 변환할 수 있지만, 이것이 항상 안정적으로 작동하는 것은 아니므로 모든 외부 데이터를 UTF-8로 보장하는 것이 더 좋습니다.

이 부분에서 실수를 했다면 가장 일반적인 증상은 브라우저에 물음표가 있는 검은 다이아몬드가 나타나는 것입니다. 다른 일반적인 증상은 “ü"와 같은 문자가 "ü"대신 나타나는 것입니다. Rails는 자동으로 감지하고 수정할 수 있는 일반적인 원인을 해결하기 위한 많은 내부 단계를 수행합니다. 그러나 UTF-8이 아닌 외부 데이터가 있는 경우 Rails가 자동으로 감지하고 수정할 수 없는 이러한 문제가 발생할 수 있습니다.

데이터의 UTF-8이 아닌 두 가지 매우 일반적인 소스:

  • 텍스트 편집기: 대부분의 텍스트 편집기(TextMate 등)는 기본적으로 파일을 UTF-8로 저장합니다. 텍스트 편집기가 그렇지 않은 경우 사용자가 템플릿에 입력한 특수 문자(예: é)가 브라우저에 물음표가 있는 다이아몬드로 나타날 수 있습니다. i18n 번역 파일에도 이 문제가 적용됩니다. UTF-8을 기본값으로 사용하지 않는 대부분의 편집기(일부 Dreamweaver 버전 등)에서는 기본값을 UTF-8로 변경하는 방법을 제공합니다.
  • 데이터베이스: Rails는 데이터베이스에서 데이터를 UTF-8로 변환하는 기능을 기본적으로 제공합니다. 그러나 데이터베이스가 내부적으로 UTF-8을 사용하지 않는 경우 사용자가 입력한 모든 문자(러시아어, 히브리어 또는 일본어 문자 등)를 저장할 수 없습니다. 예를 들어 데이터베이스가 내부적으로 Latin-1을 사용하고 사용자가 러시아어, 히브리어 또는 일본어 문자를 입력하면 데이터베이스에 들어가는 순간 해당 데이터가 영원히 손실됩니다. 가능하다면 데이터베이스의 내부 저장소로 UTF-8을 사용하세요.