1 가이드 전제 조건
이 가이드는 처음부터 Rails 애플리케이션을 만들고자 하는 초보자를 위해 작성되었습니다. Rails에 대한 사전 경험이 없다고 가정합니다.
Rails는 Ruby 프로그래밍 언어로 작성된 웹 애플리케이션 프레임워크입니다. Ruby에 대한 사전 경험이 없다면, Rails를 바로 시작하는 것은 매우 가파른 학습 곡선을 경험하게 될 것입니다. Ruby 학습을 위한 엄선된 온라인 리소스 목록이 여러 개 있습니다:
일부 리소스들은 여전히 훌륭하지만 오래된 Ruby 버전을 다루고 있어, Rails로 일상적인 개발을 할 때 보게 될 일부 구문이 포함되어 있지 않을 수 있다는 점을 유의하세요.
2 Rails란 무엇인가?
Rails는 Ruby 프로그래밍 언어로 작성된 웹 애플리케이션 개발 프레임워크입니다. 모든 개발자가 시작하는데 필요한 것들에 대한 가정을 만들어 웹 애플리케이션 프로그래밍을 더 쉽게 만들도록 설계되었습니다. 다른 많은 언어와 프레임워크에 비해 적은 코드로 더 많은 것을 할 수 있게 해줍니다. 경험 많은 Rails 개발자들은 또한 웹 애플리케이션 개발이 더 재미있어진다고 말합니다.
Rails는 독선적인(opinionated) 소프트웨어입니다. 일을 처리하는 "최선의" 방법이 있다고 가정하고, 그 방법을 권장하도록 - 그리고 경우에 따라서는 대안을 discourage하도록 - 설계되어 있습니다. "Rails 방식"을 배우면 생산성이 크게 향상되는 것을 발견하게 될 것입니다. 만약 이전의 습관을
Rails 철학은 두 가지 주요 원칙을 포함합니다:
- Don't Repeat Yourself: DRY는 "모든 지식은 시스템 내에서 단일하고, 모호하지 않으며, 신뢰할 수 있는 표현을 가져야 한다"는 소프트웨어 개발 원칙입니다. 동일한 정보를 반복해서 작성하지 않음으로써, 우리의 코드는 더 유지보수가 쉽고, 확장 가능하며, 버그가 적어집니다.
- Convention Over Configuration: Rails는 웹 애플리케이션에서 많은 것들을 수행하는 최선의 방법에 대한 의견을 가지고 있으며, 끝없는 설정 파일을 통해 세부사항을 지정하도록 요구하는 대신 이러한 관례들을 기본값으로 사용합니다.
3 새로운 Rails 프로젝트 생성하기
사전 구성된 Dev Container 개발 환경으로 새로운 Rails 앱을 생성할 수 있습니다. 이것이 Rails를 시작하는 가장 빠른 방법입니다. 자세한 내용은 Getting Started with Dev Containers를 참조하세요.
이 가이드를 읽는 가장 좋은 방법은 단계별로 따라하는 것입니다. 모든 단계는 이 예제 애플리케이션을 실행하는데 필수적이며 추가 코드나 단계는 필요하지 않습니다.
이 가이드를 따라가면서, blog
라는 (매우) 간단한 웹로그인 Rails 프로젝트를 만들게 됩니다. 애플리케이션 구축을 시작하기 전에, Rails 자체가 설치되어 있는지 확인해야 합니다.
아래 예제들은 UNIX 계열 OS의 터미널 프롬프트를 나타내기 위해 $
를 사용합니다만, 다르게 커스터마이즈되어 있을 수 있습니다. Windows를 사용하고 있다면, 프롬프트는 C:\source_code>
와 같은 형태일 것입니다.
3.1 Rails 설치하기
Rails를 설치하기 전에, 시스템에 필요한 prerequisites가 올바르게 설치되어 있는지 확인해야 합니다. 다음 항목들이 포함됩니다:
- Ruby
- SQLite3
3.1.1 Ruby 설치하기
커맨드 라인 프롬프트를 엽니다. macOS에서는 Terminal.app을 열고, Windows에서는 시작 메뉴에서 "실행"을 선택하고 cmd.exe
를 입력합니다. 달러 기호 $
로 시작하는 모든 명령어는 커맨드 라인에서 실행되어야 합니다. 현재 버전의 Ruby가 설치되어 있는지 확인하세요:
$ ruby --version
ruby 3.2.0
Rails는 Ruby 버전 3.2.0 이상이 필요합니다. 최신 Ruby 버전을 사용하는 것이 권장됩니다. 만약 반환된 버전 번호가 이보다 낮다면(예: 2.3.7이나 1.8.7), 새로운 Ruby를 설치해야 합니다.
Windows에서 Rails를 설치하려면, 먼저 Ruby Installer를 설치해야 합니다.
대부분의 운영체제에서 사용할 수 있는 더 많은 설치 방법을 알아보려면 ruby-lang.org를 참고하세요.
3.1.2 SQLite3 설치하기
SQLite3 데이터베이스도 설치해야 합니다. 많은 인기있는 UNIX 계열 운영체제들은 적절한 버전의 SQLite3가 함께 제공됩니다. 다른 운영체제의 경우 SQLite3 웹사이트에서 설치 방법을 확인할 수 있습니다.
올바르게 설치되었고 PATH
에 포함되어 있는지 확인하세요:
$ sqlite3 --version
이 프로그램은 자신의 version을 보고해야 합니다.
3.1.3 Rails 설치하기
Rails를 설치하려면 RubyGems가 제공하는 gem install
명령어를 사용하세요:
$ gem install rails
모든 것이 올바르게 설치되었는지 확인하기 위해, 새로운 터미널에서 다음을 실행할 수 있어야 합니다:
$ rails --version
Rails 8.1.0
Rails 8.1.0과 같은 내용이 표시되면, 계속 진행할 준비가 된 것입니다.
3.2 Blog 애플리케이션 생성하기
Rails에는 특정 작업을 시작하는데 필요한 모든 것을 생성해서 개발을 더 쉽게 만들어주도록 설계된 generators라고 하는 여러 스크립트들이 함께 제공됩니다. 그 중 하나가 new application generator인데, 이는 직접 작성할 필요 없이 새로운 Rails 애플리케이션의 기초를 제공합니다.
이 generator를 사용하려면, 터미널을 열고 파일을 생성할 권한이 있는 디렉토리로 이동한 다음 다음 명령을 실행하세요:
$ rails new blog
위 번역은 bash 명령어이므로 그대로 유지됩니다.
이것은 blog
디렉토리에 Blog라는 Rails 애플리케이션을 생성하고, bundle install
을 사용하여 Gemfile
에 이미 명시된 gem 의존성들을 설치할 것입니다.
rails new --help
를 실행하면 Rails 애플리케이션 생성기가 받아들이는 모든 명령행 옵션들을 볼 수 있습니다.
blog 애플리케이션을 생성한 후, 해당 폴더로 이동하세요:
$ cd blog
blog
디렉토리에는 Rails 애플리케이션의 구조를 이루는 여러 생성된 파일과 폴더들이 있습니다. 이 튜토리얼의 대부분의 작업은 app
폴더에서 이루어질 것이지만, Rails가 기본적으로 생성하는 각 파일과 폴더의 기능에 대한 기본적인 설명은 다음과 같습니다:
파일/폴더 | 용도 |
---|---|
app/ | 애플리케이션의 controllers, models, views, helpers, mailers, jobs 및 assets를 포함합니다. 이 가이드의 나머지 부분에서는 이 폴더에 초점을 맞출 것입니다. |
bin/ | 애플리케이션을 시작하는 rails 스크립트와 애플리케이션을 설정, 업데이트, 배포 또는 실행하는 데 사용하는 다른 스크립트들이 포함됩니다. |
config/ | 애플리케이션의 routes, database 및 기타 설정이 포함됩니다. 이는 Configuring Rails Applications에서 더 자세히 다룹니다. |
config.ru | 애플리케이션을 시작하는 데 사용되는 Rack 기반 서버를 위한 Rack 설정입니다. Rack에 대한 자세한 내용은 Rack website를 참조하세요. |
db/ | 현재 데이터베이스 스키마와 데이터베이스 migrations를 포함합니다. |
Dockerfile | Docker를 위한 설정 파일입니다. |
Gemfile Gemfile.lock |
이 파일들을 통해 Rails 애플리케이션에 필요한 gem 의존성을 지정할 수 있습니다. 이 파일들은 Bundler gem에 의해 사용됩니다. Bundler에 대한 자세한 내용은 Bundler website를 참조하세요. |
lib/ | 애플리케이션의 확장 모듈입니다. |
log/ | 애플리케이션 로그 파일입니다. |
public/ | 정적 파일과 컴파일된 assets를 포함합니다. 앱이 실행 중일 때 이 디렉토리는 있는 그대로 노출됩니다. |
Rakefile | 이 파일은 명령줄에서 실행할 수 있는 작업을 찾아서 로드합니다. 작업 정의는 Rails의 컴포넌트 전체에 정의되어 있습니다. Rakefile 을 직접 수정하는 대신, 애플리케이션의 lib/tasks 디렉토리에 파일을 추가하여 자신만의 작업을 추가해야 합니다. |
README.md | 애플리케이션에 대한 간단한 설명서입니다. 이 파일을 편집하여 다른 사람들에게 애플리케이션이 무엇을 하는지, 어떻게 설정하는지 등을 알려주어야 합니다. |
script/ | 일회성 또는 범용 scripts와 benchmarks를 포함합니다. |
storage/ | Disk Service를 위한 Active Storage 파일입니다. 이는 Active Storage Overview에서 다룹니다. |
test/ | 단위 테스트, fixtures 및 기타 테스트 도구입니다. 이는 Testing Rails Applications에서 다룹니다. |
tmp/ | 임시 파일(캐시 및 pid 파일과 같은)입니다. |
vendor/ | 모든 third-party 코드를 위한 장소입니다. 일반적인 Rails 애플리케이션에서 이는 vendored gems를 포함합니다. |
.dockerignore | 이 파일은 Docker에게 컨테이너에 복사하지 말아야 할 파일들을 알려줍니다. |
.gitattributes | 이 파일은 git 저장소의 특정 경로에 대한 메타데이터를 정의합니다. 이 메타데이터는 git과 다른 도구들이 동작을 향상시키는 데 사용될 수 있습니다. 자세한 내용은 gitattributes documentation를 참조하세요. |
.github/ | GitHub 특정 파일들을 포함합니다. |
.gitignore | 이 파일은 git에게 무시해야 할 파일(또는 패턴)을 알려줍니다. 파일 무시에 대한 자세한 내용은 GitHub - Ignoring files를 참조하세요. |
.rubocop.yml | 이 파일은 RuboCop의 설정을 포함합니다. |
.ruby-version | 이 파일은 기본 Ruby 버전을 포함합니다. |
4 Hello, Rails!
우선, 화면에 빠르게 텍스트를 표시해보겠습니다. 이를 위해서는 Rails 애플리케이션 서버를 실행해야 합니다.
4.1 웹 서버 시작하기
지금 이미 동작하는 Rails 애플리케이션이 있습니다. 이를 확인하려면 개발 머신에서 웹 서버를 시작해야 합니다. blog
디렉토리에서 다음 명령어를 실행하여 시작할 수 있습니다:
$ bin/rails server
Windows를 사용하는 경우, bin
폴더 아래의 스크립트를 Ruby 인터프리터에 직접 전달해야 합니다. 예: ruby bin\rails server
.
JavaScript 애셋 압축을 위해서는 시스템에 JavaScript 런타임이 설치되어 있어야 합니다. 런타임이 없는 경우 애셋 압축 도중 execjs
에러가 발생할 것입니다. 일반적으로 macOS와 Windows는 JavaScript 런타임이 기본적으로 설치되어 있습니다. JRuby 사용자의 경우 therubyrhino
가 권장 런타임이며, JRuby에서 생성된 앱의 Gemfile
에 기본적으로 추가됩니다. 지원되는 모든 런타임은 ExecJS에서 확인할 수 있습니다.
이렇게 하면 Rails의 기본 웹 서버인 Puma가 시작됩니다. 애플리케이션이 동작하는 것을 확인하려면, 브라우저 창을 열고 http://localhost:3000로 이동하세요. Rails 기본 정보 페이지가 표시될 것입니다:
웹 서버를 중지하고 싶을 때는, 서버가 실행 중인 터미널 창에서 Ctrl+C를 누르세요. 개발 환경에서는 일반적으로 서버를 재시작할 필요가 없습니다. 파일을 수정하면 서버가 자동으로 변경 사항을 감지합니다.
Rails 시작 페이지는 새로운 Rails 애플리케이션의 스모크 테스트 입니다: 페이지를 제공하기 위한 소프트웨어가 올바르게 구성되어 있는지 확인합니다.
4.2 "Hello"라고 말하는 Rails
Rails가 "Hello"라고 말하게 하려면 최소한 route, action이 있는 controller, 그리고 view가 필요합니다. route는 request를 controller action에 매핑합니다. controller action은 request를 처리하는데 필요한 작업을 수행하고, view를 위한 데이터를 준비합니다. view는 데이터를 원하는 형식으로 표시합니다.
구현 측면에서: Routes는 Ruby DSL(Domain-Specific Language)로 작성된 규칙입니다. Controllers는 Ruby 클래스이며, 그들의 public 메서드가 actions입니다. 그리고 views는 일반적으로 HTML과 Ruby가 혼합된 템플릿입니다.
먼저 config/routes.rb
파일의 Rails.application.routes.draw
블록 맨 위에 route를 추가해보겠습니다:
Rails.application.routes.draw do
get "/articles", to: "articles#index"
# 이 파일에서 사용 가능한 DSL에 대한 자세한 내용은 https://guides.rubyonrails.org/routing.html 을 참조하세요
end
위의 라우트는 GET /articles
요청이 ArticlesController
의 index
액션에 매핑된다는 것을 선언합니다.
ArticlesController
와 그것의 index
액션을 생성하기 위해, controller generator를 실행할 것입니다 (우리는 이미 적절한 라우트를 가지고 있기 때문에 --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
이들 중 가장 중요한 것은 controller 파일인 app/controllers/articles_controller.rb
입니다. 이 파일을 살펴보겠습니다:
class ArticlesController < ApplicationController
def index
end
end
index
액션은 비어있습니다. 액션이 view를 명시적으로 렌더링하지 않을 때(또는 다른 HTTP 응답을 트리거하지 않을 때), Rails는 컨트롤러와 액션 이름이 일치하는 view를 자동으로 렌더링합니다. 이것이 바로 설정보다 관습입니다! View들은 app/views
디렉토리에 위치합니다. 따라서 index
액션은 기본적으로 app/views/articles/index.html.erb
를 렌더링할 것입니다.
app/views/articles/index.html.erb
를 열고, 내용을 다음과 같이 교체해보겠습니다:
<h1>안녕하세요, Rails!</h1>
이전에 controller generator를 실행하기 위해 web server를 중지했다면, bin/rails server
로 다시 시작하세요. 이제 http://localhost:3000/articles를 방문하면 우리가 작성한 텍스트가 표시됩니다!
4.3 애플리케이션 홈 페이지 설정하기
현재, http://localhost:3000은 여전히 Ruby on Rails 로고가 있는 페이지를 보여줍니다. http://localhost:3000에서도 우리의 "Hello, Rails!" 텍스트를 표시해보겠습니다. 이를 위해 애플리케이션의 root path를 적절한 controller와 action에 매핑하는 route를 추가할 것입니다.
config/routes.rb
를 열고, Rails.application.routes.draw
블록의 맨 위에 다음과 같은 root
route를 추가해봅시다:
Rails.application.routes.draw do
root "articles#index"
get "/articles", to: "articles#index"
end
http://localhost:3000에 접속하면 "Hello, Rails!" 텍스트를 볼 수 있습니다. 이는 root
라우트가 ArticlesController
의 index
액션에도 매핑되어 있다는 것을 확인해줍니다.
라우팅에 대해 더 자세히 알아보려면 외부에서 보는 Rails 라우팅을 참조하세요.
5 Autoloading
Rails 애플리케이션은 애플리케이션 코드를 로드하기 위해 require
를 사용하지 않습니다.
ArticlesController
가 ApplicationController
를 상속받고 있지만 app/controllers/articles_controller.rb
에는 다음과 같은 내용이 없다는 것을 알 수 있습니다.
require "application_controller" # 이렇게 하지 마세요.
app
내의 애플리케이션 클래스와 모듈은 어디서나 사용할 수 있으므로 require
로 로드할 필요가 없으며 그래서도 안 됩니다. 이 기능을 autoloading이라고 하며, Autoloading and Reloading Constants에서 자세히 알아볼 수 있습니다.
require
가 필요한 경우는 다음 두 가지입니다:
lib
디렉토리 아래의 파일들을 로드할 때Gemfile
에require: false
가 설정된 gem 의존성을 로드할 때
6 MVC와 당신
지금까지 routes, controllers, actions, views에 대해 논의했습니다. 이들은 모두 MVC (Model-View-Controller) 패턴을 따르는 웹 애플리케이션의 전형적인 구성 요소입니다. MVC는 애플리케이션의 책임을 나누어 이해하기 쉽게 만드는 디자인 패턴입니다. Rails는 이 디자인 패턴을 관례적으로 따릅니다.
controller와 view를 다뤄봤으니, 이제 다음 구성 요소인 model을 생성해보겠습니다.
6.1 Model 생성하기
model은 데이터를 표현하는데 사용되는 Ruby 클래스입니다. 또한 model은 Active Record라고 불리는 Rails의 기능을 통해 애플리케이션의 데이터베이스와 상호작용할 수 있습니다.
model을 정의하기 위해, 우리는 model generator를 사용할 것입니다:
$ bin/rails generate model Article title:string body:text
Model 이름은 단수형입니다. 인스턴스화된 model이 단일 데이터 레코드를 나타내기 때문입니다. 이 규칙을 기억하기 쉽게 하기 위해, model의 생성자를 어떻게 호출할지 생각해보세요: Articles.new(...)
가 아닌 Article.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
위와 같이 파일들이 생성됩니다. 가장 중요한 파일은 migration과 모델 파일입니다.
우리가 초점을 맞출 두 파일은 migration 파일(db/migrate/<timestamp>_create_articles.rb
)과 model 파일(app/models/article.rb
)입니다.
6.2 데이터베이스 마이그레이션
Migration은 애플리케이션의 데이터베이스 구조를 변경하는 데 사용됩니다. Rails 애플리케이션에서 migration은 데이터베이스에 구애받지 않도록 Ruby로 작성됩니다.
새로운 migration 파일의 내용을 살펴보겠습니다:
class CreateArticles < ActiveRecord::Migration[8.1]
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
블록 내부에는 title
과 body
두 개의 컬럼이 정의되어 있습니다. 이것들은 generate 명령어(bin/rails generate model Article title:string body:text
)에 포함시켰기 때문에 generator에 의해 추가되었습니다.
블록의 마지막 줄에는 t.timestamps
호출이 있습니다. 이 메서드는 created_at
과 updated_at
이라는 두 개의 추가 컬럼을 정의합니다. 앞으로 보게 되겠지만, Rails는 모델 객체를 생성하거나 업데이트할 때 이 값들을 자동으로 관리합니다.
다음 명령어로 migration을 실행해봅시다:
$ bin/rails db:migrate
명령어를 실행하면 테이블이 생성되었다는 내용이 출력됩니다:
== CreateArticles: 마이그레이션 중 ===================================
-- create_table(:articles)
-> 0.0018s
== CreateArticles: 마이그레이션 완료 (0.0018s) ==========================
migrations에 대해 더 알아보려면 Active Record Migrations를 참조하세요.
이제 우리는 모델을 사용하여 테이블과 상호작용할 수 있습니다.
6.3 모델을 사용하여 데이터베이스와 상호작용하기
모델을 가지고 좀 더 실험해보기 위해, console이라고 불리는 Rails의 기능을 사용해보겠습니다. console은 irb
와 비슷한 대화형 코딩 환경이지만, Rails와 우리의 애플리케이션 코드를 자동으로 불러온다는 점이 다릅니다.
다음 명령어로 console을 실행해봅시다:
$ bin/rails console
rails console 프롬프트가 다음과 같이 보일 것입니다:
개발 환경 로딩 중 (Rails 8.1.0)
blog(dev)>
이 프롬프트에서 새로운 Article
객체를 초기화할 수 있습니다:
blog(dev)> article = Article.new(title: "Hello Rails", body: "I am on Rails!")
이 객체를 단지 초기화했다는 점을 주목하는 것이 중요합니다. 이 객체는 아직 데이터베이스에 전혀 저장되지 않았습니다. 현재는 콘솔에서만 사용할 수 있습니다. 객체를 데이터베이스에 저장하려면 save
를 호출해야 합니다:
blog(dev)> article.save
(0.1ms) 트랜잭션 시작
Article Create (0.4ms) "articles" 테이블에 ("title", "body", "created_at", "updated_at") 값을 (?, ?, ?, ?) 로 INSERT [["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) 트랜잭션 커밋
=> true
위의 출력에는 INSERT INTO "articles" ...
데이터베이스 쿼리가 표시됩니다. 이는 article이 우리의 테이블에 삽입되었음을 나타냅니다. 그리고 article
객체를 다시 살펴보면, 흥미로운 일이 일어났음을 알 수 있습니다:
blog(dev)> 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_at
, updated_at
속성들이 이제 설정되었습니다.
Rails는 우리가 객체를 저장할 때 이것들을 자동으로 처리했습니다.
데이터베이스에서 이 article을 가져오고 싶을 때는, model에서 find
를 호출하고 id
를 인자로 전달하면 됩니다:
blog(dev)> Article.find(1)
=> #<Article id: 1, title: "Hello Rails", body: "Rails 시작하기!", created_at: "2020-01-18 23:47:30", updated_at: "2020-01-18 23:47:30">
그리고 database에서 모든 article을 가져오고 싶을 때는 model에서 all
을 호출하면 됩니다:
blog(dev)> Article.all
=> #<ActiveRecord::Relation [#<Article id: 1, title: "Hello Rails", body: "Rails에 있어요!", created_at: "2020-01-18 23:47:30", updated_at: "2020-01-18 23:47:30">]>
이 메서드는 ActiveRecord::Relation
객체를 반환하는데, 이는 강화된 배열이라고 생각하시면 됩니다.
모델에 대해 더 자세히 알아보려면 Active Record Basics와 Active Record Query Interface를 참고하세요.
모델은 MVC 퍼즐의 마지막 조각입니다. 다음으로, 우리는 모든 조각들을 서로 연결할 것입니다.
6.4 게시글 목록 표시하기
app/controllers/articles_controller.rb
컨트롤러로 돌아가서, index
action을 데이터베이스로부터 모든 게시글을 가져오도록 수정해봅시다:
class ArticlesController < ApplicationController
def index
@articles = Article.all
end
end
컨트롤러의 인스턴스 변수는 view에서 접근할 수 있습니다. 이는 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에 대해 article.title
이 반환하는 값을 출력하고 싶기 때문에, 해당 코드는 <%= %>
로 감쌌습니다.
http://localhost:3000을 방문하여 최종 결과를 볼 수 있습니다. (bin/rails server
가 실행 중이어야 합니다!) 이를 실행할 때 일어나는 일은 다음과 같습니다:
- 브라우저가 요청을 보냅니다:
GET http://localhost:3000
- Rails 애플리케이션이 이 요청을 받습니다.
- Rails 라우터가 루트 경로를
ArticlesController
의index
액션에 매핑합니다. index
액션이Article
모델을 사용하여 데이터베이스의 모든 article을 가져옵니다.- Rails가 자동으로
app/views/articles/index.html.erb
뷰를 렌더링합니다. - 뷰의 ERB 코드가 평가되어 HTML을 출력합니다.
- 서버가 HTML을 포함한 응답을 브라우저에 보냅니다.
이제 모든 MVC 조각들을 연결했고, 첫 번째 컨트롤러 액션이 완성되었습니다! 다음으로, 두 번째 액션으로 넘어가겠습니다.
7 CRUDit Where CRUDit Is Due
거의 모든 웹 애플리케이션은 CRUD (Create, Read, Update, Delete) 작업을 포함합니다. 여러분의 애플리케이션이 수행하는 대부분의 작업이 CRUD라는 것을 발견할 수도 있습니다. Rails는 이를 인정하고, CRUD를 수행하는 코드를 단순화하는데 도움이 되는 많은 기능을 제공합니다.
애플리케이션에 더 많은 기능을 추가하면서 이러한 기능들을 살펴보겠습니다.
7.1 단일 Article 표시
현재 우리는 데이터베이스의 모든 article들을 나열하는 view를 가지고 있습니다. 이제 하나의 article의 제목과 본문을 보여주는 새로운 view를 추가해보겠습니다.
먼저 새로운 controller action(이는 다음에 추가할 예정입니다)에 매핑되는 새로운 route를 추가하는 것부터 시작합니다. config/routes.rb
를 열고, 여기 보이는 마지막 route를 삽입하세요:
Rails.application.routes.draw do
root "articles#index"
get "/articles", to: "articles#index"
get "/articles/:id", to: "articles#show"
end
새로운 route는 또 다른 get
route이지만, 경로에 추가적인 :id
가 있습니다. 이는 route parameter를 지정합니다. route parameter는 요청 경로의 일부를 캡처하고, 그 값을 controller action에서 접근할 수 있는 params
Hash에 넣습니다. 예를 들어, GET http://localhost:3000/articles/1
와 같은 요청을 처리할 때, 1
은 :id
의 값으로 캡처되어 ArticlesController
의 show
action에서 params[:id]
로 접근할 수 있게 됩니다.
이제 app/controllers/articles_controller.rb
의 index
action 아래에 show
action을 추가해봅시다:
class ArticlesController < ApplicationController
def index
@articles = Article.all
end
def show
@article = Article.find(params[:id])
end
end
show
액션은 라우트 파라미터로 캡처된 ID와 함께 Article.find
(이전에 언급됨)를 호출합니다. 반환된 article은 @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을 방문하면 article을 볼 수 있습니다!
마무리하기 위해, article 페이지로 이동하는 편리한 방법을 추가해보겠습니다. app/views/articles/index.html.erb
에서 각 article의 제목을 해당 페이지로 링크할 것입니다:
<h1>게시물</h1>
<ul>
<% @articles.each do |article| %>
<li>
<a href="/articles/<%= article.id %>">
<%= article.title %>
</a>
</li>
<% end %>
</ul>
7.2 Resourceful 라우팅
지금까지 우리는 CRUD의 "R"(Read)을 다뤘습니다. 앞으로 "C"(Create), "U"(Update), "D"(Delete)를 다룰 예정입니다. 짐작하셨겠지만, 새로운 route, controller action, view를 추가하면서 이를 진행할 것입니다. 이렇게 entity에 대해 CRUD 작업을 수행하기 위해 함께 작동하는 route, controller action, view의 조합이 있을 때마다, 우리는 그 entity를 resource라고 부릅니다. 예를 들어, 우리 애플리케이션에서 article은 resource라고 할 수 있습니다.
Rails는 resources
라는 라우팅 메서드를 제공하는데, 이는 article과 같은 리소스 컬렉션에 대한 모든 관례적인 route를 매핑합니다. 따라서 "C", "U", "D" 섹션으로 진행하기 전에, config/routes.rb
의 두 개의 get
route를 resources
로 교체해보겠습니다:
Rails.application.routes.draw do
root "articles#index"
resources :articles
end
bin/rails routes
명령을 실행하여 어떤 라우트가 매핑되어 있는지 확인할 수 있습니다:
$ bin/rails routes
Prefix Verb URI 패턴 Controller#Action
root GET / articles#index
articles GET /articles(.:format) articles#index
new_article GET /articles/new(.:format) articles#new
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
PUT /articles/:id(.:format) articles#update
DELETE /articles/:id(.:format) articles#destroy
resources
메서드는 특정한 라우트 설정에 의존하지 않도록 URL과 path 헬퍼 메서드도 설정합니다. 위 "Prefix" 열의 값에 _url
또는 _path
접미사를 붙이면 이러한 헬퍼들의 이름이 됩니다. 예를 들어, article이 주어졌을 때 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
helper를 사용하여 한 단계 더 나아가보겠습니다. link_to
helper는 첫 번째 인자를 링크의 텍스트로, 두 번째 인자를 링크의 목적지로 하여 링크를 렌더링합니다. 두 번째 인자로 model 객체를 전달하면, link_to
는 해당 객체를 path로 변환하기 위해 적절한 path helper를 호출합니다. 예를 들어, article을 전달하면, link_to
는 article_path
를 호출합니다. 따라서 app/views/articles/index.html.erb
는 다음과 같이 됩니다:
<h1>Articles</h1>
<ul>
<% @articles.each do |article| %>
<li>
<%= link_to article.title, article %>
</li>
<% end %>
</ul>
훌륭합니다!
라우팅에 대해 더 자세히 알아보려면, Rails Routing from the Outside In을 참조하세요.
7.3 새로운 Article 생성하기
이제 우리는 CRUD의 "C"(Create)로 넘어갑니다. 일반적으로 웹 애플리케이션에서 새로운 resource를 생성하는 것은 여러 단계의 프로세스입니다. 먼저, 사용자가 양식을 작성하기 위해 요청합니다. 그런 다음, 사용자가 양식을 제출합니다. 오류가 없다면 resource가 생성되고 어떤 종류의 확인이 표시됩니다. 그렇지 않으면, 양식이 오류 메시지와 함께 다시 표시되고, 이 프로세스가 반복됩니다.
Rails 애플리케이션에서 이러한 단계들은 일반적으로 controller의
new
와 create
action들로 처리됩니다. app/controllers/articles_controller.rb
의 show
action 아래에
이러한 action들의 일반적인 구현을 추가해보겠습니다:
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
action은 새로운 article을 인스턴스화하지만 저장하지는 않습니다. 이 article은 폼을 만들 때 view에서 사용됩니다. 기본적으로 new
action은 app/views/articles/new.html.erb
를 렌더링하며, 이는 다음에 만들 것입니다.
create
action은 title과 body 값을 가진 새로운 article을 인스턴스화하고 저장을 시도합니다. article이 성공적으로 저장되면, 액션은 브라우저를 "http://localhost:3000/articles/#{@article.id}"
의 article 페이지로 리다이렉트합니다. 그렇지 않으면, 액션은 422 Unprocessable Entity 상태 코드와 함께 app/views/articles/new.html.erb
를 렌더링하여 폼을 다시 표시합니다. 여기서 title과 body는 임시 값입니다. 폼을 생성한 후에 이것들을 변경할 것입니다.
redirect_to
는 브라우저가 새로운 요청을 하도록 하는 반면, render
는 현재 요청에 대해 지정된 view를 렌더링합니다. 데이터베이스나 애플리케이션 상태를 변경한 후에는 redirect_to
를 사용하는 것이 중요합니다. 그렇지 않으면, 사용자가 페이지를 새로고침할 때 브라우저가 동일한 요청을 하게 되어 변경이 반복됩니다.
7.3.1 Form Builder 사용하기
폼을 만들기 위해 Rails의 form builder 기능을 사용할 것입니다. form builder를 사용하면 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.textarea :body %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
form_with
helper 메서드는 form builder를 인스턴스화합니다. form_with
블록 내에서 우리는 form builder의 label
과 text_field
같은 메서드를 호출하여 적절한 form 요소들을 출력합니다.
우리의 form_with
호출로부터 생성되는 출력은 다음과 같습니다:
<form action="/articles" accept-charset="UTF-8" method="post">
<input type="hidden" name="authenticity_token" value="...">
<div>
<label for="article_title">제목</label><br>
<input type="text" name="article[title]" id="article_title">
</div>
<div>
<label for="article_body">본문</label><br>
<textarea name="article[body]" id="article_body"></textarea>
</div>
<div>
<input type="submit" name="commit" value="게시글 작성" data-disable-with="게시글 작성">
</div>
</form>
Form builder에 대해 더 자세히 알아보려면 Action View Form Helpers를 참고하세요.
7.3.2 Strong Parameters 사용하기
제출된 form 데이터는 route parameter와 함께 params
Hash에 저장됩니다. 따라서 create
액션은 제출된 title을 params[:article][:title]
을 통해, body를 params[:article][:body]
를 통해 접근할 수 있습니다. 이 값들을 개별적으로 Article.new
에 전달할 수 있지만, 그렇게 하면 장황하고 오류가 발생하기 쉽습니다. 그리고 필드를 더 추가하면 상황은 더 나빠질 것입니다.
대신, 값들을 포함하는 하나의 Hash를 전달할 것입니다. 하지만, 해당 Hash에서 어떤 값들이 허용되는지 명시해야 합니다. 그렇지 않으면, 악의적인 사용자가 추가 form 필드를 제출하여 private 데이터를 덮어쓸 수 있습니다. 실제로 필터링되지 않은 params[:article]
Hash를 직접 Article.new
에 전달하면, Rails는 문제를 알리기 위해 ForbiddenAttributesError
를 발생시킵니다. 따라서 우리는 params
를 필터링하기 위해 Rails의 Strong Parameters 기능을 사용할 것입니다. 이것은 params
에 대한 strong typing이라고 생각하면 됩니다.
app/controllers/articles_controller.rb
하단에 params
를 필터링하는 article_params
라는 private 메서드를 추가하고, 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.expect(article: [:title, :body])
end
end
Strong Parameters에 대해 자세히 알아보려면 Action Controller Overview § Strong Parameters를 참조하세요.
7.3.3 Validations과 에러 메시지 표시하기
우리가 살펴본 것처럼, resource를 생성하는 것은 여러 단계의 프로세스입니다. 유효하지 않은 사용자 입력을 처리하는 것도 그 프로세스의 또 다른 단계입니다. Rails는 유효하지 않은 사용자 입력을 처리하는 데 도움이 되는 validations라는 기능을 제공합니다. Validations은 model 객체가 저장되기 전에 확인되는 규칙입니다. 검사 중 하나라도 실패하면 저장이 중단되고, 적절한 에러 메시지가 model 객체의 errors
속성에 추가됩니다.
app/models/article.rb
에 몇 가지 validations을 추가해 보겠습니다:
class Article < ApplicationRecord
validates :title, presence: true
validates :body, presence: true, length: { minimum: 10 }
end
첫 번째 validation은 title
값이 존재해야 함을 선언합니다. title
이 문자열이기 때문에, 이는 title
값에 최소한 하나의 공백이 아닌 문자가 포함되어야 한다는 의미입니다.
두 번째 validation은 body
값도 존재해야 함을 선언합니다. 추가적으로, body
값은 최소 10자 이상이어야 한다고 선언합니다.
title
과 body
속성이 어디에 정의되어 있는지 궁금할 수 있습니다. Active Record는 모든 테이블 컬럼에 대해 모델 속성을 자동으로 정의하므로, 모델 파일에서 이러한 속성들을 선언할 필요가 없습니다.
validation을 설정했으니, app/views/articles/new.html.erb
를 수정하여 title
과 body
에 대한 오류 메시지를 표시해보겠습니다:
<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.textarea :body %><br>
<% @article.errors.full_messages_for(:body).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
full_messages_for
메서드는 지정된 attribute에 대한 사용자 친화적인 에러 메시지 배열을 반환합니다. 해당 attribute에 대한 에러가 없다면 빈 배열이 반환됩니다.
이 모든 것이 어떻게 함께 작동하는지 이해하기 위해 new
와 create
컨트롤러 액션을 다시 한 번 살펴보겠습니다:
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 Validations를 참조하세요. 유효성 검증 에러 메시지에 대해 자세히 알아보려면 Active Record Validations § Working with Validation Errors를 참조하세요.
7.3.4 마무리하기
이제 http://localhost:3000/articles/new를 방문해서 article을 생성할 수 있습니다. 마무리하기 위해 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 %>
7.4 Article 업데이트하기
CRUD의 "CR"을 다뤘습니다. 이제 "U"(Update)로 넘어가보겠습니다. 리소스를 업데이트하는 것은 리소스를 생성하는 것과 매우 유사합니다. 둘 다 여러 단계의 프로세스입니다. 먼저, 사용자가 데이터를 편집하기 위한 폼을 요청합니다. 그런 다음 사용자가 폼을 제출합니다. 오류가 없다면 리소스가 업데이트됩니다. 그렇지 않으면 폼이 에러 메시지와 함께 다시 표시되고, 프로세스가 반복됩니다.
이러한 단계들은 일반적으로 controller의 edit
과 update
action으로 처리됩니다. app/controllers/articles_controller.rb
의 create
action 아래에 이러한 action들의 일반적인 구현을 추가해보겠습니다:
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.expect(article: [:title, :body])
end
end
이 코드는 ArticlesController
클래스를 정의하며, ApplicationController
를 상속받습니다. 여기에는 다음과 같은 액션들이 포함됩니다:
index
: 모든 article을 가져옵니다show
: 특정 article을 보여줍니다new
: 새 article을 생성하기 위한 폼을 표시합니다create
: 새 article을 생성합니다edit
: 기존 article을 수정하기 위한 폼을 표시합니다update
: 기존 article을 수정합니다
private
메소드 article_params
는 허용된 파라미터들을 필터링합니다.
edit
와 update
액션이 new
와 create
액션과 유사하다는 점에 주목하세요.
edit
액션은 데이터베이스에서 article을 가져와서 @article
에 저장하여 폼을 만들 때 사용할 수 있도록 합니다. 기본적으로 edit
액션은 app/views/articles/edit.html.erb
를 렌더링합니다.
update
액션은 데이터베이스에서 article을 (다시) 가져와서 article_params
로 필터링된 제출된 폼 데이터로 업데이트를 시도합니다. 유효성 검사에 실패하지 않고 업데이트가 성공하면 액션은 브라우저를 해당 article의 페이지로 리다이렉트합니다. 그렇지 않으면 액션은 app/views/articles/edit.html.erb
를 렌더링하여 에러 메시지와 함께 폼을 다시 표시합니다.
7.4.1 View 코드 공유를 위한 Partial 사용하기
우리의 edit
폼은 new
폼과 동일하게 보일 것입니다. Rails form builder와 resourceful routing 덕분에 코드도 동일할 것입니다. form builder는 모델 객체가 이전에 저장되었는지 여부에 따라 적절한 종류의 요청을 만들도록 폼을 자동으로 구성합니다.
코드가 동일하기 때문에, 이를 partial이라고 하는 공유 view로 분리할 것입니다. 다음 내용으로 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.textarea :body %><br>
<% article.errors.full_messages_for(:body).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
이 코드는 form_with helper를 사용하여 article 모델에 대한 form을 생성하는 것입니다. form은 title과 body 필드를 포함하며, 각 필드에 오류가 있는 경우 해당 오류 메시지를 표시합니다. 각 필드는 label과 입력 필드로 구성되어 있습니다.
위 코드는 app/views/articles/new.html.erb
에 있는 form과 동일하지만, @article
이 나오는 모든 부분이 article
로 대체되었습니다. Partial은 공유되는 코드이므로, controller action에서 설정한 특정 instance 변수에 의존하지 않는 것이 가장 좋은 방법입니다. 대신 article을 local 변수로 partial에 전달할 것입니다.
render
를 사용하여 app/views/articles/new.html.erb
를 업데이트해봅시다:
<h1>새 글</h1>
<%= render "form", article: @article %>
partial의 파일명은 반드시 언더스코어로 시작해야 합니다. 예를 들어
_form.html.erb
와 같이 작성합니다. 하지만 렌더링할 때는 언더스코어 없이 참조됩니다. 예를 들어 render "form"
과 같이 작성합니다.
이제 매우 비슷한 app/views/articles/edit.html.erb
를 만들어보겠습니다:
<h1>게시글 수정</h1>
<%= render "form", article: @article %>
partial에 대해 더 알아보려면 Rails의 레이아웃과 렌더링 § Partial 사용하기를 참조하세요.
7.4.2 마무리
이제 article의 edit 페이지를 방문하여 수정할 수 있습니다. (예: http://localhost:3000/articles/1/edit)
마무리를 위해 app/views/articles/show.html.erb
하단에 edit 페이지로 연결되는 링크를 추가해보겠습니다:
<h1><%= @article.title %></h1>
<p><%= @article.body %></p>
<ul>
<li><%= link_to "수정", edit_article_path(@article) %></li>
</ul>
7.5 Article 삭제하기
마지막으로 CRUD의 "D" (Delete)에 도달했습니다. 리소스를 삭제하는 것은 생성이나 수정보다 간단한 프로세스입니다. 라우트와 컨트롤러 액션만 필요합니다. 그리고 우리의 resourceful routing(resources :articles
)이 이미 라우트를 제공하며, 이는 DELETE /articles/:id
요청을 ArticlesController
의 destroy
액션에 매핑합니다.
그러면 일반적인 destroy
액션을 app/controllers/articles_controller.rb
의 update
액션 아래에 추가해보겠습니다:
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.expect(article: [:title, :body])
end
end
destroy
액션은 데이터베이스에서 article을 가져와서 해당 객체의 destroy
를 호출합니다. 그런 다음, 브라우저를 303 See Other 상태 코드와 함께 root path로 리다이렉트합니다.
우리는 articles의 주요 접근점이기 때문에 root path로 리다이렉트하기로 선택했습니다. 하지만 다른 상황에서는 예를 들어 articles_path
로 리다이렉트하도록 선택할 수도 있습니다.
이제 app/views/articles/show.html.erb
의 하단에 링크를 추가하여 해당 페이지에서 article을 삭제할 수 있도록 만들어보겠습니다:
<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
옵션을 사용하여 "Destroy" 링크의 data-turbo-method
와 data-turbo-confirm
HTML 속성을 설정합니다. 이러한 속성들은 새로운 Rails 애플리케이션에 기본적으로 포함되어 있는 Turbo와 연동됩니다. data-turbo-method="delete"
는 링크가 GET
요청 대신 DELETE
요청을 하도록 만듭니다. data-turbo-confirm="Are you sure?"
는 링크를 클릭했을 때 확인 대화상자가 나타나도록 합니다. 사용자가 대화상자를 취소하면 요청이 중단됩니다.
이게 전부입니다! 이제 우리는 article을 목록화, 조회, 생성, 수정 그리고 삭제할 수 있습니다! InCRUDable!
8 두 번째 Model 추가하기
이제 애플리케이션에 두 번째 model을 추가할 시간입니다. 두 번째 model은 article에 대한 댓글을 처리할 것입니다.
8.1 Model 생성하기
이전에 Article
model을 생성할 때 사용했던 것과 동일한 generator를 보겠습니다. 이번에는 article에 대한 참조를 담을 Comment
model을 생성할 것입니다. 터미널에서 다음 명령어를 실행하세요:
$ bin/rails generate model Comment commenter:string body:text article:references
이 명령어는 4개의 파일을 생성합니다:
파일 | 목적 |
---|---|
db/migrate/<timestamp>_create_comments.rb | 데이터베이스에 comments 테이블을 생성하는 마이그레이션 |
app/models/comment.rb | Comment 모델 |
test/models/comment_test.rb | comment 모델을 위한 테스트 하네스 |
test/fixtures/comments.yml | 테스트에 사용할 샘플 comments |
먼저 app/models/comment.rb
를 살펴보겠습니다:
class Comment < ApplicationRecord
belongs_to :article
end
이는 앞서 본 Article
모델과 매우 유사합니다. 차이점은 Active Record association을 설정하는 belongs_to :article
라인입니다. 이 가이드의 다음 섹션에서 association에 대해 자세히 알아볼 것입니다.
셸 명령어에서 사용된 (:references
) 키워드는 모델을 위한 특별한 데이터 타입입니다. 이는 제공된 모델 이름에 _id
를 붙여 정수 값을 저장할 수 있는 새로운 컬럼을 데이터베이스 테이블에 생성합니다. 더 잘 이해하기 위해서는 마이그레이션을 실행한 후 db/schema.rb
파일을 분석해보세요.
모델 외에도 Rails는 해당하는 데이터베이스 테이블을 생성하기 위한 마이그레이션도 만들었습니다:
class CreateComments < ActiveRecord::Migration[8.1]
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
라는 이름의 integer 컬럼과 해당 컬럼에 대한 인덱스, 그리고 articles
테이블의 id
컬럼을 가리키는 foreign key 제약조건을 생성합니다. migration을 실행해보세요:
$ bin/rails db:migrate
Rails는 현재 데이터베이스에서 아직 실행되지 않은 migration만 실행할 만큼 똑똑하므로, 이 경우 다음과 같이만 표시됩니다:
== CreateComments: 마이그레이션 실행중 =================================================
-- create_table(:comments)
-> 0.0115s
== CreateComments: 마이그레이션 완료 (0.0119s) ========================================
8.2 모델 연관짓기
Active Record associations를 사용하면 두 모델 간의 관계를 쉽게 선언할 수 있습니다. comments와 articles의 경우, 관계를 다음과 같이 작성할 수 있습니다:
- 각 comment는 하나의 article에 속합니다.
- 하나의 article은 여러 comments를 가질 수 있습니다.
실제로 이것은 Rails가 이 association을 선언하는데 사용하는 구문과 매우 유사합니다. 이미 Comment
모델(app/models/comment.rb) 내부에서 각 comment가 Article에 속하도록 하는 코드를 보셨을 것입니다:
class Comment < ApplicationRecord
belongs_to :article
end
app/models/article.rb
를 수정하여 association의 다른 쪽을 추가해야 합니다:
class Article < ApplicationRecord
has_many :comments
validates :title, presence: true
validates :body, presence: true, length: { minimum: 10 }
end
이 두 가지 선언으로 많은 자동화된 동작이 가능해집니다. 예를 들어, article을 포함하는 인스턴스 변수 @article
이 있다면, @article.comments
를 사용하여 해당 article에 속한 모든 comment들을 배열로 가져올 수 있습니다.
Active Record associations에 대한 자세한 내용은 Active Record Associations 가이드를 참조하세요.
8.3 Comments를 위한 라우트 추가하기
articles
컨트롤러와 마찬가지로, Rails가 comments
를 어디서 볼 수 있는지 알 수 있도록 라우트를 추가해야 합니다. config/routes.rb
파일을 다시 열고 다음과 같이 수정하세요:
Rails.application.routes.draw do
root "articles#index"
resources :articles do
resources :comments
end
end
이는 articles
내에 중첩 리소스 로 comments
를 생성합니다. 이것은 article과 comment 사이에 존재하는 계층적 관계를 표현하는 또 다른 방법입니다.
라우팅에 대한 자세한 내용은 Rails Routing 가이드를 참조하세요.
8.4 Controller 생성하기
모델이 준비되었으므로, 이제 그에 맞는 controller를 생성하는데 집중할 수 있습니다. 다시 한번 이전에 사용했던 generator를 사용하겠습니다:
$ bin/rails generate controller Comments
이것은 세 개의 파일과 하나의 빈 디렉토리를 생성합니다:
파일/디렉토리 | 용도 |
---|---|
app/controllers/comments_controller.rb | Comments 컨트롤러 |
app/views/comments/ | 컨트롤러의 view들이 저장되는 곳 |
test/controllers/comments_controller_test.rb | 컨트롤러를 위한 테스트 |
app/helpers/comments_helper.rb | view 헬퍼 파일 |
모든 블로그와 마찬가지로, 독자들은 글을 읽은 직후 댓글을 작성하고, 댓글을 추가한 후에는 자신이 작성한 댓글이 목록에 표시된 것을 확인할 수 있도록 글 상세 페이지로 다시 이동하게 됩니다. 이러한 이유로 CommentsController
는 댓글을 생성하고 스팸 댓글이 달렸을 때 삭제하는 메서드를 제공합니다.
먼저, Article 상세 템플릿(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.textarea :body %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
이것은 Article
show 페이지에 CommentsController
의 create
액션을 호출하여 새 comment를 생성하는 폼을 추가합니다. 여기서 form_with
호출은 배열을 사용하는데, 이는 /articles/1/comments
와 같은 중첩된 라우트를 생성합니다.
이제 app/controllers/comments_controller.rb
에서 create
를 연결해 보겠습니다:
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.expect(comment: [:commenter, :body])
end
end
articles controller보다 복잡한 코드를 볼 수 있습니다. 이는 설정한 nesting의 부작용입니다. comment에 대한 각 요청은 comment가 첨부된 article을 추적해야 하므로, 해당 article을 가져오기 위해 Article
모델의 find
메서드를 먼저 호출합니다.
또한 이 코드는 association에서 사용할 수 있는 메서드들을 활용합니다. comment를 생성하고 저장하기 위해 @article.comments
의 create
메서드를 사용합니다. 이렇게 하면 comment가 해당 article에 자동으로 연결됩니다.
새 comment를 생성한 후에는 article_path(@article)
헬퍼를 사용하여 사용자를 원래 article로 다시 보냅니다. 이전에 보았듯이, 이는 ArticlesController
의 show
액션을 호출하고 이어서 show.html.erb
템플릿을 렌더링합니다. 여기가 comment를 표시하고 싶은 곳이므로, 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.textarea :body %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
이제 블로그에 article과 comment를 추가할 수 있고 적절한 위치에서 보여줄 수 있습니다.
9 Refactoring
이제 article과 comment가 작동하니 app/views/articles/show.html.erb
템플릿을 살펴보세요. 이 템플릿이 길어지고 다루기 어려워지고 있습니다. partial을 사용하여 깔끔하게 정리할 수 있습니다.
9.1 Partial 컬렉션 렌더링하기
먼저, article의 모든 comment를 보여주기 위한 comment partial을 만들어보겠습니다. 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.textarea :body %>
</p>
<p>
<%= form.submit %>
</p>
<% end %>
이제 app/views/comments/_comment.html.erb
partial이 @article.comments
collection에 있는 각 comment마다 한 번씩 렌더링됩니다. render
메소드가 @article.comments
collection을 순회하면서 각 comment를 partial과 같은 이름의 로컬 변수(이 경우 comment
)에 할당하며, 이는 우리가 보여줄 수 있도록 partial 내에서 사용 가능합니다.
9.2 Partial Form 렌더링하기
새 댓글 섹션을 별도의 partial로 분리해봅시다. 마찬가지로 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.textarea :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", article: @article %>
두 번째 render는 우리가 렌더링하고자 하는 partial 템플릿인 comments/form
을 정의합니다. Rails는 이 문자열의 슬래시를 감지하고 app/views/comments
디렉토리에 있는 _form.html.erb
파일을 렌더링하려 한다는 것을 이해할 만큼 똑똑합니다.
9.3 Concerns 사용하기
Concerns는 큰 controller나 model을 더 쉽게 이해하고 관리할 수 있게 해주는 방법입니다. 또한 여러 model(또는 controller)이 동일한 concern을 공유할 때 재사용성이라는 장점이 있습니다. Concerns는 model이나 controller가 담당하는 기능의 잘 정의된 부분을 나타내는 메서드들을 포함하는 module을 사용하여 구현됩니다. 다른 프로그래밍 언어에서는 이러한 module을 흔히 mixin이라고 부릅니다.
controller나 model에서 다른 module을 사용하는 것과 동일한 방식으로 concern을 사용할 수 있습니다. rails new blog
로 앱을 처음 생성했을 때, app/
디렉토리 안에 다른 폴더들과 함께 두 개의 폴더가 생성되었습니다:
app/controllers/concerns
app/models/concerns
아래 예제에서는 concern을 사용하면 좋은 새로운 블로그 기능을 구현할 것입니다. 그 다음, concern을 만들고 이를 사용하도록 코드를 리팩토링하여 코드를 더 DRY하고 유지보수하기 좋게 만들 것입니다.
블로그 게시글은 다양한 상태를 가질 수 있습니다. 예를 들어 모든 사람이 볼 수 있거나(public
), 작성자만 볼 수 있거나(private
), 또는 모든 사람에게 숨겨져 있지만 여전히 검색할 수 있는 상태(archived
)일 수 있습니다. 댓글도 마찬가지로 숨겨져 있거나 보일 수 있습니다. 이는 각 모델에서 status
컬럼을 사용하여 표현할 수 있습니다.
먼저, Articles
와 Comments
에 status
를 추가하기 위해 다음 마이그레이션을 실행해보겠습니다:
$ bin/rails generate migration AddStatusToArticles status:string
$ bin/rails generate migration AddStatusToComments status:string
이제 생성된 migration으로 데이터베이스를 업데이트해보겠습니다:
$ bin/rails db:migrate
기존 articles와 comments의 status를 선택하기 위해서는 생성된 migration 파일에 default: "public"
옵션을 추가하여 기본값을 설정할 수 있습니다. migration 파일을 수정한 후에는 이전 단계를 다시 실행할 수 있습니다:
$ bin/rails db:migrate:redo
rails console에서 Article.update_all(status: "public")
과 Comment.update_all(status: "public")
을 호출할 수도 있습니다.
migration에 대해 더 자세히 알아보려면 Active Record Migrations를 참조하세요.
또한 app/controllers/articles_controller.rb
에서 strong parameter의 일부로 :status
키를 허용해야 합니다:
private
def article_params
params.expect(article: [:title, :body, :status])
end
그리고 app/controllers/comments_controller.rb
에서:
private
def comment_params
params.expect(comment: [: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
그런 다음 index
액션 템플릿(app/views/articles/index.html.erb
)에서 archived?
메서드를 사용하여 archive된 article이 표시되지 않도록 할 수 있습니다:
<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 %>
비슷하게, comment partial view (app/views/comments/_comment.html.erb
)에서 archived된 comment를 표시하지 않기 위해 archived?
메서드를 사용합니다:
<% unless comment.archived? %>
<p>
<strong>작성자:</strong>
<%= comment.commenter %>
</p>
<p>
<strong>댓글:</strong>
<%= comment.body %>
</p>
<% end %>
하지만 지금 우리의 모델들을 다시 보면 로직이 중복되어 있다는 것을 알 수 있습니다. 나중에 블로그의 기능을 확장할 때 - 예를 들어 개인 메시지를 포함한다면 - 우리는 로직을 또다시 중복해야 할 수 있습니다. 이럴 때 concern이 유용하게 사용됩니다.
concern은 모델의 책임 중 특정 부분에만 집중합니다. 우리의 concern에 있는 메서드들은 모두 모델의 가시성과 관련되어 있을 것입니다. 새로운 concern(모듈)을 Visible
이라고 부르겠습니다. app/models/concerns
안에 visible.rb
라는 새 파일을 만들고, 모델들에서 중복되었던 모든 status 메서드들을 여기에 저장할 수 있습니다.
app/models/concerns/visible.rb
module Visible
def archived?
status == "archived"
end
end
우리는 status validation을 concern에 추가할 수 있지만, validation이 클래스 레벨에서 호출되는 메서드이기 때문에 이는 약간 더 복잡합니다. ActiveSupport::Concern
(API Guide)은 이들을 포함시키는 더 간단한 방법을 제공합니다:
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
app/models/article.rb
에서 각 모델의 중복된 로직을 제거하고 새로운 Visible
module을 대신 include할 수 있습니다:
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
클래스 메서드도 concern에 추가할 수 있습니다. 만약 메인 페이지에서 public article이나 comment의 수를 표시하고 싶다면, 다음과 같이 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
그리고 view에서는 이것을 클래스 메소드처럼 호출할 수 있습니다:
<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 %>
마무리하기 위해, form에 select box를 추가하고 사용자가 새로운 article을 생성하거나 새로운 comment를 작성할 때 status를 선택할 수 있도록 하겠습니다. 객체의 status를 선택하거나, 아직 설정되지 않은 경우 기본값으로 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>
10 Deleting Comments
블로그의 또 다른 중요한 기능은 스팸 댓글을 삭제할 수 있다는 것입니다. 이를 위해서는 view에 링크를 구현하고 CommentsController
에 destroy
action을 구현해야 합니다.
먼저 app/views/comments/_comment.html.erb
partial에 삭제 링크를 추가해보겠습니다:
<% 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 %>
새로운 "Destroy Comment" 링크를 클릭하면 CommentsController
로 DELETE /articles/:article_id/comments/:id
가 전송되며, 이를 통해 삭제하려는 comment를 찾을 수 있습니다. 그러므로 controller에 destroy
액션을 추가해보겠습니다(app/controllers/comments_controller.rb
):
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.expect(comment: [:commenter, :body, :status])
end
end
destroy
액션은 우리가 보고 있는 article을 찾고, @article.comments
컬렉션에서 해당 comment를 찾은 다음, 데이터베이스에서 이를 제거하고 article의 show 액션으로 되돌아갑니다.
10.1 연관된 객체 삭제하기
article을 삭제하면 연관된 comment들도 삭제해야 합니다. 그렇지 않으면 데이터베이스에서 불필요한 공간만 차지하게 됩니다. Rails는 이를 위해 association의 dependent
옵션을 사용할 수 있게 해줍니다. Article 모델 app/models/article.rb
를 다음과 같이 수정하세요:
class Article < ApplicationRecord
include Visible
has_many :comments, dependent: :destroy # article이 삭제되면 연관된 comment도 삭제
validates :title, presence: true # title은 반드시 있어야 함
validates :body, presence: true, length: { minimum: 10 } # body는 반드시 있어야 하고 최소 10자 이상이어야 함
end
11 Security
11.1 기본 인증
블로그를 온라인에 올리면 누구나 게시글을 추가하고, 수정하고, 삭제하거나 댓글을 삭제할 수 있게 됩니다.
Rails는 이러한 상황에서 유용하게 사용할 수 있는 HTTP 인증 시스템을 제공합니다.
ArticlesController
에서는 인증되지 않은 사용자의 다양한 액션 접근을 차단할 수 있는 방법이 필요합니다. 여기서 Rails의 http_basic_authenticate_with
메서드를 사용할 수 있는데, 이 메서드는 허용된 경우에만 요청된 액션에 대한 접근을 허용합니다.
인증 시스템을 사용하기 위해, app/controllers/articles_controller.rb
의 ArticlesController
맨 위에 명시합니다. 우리의 경우에는 index
와 show
를 제외한 모든 액션에서 사용자 인증이 필요하므로, 다음과 같이 작성합니다:
class ArticlesController < ApplicationController
http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]
def index
@articles = Article.all
end
# 간단한 예시를 위해 생략
end
인증된 사용자만 comment를 삭제할 수 있도록 허용하기 위해, CommentsController
(app/controllers/comments_controller.rb
)에 다음과 같이 작성합니다:
class CommentsController < ApplicationController
http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy
def create
@article = Article.find(params[:article_id])
# ...
end
# 간결성을 위한 코드 생략
end
이제 새로운 article을 생성하려고 하면, 기본적인 HTTP Authentication 인증 요청을 보게 될 것입니다:
올바른 username과 password를 입력하면, 다른 username과 password가 필요하거나 브라우저를 닫을 때까지 인증된 상태로 유지됩니다.
Rails 애플리케이션에는 다른 인증 방법들도 있습니다. Rails에서 널리 사용되는 두 가지 인증 애드온은 Devise rails engine과 Authlogic gem이며, 이 외에도 많은 다른 방법들이 있습니다.
11.2 기타 보안 고려사항
웹 애플리케이션의 보안은 광범위하고 세부적인 영역입니다. Rails 애플리케이션의 보안에 대해서는 Ruby on Rails Security Guide에서 더 자세히 다루고 있습니다.
12 다음은 무엇인가요?
이제 첫 번째 Rails 애플리케이션을 보았으니, 자유롭게 업데이트하고 직접 실험해보세요.
모든 것을 혼자 해결할 필요는 없다는 것을 기억하세요. Rails를 시작하고 실행하는 데 도움이 필요하다면 다음의 지원 리소스를 자유롭게 참고하세요:
13 Configuration 관련 주의사항
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을 사용하고 있고 사용자가 러시아어를
Hebrew나 Japanese 문자와 같이 non-ASCII 데이터가 데이터베이스에 한번 들어가면, 그 데이터는 영원히 손실됩니다. 가능하다면 데이터베이스의 내부 저장소로 UTF-8을 사용하세요.