갤러리형 레이아웃 작성하기

이전에 언급한 바와 같이 게시판의 종류를 세가지로 분류한 바 있다. 일반형, 블로그형, 갤러리형 세가지 중에 일반형과 블로그형은 이미 전용 레이아웃을 구현하였고, 이제 남은 건 갤러리형 레이아웃을 만드는 것이다.

갤러리형 게시판은 이미지를 업로드할 수 있으면 업로드된 이미지들은 쎔네일 형태로 보이도록 하고자 한다.

이미지를 업로드를 구현하기 위해서는 두가지 젬을 사용할 수 있다. 하나는 paperclip이고 다른 하나는 carrierwave라는 젬이다.

이 두 젬은 모두 시스템에 ImageMagick이라는 툴이 설치되어 있어야 한다. 시스템에서 아래와 같은 명령으로 설치 여부를 알 수 있다. ghostscript도 함께 설치하면 PDF 파일 업로드시 첫페이지 이미지를 썸네일로 만들 수 있다.

$ convert

Version: ImageMagick 6.8.7-7 Q16 x86_64 2013-11-27 http://www.imagemagick.org
Copyright: Copyright (C) 1999-2014 ImageMagick Studio LLC
Features: DPC Modules
Delegates: bzlib freetype jng jpeg ltdl png xml zlib
~중략~

만약 설치되어 있지 않으면 ImageMagick 설치하기를 참고하여 설치하면 된다.

Carrierwave 젬 설치하기

여기서는 carrierwave 젬을 사용하기로 한다.

Gemfile에 아래와 같이 젬을 추가한다.

gem 'carrierwave'

젬을 설치한다.

$ bundle install

한글 파일명의 인코딩 문제

carreirwave 젬을 사용할 때에는 한글파일명을 가진 파일을 업로드할 때 문제가 있다. 즉, 한글 파일명을 가진 파일이 업로드되면 sanitization 과정에서 한글이 모두 “_” 문자로 대체되어 파일명을 알수 없게 된다.

github해결책이 기술되어 있어 소개한다.

config/initializers/carrierwave.rb 파일을 생성한 후 아래의 코드를 추가해 주기만 하면 한글파일명이 깨지지 않고 그대로 업로드되는 것을 확인할 수 있다.

# Allow non-ascii letters in uploaded filenames
CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/

그러나 이렇게 하면 한글파일명 중에 스페이스가 포함되면 이 또한 "_" 문자로 보이게 되는데, 이것 마저도 스페이스 그대로 보이게 하려면 "\s"를 추가해 주면 된다.

# Allow non-ascii letters in uploaded filenames
CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\s\.\-\+]/

Caution : 젬을 추가하거나 설정파일을 변경한 경우에는 반드시 웹서버를 다시 시작해야 한다.

업로드 클래스의 생성

이미지 업로드를 위한 Picture라는 이름을 가지는 업로더를 생성한다.

$ bin/rails g uploader Picture
      create  app/uploaders/picture_uploader.rb

생성된 PictureUploader 클래스 파일의 내용은 아래와 같다

# encoding: utf-8

class PictureUploader < CarrierWave::Uploader::Base

  # Include RMagick or MiniMagick support:
  # include CarrierWave::RMagick
  # include CarrierWave::MiniMagick

  # Choose what kind of storage to use for this uploader:
  storage :file
  # storage :fog

  # Override the directory where uploaded files will be stored.
  # This is a sensible default for uploaders that are meant to be mounted:
  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  # Provide a default URL as a default if there hasn't been a file uploaded:
  # def default_url
  #   # For Rails 3.1+ asset pipeline compatibility:
  #   # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.webp"].compact.join('_'))
  #
  #   "/images/fallback/" + [version_name, "default.webp"].compact.join('_')
  # end

  # Process files as they are uploaded:
  # process :scale => [200, 300]
  #
  # def scale(width, height)
  #   # do something
  # end

  # Create different versions of your uploaded files:
  # version :thumb do
  #   process :resize_to_fit => [50, 50]
  # end

  # Add a white list of extensions which are allowed to be uploaded.
  # For images you might use something like this:
  # def extension_white_list
  #   %w(jpg jpeg gif png)
  # end

  # Override the filename of the uploaded files:
  # Avoid using model.id or version_name here, see uploader/store.rb for details.
  # def filename
  #   "something.jpg" if original_filename
  # end

end

사용법에 대한 자세한 안내가 코멘트로 처리되어 있다. 코멘트를 제거한 클래스를 보면 아래와 같다.

# encoding: utf-8

class PictureUploader < CarrierWave::Uploader::Base

  storage :file

  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

end

빈 디렉토리 자동으로 삭제하기

업로드한 이미지를 삭제하면 해당 폴더가 남아 있게 된다. 아래와 같이 업로더 클래스(PictureUploader)에 추가하면 자동으로 빈 폴더가 삭제된다. 자세한 내용은 여기를 참고하라.

# 업로드 상단에 아래의 after 매크로를 추가한다.
after :remove, :delete_empty_upstream_dirs

def delete_empty_upstream_dirs
  path = ::File.expand_path(store_dir, root)
  Dir.delete(path) # fails if path not empty dir

  path = ::File.expand_path(base_store_dir, root)
  Dir.delete(path) # fails if path not empty dir
rescue SystemCallError
  true # nothing, the dir is not empty
end

Picture 속성 추가하기

Post 클래스에 picture 속성을 추가하기 위해서 아래와 같이 마이그레이션 클래스 파일을 생성하고 DB 마이그레이션 한다.

$ bin/rails g migration add_picture_to_posts picture
      invoke  active_record
      create    db/migrate/20150201000532_add_picture_to_posts.rb

$ bin/rake db:migrate
== 20150201000532 AddPictureToPosts: migrating ================================
-- add_column(:posts, :picture, :string)
   -> 0.0007s
== 20150201000532 AddPictureToPosts: migrated (0.0007s) =======================

업로더 마운트하기

Post 클래스 파일(app/models/post.rb)을 열어 PictureUploader 업로더 클래스를 picture 속성으로 마우트한다.

class Post < ActiveRecord::Base
  belongs_to :bulletin
  mount_uploader :picture, PictureUploader
end

또한 posts 컨트롤러의 params 해시에 picturepicture_cahe 속성을 추가한다.

def post_params
  params.require(:post).permit(:title, :content, :picture, :picture_cache)
end

MiniMagick 젬 추가하기

이미지 크기를 조절하기 위해서 Rmagick이나 MiniMagick 젬을 추가한다. carrierwave 문서에 따르면 MiniMagick 젬을 추천하므로 아래와 같이 Gemfileminimagick 젬을 추가하고,

gem 'mini_magick'

젬을 설치한다.

$ bundle install

업로더에 이미지 크기 옵션 추가하기

app/uploaders/picture_uploader.rb 파일을 열고 아래의 내용을 추가한다.

# encoding: utf-8

class PictureUploader < CarrierWave::Uploader::Base

  include CarrierWave::MiniMagick

  ...

  process :resize_to_fit => [800, 800]

  version :thumb do
    process :resize_to_fill => [200,200]
  end

end

Post 폼에 파일업로드 추가하기

partial 템플릿 파일(app/views/posts/_form.html.erb)을 아래와 같이 변경한다.

<%= simple_form_for([@bulletin, @post]) do |f| %>
  <%= f.error_notification %>

  <div class="form-inputs">
    <%= f.input :title %>
    <%= f.input :content, input_html: { rows: 10 } %>

    <% if @post.bulletin.post_type == "gallery" %>
      <%= f.input :picture, as: :file %>
      <%= f.hidden_field :picture_cache %>
    <% end %>
  </div>

  <div class="form-actions">
    <%= f.button :submit %>
  </div>
<% end %>

위에서 erb 코드 부분을 보면, gallery형 게시판에서만 이미지를 업로드할 수 있도록 조건을 추가한 것을 주목하자.

그리고 파일 업로드 input에 대한 스타일을 app/assets/stylesheets/posts.scss 파일에 추가한다.

input[type='file'] {
  border: 1px solid #d1d1d1;
  padding: .5em;
  border-radius: .3em;
  width: 100%;
}

갤러리 게시판을 생성

이미지를 업로드하는 게시판을 생성하기 위해서 http://localhost:3000/bulletins로 접속한 후 아래와 같이 “갤러리”라는 게시판을 추가한다. 이 때 Post_type에서 갤러리로 선택하고 저장한다.

이렇게 해서 게시판은 총 4개가 되었다.

이제 어플리케이션 레이아웃 파일(app/views/layouts/application.html.erb)을 열고, 상단 메뉴항목에 갤러리를 추가한다.

...
<li class="<%= params[:bulletin_id] == '4' ? 'active' : '' %>"><%= link_to '갤러리', bulletin_posts_path(4) %></li>
...

다음은, awesome 폰트를 사용하기 위해서 아래와 같이 Gemfile에 추가하고

gem "font-awesome-rails"

bundle install 후 웹서버를 다시 시작한다.

그리고 app/assets/stylesheets/application.scss 파일을 열고 아래와 같이 추가한다.

...
@import 'bootstrap';
@import "font-awesome";  <<< 추가
@import 'posts';
...

Info : 이 젬은 fa_icon이라는 헬퍼메소드를 지원해 준다. 사용법은 여기를 참고한다.

다음에는 갤러리partial 템플릿 파일을 생성하기 위해 app/views/posts/post_types/ 디렉토리에 _gallery.html.erb 파일을 추가하고 아래와 같이 작성한다.

<h2><%= bulletin_name params[:bulletin_id] %></h2>

<div class='gallery'>
<% @posts.each do | post | %>
    <div class='col-lg-3 col-md-3 col-xs-4  picture'>
      <div class='image'><%= link_to(image_tag(post.picture_url(:thumb)), [post.bulletin, post]) if post.picture? %></div>
      <div class='title'><%= post.title %></div>
      <div class='content'><%= post.content %></div>
      <div class='actions'>
          <%= link_to fa_icon('eye'), [post.bulletin, post] %>
          <%= link_to fa_icon('edit'), edit_bulletin_post_path(post.bulletin, post) %>
          <%= link_to fa_icon('trash'), [post.bulletin, post], method: :delete, data: { confirm: 'Are you sure?' } %>
      </div>
    </div>
<% end %>
</div>
<br>

<%= link_to 'New Post', new_bulletin_post_path, class: 'btn btn-default' %>

app/assets/stylesheets/posts.scss 파일에 아래와 같이 추가한다.

.gallery {
  overflow:auto;
  .picture {
    position:relative;
    margin:1.5em 0;
    .image img {
      border: 1px solid #928c75;
    }
    .title {}
    .content {}
    .actions {
      position:absolute;
    }
  }
}

갤러리 게시판에서 이미지를 추가하는 화면은 아래와 같다.

또한, 업로더 클래스에 아래와 같이 업로드할 수 있는 파일 포맷을 지정할 수 있다.

...
def extension_white_list
  %w(jpg jpeg gif png pdf)
end
...

이제 .jpg, .jpeg, .gif, .webp, .pdf 확장자를 가진 파일만 업로드할 수 있게 된다.

이러한 파일확장자 이외의 파일을 업로드하면 아래와 같은 에러 메시지가 표시된다.

.pdf 파일을 업로드할 경우에는 pdf 파일의 첫페이지가 쎔네일 이미지로 만들어진다.

각 게시판형에 따른 레이아웃용 partial 템플릿 파일에서 헤더 부분에 아래와 같이 추가하여 게시판 제목 옆에 설정 링크를 두면 좋겠다. (_bulletin.html.erb, _blog.html.erb, _gallery.html.erb)

<h2><%= bulletin_name params[:bulletin_id] %> <small><%= link_to '설정', edit_bulletin_path(params[:bulletin_id])%></small></h2>

Info : 업로드된 파일은 public/uploads/ 디렉토리에 저장된다. 이 파일들은 소스관리를 할 필요가 없기 때문에, .gitignore 파일을 열어 하단에 아래와 같이 추가해 준다.

...
public/uploads/*

Git소스 https://github.com/rorlakr/rcafe/tree/chapter_05_11