Computer >> 컴퓨터 >  >> 프로그래밍 >> Ruby

Rails와 Carrierwave로 파일 업로드 기능 구현하기: 완벽 가이드

이 글은 "Rails로 업로드하기" 시리즈의 또 다른 편입니다. 오늘은 Rails에서 가장 인기 있는 파일 업로드 솔루션 중 하나인 Carrierwave를 소개하려고 합니다. 제가 Carrierwave를 좋아하는 이유는 시작하기 쉽고, 기본적으로 제공되는 기능이 풍부하며, 커뮤니티 회원들이 작성한 수많은 "How to" 문서가 있어서 길을 잃을 걱정이 없기 때문입니다.

이 글에서 배울 내용은 다음과 같습니다:

  • Rails 앱에 Carrierwave 통합하기
  • 유효성 검사(Validation) 추가하기
  • 요청 간 파일 상태 유지하기
  • 파일 삭제하기
  • 썸네일 생성하기
  • 원격 URL에서 파일 업로드하기
  • 다중 파일 업로드 지원하기
  • 클라우드 스토리지 연동하기

이 글의 전체 소스 코드는 GitHub에서 확인할 수 있습니다. 즐거운 독서 되세요!

기본 환경 구축하기

늘 그렇듯이, 새로운 Rails 애플리케이션부터 만들어 보겠습니다:

이 데모에서는 Rails 5.0.2를 사용합니다. 참고로 Carrierwave 1은 Rails 4 이상과 Ruby 2만 지원합니다. 아직 Rails 3를 사용 중이라면 Carrierwave 버전 0.11을 설치해야 합니다.

Carrierwave의 동작을 확인하기 위해 Post 모델 하나만 있는 아주 간단한 블로그 애플리케이션을 만들어 보겠습니다. 주요 속성은 다음과 같습니다:

  • title (string) — 게시글 제목
  • body (text) — 게시글 본문
  • image (string) — 게시글에 첨부된 이미지를 담는 필드 (정확히는 파일 이름)

마이그레이션을 생성하고 적용합니다:

라우트를 설정합니다:

config/routes.rb

그리고 기본적인 컨트롤러를 작성합니다:

posts_controller.rb

이제 index 뷰를 만들어 보겠습니다:

views/posts/index.html.erb

그리고 대응하는 파셜(partial)입니다:

views/posts/_post.html.erb

여기서는 Rails의 truncate 메서드를 사용해 게시글 본문 중 처음 150자만 표시했습니다. 나머지 뷰와 폼 파셜을 만들기 전에, 먼저 Carrierwave를 애플리케이션에 통합해 보겠습니다.

Carrierwave 통합하기

Gemfile에 새 젬(gem)을 추가합니다:

Gemfile

실행합니다:

Carrierwave는 모델에 포함되는 업로더(uploader) 안에 설정 정보를 저장합니다. 업로더를 생성하려면 다음 명령어를 사용하세요:

이제 app/uploaders 디렉터리 안에 image_uploader.rb라는 새 파일이 생긴 것을 확인할 수 있습니다. 이 파일에는 유용한 주석과 예제가 포함되어 있으므로, 시작점으로 활용하기 좋습니다. 이 데모에서는 ActiveRecord를 사용하지만, Carrierwave는 Mongoid, Sequel, DataMapper도 지원합니다.

다음으로, 이 업로더를 모델에 포함(마운트)해야 합니다:

models/post.rb

업로더에는 이미 합리적인 기본 설정이 들어 있지만, 최소한 업로드된 파일이 저장될 위치는 정해줘야 합니다. 우선 파일 스토리지(file storage)를 사용해 보겠습니다:

uploaders/image_uploader.rb

기본적으로 파일은 public/uploads 디렉터리에 저장되므로, 버전 관리 시스템에서 이 디렉터리를 제외하는 것이 좋습니다:

.gitignore

업로더 내부의 store_dir 메서드를 수정하면 다른 저장 위치를 지정할 수도 있습니다.

이제 새 뷰와 폼 파셜을 만들어 파일 업로드를 시작할 준비가 되었습니다:

views/posts/new.html.erb

views/posts/_form.html.erb

PostsController는 이미 image 속성을 허용(permit)했기 때문에 수정할 필요가 없습니다.

마지막으로 edit 뷰를 생성합니다:

views/posts/edit.html.erb

이걸로 끝입니다! 서버를 실행하고 이미지가 첨부된 게시글을 만들어 보세요. 문제는 이 이미지가 어디에도 표시되지 않는다는 것입니다. 다음 섹션에서 show 페이지를 추가해 해결해 보겠습니다.

이미지 표시하기

아직 만들지 않은 유일한 뷰는 show입니다. 바로 추가해 보겠습니다:

views/posts/show.html.erb

보시다시피 첨부 파일을 표시하는 방법은 정말 간단합니다. @post.image.url이라고 입력하기만 하면 이미지의 URL을 가져올 수 있습니다. 파일 경로가 필요하면 current_path 메서드를 사용하세요. 또한 Carrierwave는 첨부 파일이 실제로 존재하는지 확인할 수 있는 image? 메서드도 제공합니다 (파일이 없더라도 image 메서드 자체는 절대 nil을 반환하지 않습니다).

이제 게시글 페이지로 이동하면 이미지가 보이지만, 화면이 너무 클 수 있습니다. 어차피 크기를 제한하는 곳이 없었으니까요. 물론 CSS 규칙으로 이미지를 축소할 수도 있지만, 파일이 업로드된 후 썸네일을 생성하는 것이 훨씬 더 좋은 방법입니다. 다만 이를 위해서는 몇 가지 추가 단계가 필요합니다.

썸네일 생성하기

이미지를 자르고 크기를 조절하려면 별도의 도구가 필요합니다. Carrierwave는 기본적으로 RMagick과 MiniMagick 젬을 지원하며, 이 젬들은 ImageMagick을 통해 이미지를 조작합니다. ImageMagick은 기존 이미지를 편집하고 새 이미지를 생성할 수 있는 오픈소스 솔루션이므로, 진행하기 전에 먼저 다운로드하여 설치해야 합니다. 그다음 두 젬 중 하나를 선택하면 됩니다. 저는 설치가 훨씬 쉽고 지원도 더 좋은 MiniMagick을 사용하겠습니다:

Gemfile

실행합니다:

그런 다음 업로더에 MiniMagick을 포함시킵니다:

uploaders/image_uploader.rb

이제 업로더에 새로운 버전(version)을 정의하기만 하면 됩니다. 버전(또는 스타일)이라는 개념은 많은 파일 업로드 라이브러리에서 사용되는데, 원본 첨부 파일을 기반으로 서로 다른 크기나 형식의 추가 파일을 생성한다는 의미입니다. thumb라는 새 버전을 추가해 보겠습니다:

uploaders/image_uploader.rb

버전은 원하는 만큼 여러 개 만들 수 있으며, 심지어 다른 버전을 기반으로 새 버전을 만드는 것도 가능합니다:

uploaders/image_uploader.rb

이미 업로드된 이미지가 있다면 썸네일이 없을 것입니다. 하지만 걱정할 필요 없습니다. Rails 콘솔에서 다시 생성할 수 있습니다:

마지막으로, 원본 이미지로 연결되는 링크와 함께 썸네일을 표시합니다:

views/posts/show.html.erb

서버를 실행하고 결과를 확인해 보세요!

유효성 검사 추가하기

현재 업로드는 잘 작동하지만 사용자 입력에 대한 유효성 검사가 전혀 없습니다. 물론 이건 좋지 않습니다. 이미지만 다루고 싶으므로 .png, .jpg, .gif 확장자만 허용 목록에 넣어 보겠습니다:

uploaders/image_uploader.rb

content_type_whitelist 메서드를 정의하면 콘텐츠 타입 검사도 추가할 수 있습니다:

uploaders/image_uploader.rb

반대로 실행 파일 같은 특정 파일 형식을 차단하고 싶다면 content_type_blacklist 메서드를 정의하면 됩니다.

파일의 타입과 확장자 검사 외에도, 파일 크기가 1메가바이트 미만이 되도록 강제해 보겠습니다. 이를 위해서는 ActiveModel의 파일 유효성 검사를 지원하는 추가 젬이 필요합니다:

Gemfile

설치합니다:

이제 원하는 유효성 검사를 추가합니다 (titlebody 속성에 대한 검사도 함께 넣었습니다):

models/post.rb

다음으로 할 일은 Carrierwave의 에러 메시지에 대한 I18n 번역을 추가하는 것입니다:

config/locales/en.yml

현재는 유효성 검사 에러를 어디에도 표시하지 않으므로, 공유 파셜을 하나 만들겠습니다:

views/shared/_errors.html.erb

이 파셜을 폼 안에서 사용합니다:

views/posts/_form.html.erb

이제 잘못된 파일을 업로드해 보고 결과를 확인하세요. 잘 작동할 것입니다. 하지만 유효한 파일을 선택한 후 제목이나 본문을 채우지 않으면 검사에 실패하고 에러가 표시됩니다. 문제는 파일 필드가 초기화되어 사용자가 이미지를 다시 선택해야 한다는 점인데, 이건 꽤 불편합니다. 이를 해결하려면 폼에 필드를 하나 더 추가해야 합니다.

요청 간 파일 상태 유지하기

폼이 다시 표시될 때 파일 상태를 유지하는 것은 사실 아주 간단합니다. 숨김 필드(hidden field)를 하나 추가하고 컨트롤러에서 허용하기만 하면 됩니다:

views/shared/_form.html.erb

posts_controller.rb

이제 image_cache가 자동으로 채워져 이미지가 사라지지 않습니다. 사용자가 이미지가 성공적으로 처리되었음을 알 수 있도록 썸네일을 함께 표시하는 것도 도움이 됩니다:

views/shared/_form.html.erb

이미지 삭제하기

레코드를 편집할 때 첨부 파일을 삭제할 수 있는 기능도 매우 흔하게 요구됩니다. Carrierwave로는 이 기능 구현이 전혀 문제가 되지 않습니다. 폼에 새 체크박스를 추가합니다:

views/shared/_form.html.erb

그리고 remove_image 속성을 허용합니다:

posts_controller.rb

이걸로 끝입니다! 코드로 직접 이미지를 삭제하려면 remove_image! 메서드를 사용하세요:

원격 위치에서 업로드하기

Carrierwave는 기본적으로 아주 멋진 기능도 제공합니다. 바로 URL을 통해 원격 위치에서 파일을 업로드하는 기능입니다. 새 필드를 추가하고 해당 속성을 허용해서 이 기능을 도입해 보겠습니다:

views/shared/_form.html.erb

posts_controller.rb

얼마나 멋진가요? 어떤 변경도 할 필요 없이 이 기능을 바로 테스트할 수 있습니다!

다중 파일 업로드 다루기

게시글에 여러 개의 첨부 파일을 달 수 있게 하고 싶다고 가정해 봅시다. 현재 설정으로는 불가능하지만, 다행히 Carrierwave는 이런 시나리오도 지원합니다. 이 기능을 구현하려면 직렬화(serialized) 필드(SQLite의 경우)나 JSON 필드(Postgres 또는 MySQL의 경우)를 추가해야 합니다. 저는 후자를 선호하므로, 지금 새 데이터베이스 어댑터로 전환해 보겠습니다. Gemfile에서 sqlite3 젬을 제거하고 pg를 추가합니다:

Gemfile

설치합니다:

데이터베이스 설정을 다음과 같이 수정합니다:

config/database.yml

대응하는 Postgres 데이터베이스를 생성한 후, 마이그레이션을 생성하고 적용합니다:

SQLite를 계속 사용하고 싶다면 Carrierwave 공식 문서의 안내를 따르세요.

이제 업로더를 마운트합니다 (복수형에 주목하세요!):

model/post.rb

여기서는 첨부 파일에 같은 업로더를 사용했지만, 물론 다른 설정을 가진 새 업로더를 생성할 수도 있습니다.

폼에 다중 파일 필드를 추가합니다:

views/shared/_form.html.erb

attachments 필드는 배열을 담게 되므로, 다음과 같이 허용해야 합니다:

posts_controller.rb

마지막으로 게시글의 첨부 파일들을 반복(iterate)하면서 평소처럼 표시할 수 있습니다:

views/shared/show.html.erb

각 첨부 파일은 ImageUploader에서 설정한 대로 썸네일을 갖게 됩니다. 훌륭하죠!

클라우드 스토리지 사용하기

파일 스토리지를 고수하는 것이 항상 편리하거나 가능한 것은 아닙니다. 예를 들어 Heroku에서는 커스텀 파일을 저장할 수 없습니다. 그렇다면 Carrierwave와 Amazon S3 클라우드 스토리지를 어떻게 결합할 수 있을까요? 사실 이것도 꽤 쉬운 작업입니다. Carrierwave는 fog-aws 젬에 의존해 이 기능을 구현합니다:

Gemfile

설치합니다:

Carrierwave용 이니셜라이저를 만들고 클라우드 스토리지를 전역적으로 설정해 보겠습니다:

config/initializers/carrierwave.rb

그 외에도 몇 가지 옵션이 있으며, 자세한 내용은 공식 문서에서 확인할 수 있습니다.

저는 dotenv-rails 젬을 사용해 환경 변수를 안전하게 설정하지만, 다른 방법을 선택해도 됩니다. 다만 S3 키 쌍이 외부에 노출되지 않도록 반드시 주의하세요. 그렇지 않으면 누구나 여러분의 버킷에 무엇이든 업로드할 수 있게 됩니다!

다음으로 storage :file 라인을 다음과 같이 교체합니다:

uploaders/image_uploader.rb

S3 외에도 Carrierwave는 Google Storage와 Rackspace로의 업로드를 지원합니다. 이 서비스들 역시 쉽게 설정할 수 있습니다.

마무리

오늘은 여기까지입니다! 우리는 Carrierwave의 주요 기능을 모두 살펴봤고, 이제 여러분의 프로젝트에서 바로 사용할 수 있습니다. 추가로 사용할 수 있는 옵션들이 더 있으니, 공식 문서를 꼭 둘러보시기 바랍니다.

막히는 부분이 있다면 주저하지 말고 질문을 남겨주세요. 또한 많은 일반적인 질문에 답하는 유용한 "How to" 문서가 모여 있는 Carrierwave 위키를 살펴보는 것도 도움이 될 것입니다.

끝까지 읽어주셔서 감사합니다. 즐거운 코딩 되세요!