Rails Image Upload Using Dragonfly

Upload file là một trong những tính năng wuan trọng của một ứng dụng Web. Nó cho phép người dung tải file cá nhân lên ứng dụng Web để sử dụng vào một mục đích cá nhân nào đó. Đã cá rất nhiều gem hỗ trợ developer thực hiện tính năng này như PaperClip, CarrierWave, v.v… Ngày hôm nay, tôi xin giới thiệu them một lựa chọn nữa cho các bạn đó là gem Dragonfly.

Dragonfly là một Ruby gem có khả năng tùy biến cao cho việc upload ảnh cũng như đính kèm file trong ứng dụng Rails.

Trong bài viết này, tôi sẽ tạo một ứng dụng Rails có tên là Dragon-Uploader, chỉ có một tính năng duy nhất là upload ảnh để minh họa cho việc sử dụng Dragonfly.

Cài đặt ImageMagick#

Để sử dụng Dragonfly, ta cần cài đặt ImageMagick trong máy. Để thực hiện việc này trên Ubuntu đơn giản chỉ với một câu lệnh sau:

sudo apt-get install imagemagick

Tạo Rails Application#

rails new dragon-uploader -T

-T là tùy chọn giúp loại bỏ việc generate test cho ứng dụng.

Thêm gem dragonfly vào Gemfile

#Gemfile

gem 'dragonfly', '~> 1.0', '>= 1.0.12'

Sau đó chạy bundle để cài đặt:

bundle install

Tiếp theo, ta sẽ tạo PhotosController cho ứng dụng:

rails generate controller Photos

Sử dụng Dragonfly#

Để sử dụng Dragonfly vào ứng dụng Rails thì điều đầu tiên cần làm là config thư viện này bằng cách thực hiện lệnh sau trong terminal:

rails generate dragonfly

Câu lệnh trên sẽ tạo ra file config dragonfly.rb trong thư mục config/initializers như sau:

#config/intializers/dragonfly.rb

require 'dragonfly'

# Configure
Dragonfly.app.configure do
  plugin :imagemagick

  secret "e83b8affbf1c807c7788c07d27e70e79fb0459f8e2c4375b59e60a3da11631e5"

  url_format "/media/:job/:name"

  datastore :file,
    root_path: Rails.root.join('public/system/dragonfly', Rails.env),
    server_root: Rails.root.join('public')
end

# Logger
Dragonfly.logger = Rails.logger

# Mount as middleware
Rails.application.middleware.use Dragonfly::Middleware

# Add model functionality
if defined?(ActiveRecord::Base)
  ActiveRecord::Base.extend Dragonfly::Model
  ActiveRecord::Base.extend Dragonfly::Model::Validations
end

Sau khi đã taọ ra file config, kế đến ta sẽ tạo model Photo cho ứng dụng:

rails generate model Photo

#app/models/photo.rb

class Photo < ActiveRecord::Base
  dragonfly_accessor :image
end

Dragonfly cung cấp phương thức dragonfly_accessor được sử dụng để đọc và ghi dữ liệu vào cột đực chỉ định (Trong ví dụ trên là cột image_uid).

Bây giờ, ta cần migrate column cho bảng photos như sau:

#xxx_create_photos.rb

class CreatePhotos < ActiveRecord::Migration
  def change
    create_table :photos do |t|
      t.string :image_uid
      t.string :title

      t.timestamps null: false
    end
  end
end

Chú ý: Nếu bạn đang sử dụng là avatar thì tên cột phải là avatar_uid

Migrate database:

rake db:migrate

Cài đặt PhotosController để có thể upload ảnh:

#app/controllers/photos_controller.rb

class PhotosController < ApplicationController
  def index
    @photos = Photo.all
  end

  def new
    @photo = Photo.new
  end

  def create
    @photo = Photo.new(photo_params)
    if @photo.save
      redirect_to photos_path
    else
      render :new
    end
  end

  private

  def photo_params
    params.require(:photo).permit(:image, :title)
  end
end

Sau đó, ta cần config routes.rb cho các action index, new, create:

#config/routes.rb

Rails.application.routes.draw do
  resource :photos only: [:index, :new, :create]

  root to: "photos#index"
end

Bước tiếp theo là ta cần tạo views để hiển thị danh sách ảnh(Ở đây tôi chỉ hiển thị dạng table đơn giản):

#app/views/photos/index.html.erb

<h2>Photos</h2>

<p id="notice"><%= notice %></p>

<table>
  <thead>
    <tr>
      <th>Title</th>
      <th>Image</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @photos.each do |photo| %>
      <tr>
        <td><%= photo.title %></td>
        <td><%= link_to image_tag(photo.image.thumb('100x100').url), photo.image.url %></td>
        <td><%= link_to 'Show', photo %></td>
        <td><%= link_to 'Edit', edit_photo_path(photo) %></td>
        <td><%= link_to 'Destroy', photo, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>
  • Và tạo form upload ảnh:
#app/views/photos/new.html.erb

<%= form_for @photo do |f| %>
  <div>
    <%= f.label :title %>
    <%= f.text_field :title %>
  </div>
  <div>
    <%= f.label :image %>
    <%= f.file_field :image %>
  </div>
  <div>
    <%= f.submit :submit %>
  </div>
<% end %>

Validations#

Để đảm bảo tính bảo mật, bạ không muốn người dung có thể upload bất kỳ loại file nào, hay giới hạn dung lượng file upload trong một lần. Tất nhiên, Drangonfly cung cấp đầy đủ các phương thức để thực hiện điều này. Ta cần khai báo để sử dụng các phương thức này.

#config/initializers/dragonfly.rb

# Sử dụng các phương thức validate của Dragonfly
if defined?(ActiveRecord::Base)
  ActiveRecord::Base.extend Dragonfly::Model
  ActiveRecord::Base.extend Dragonfly::Model::Validations
end

Bây giờ ta thêm validate cho model Photo:

#app/models/photo.rb

class Photo < ActiveRecord::Base
  dragonfly_accessor :image

  #title validation
  validates_presence_of :title

  #image validations
  validates_presence_of :image
  validates_size_of :image, maximum: 400.kilobytes,
                    message: "should not be more than 400KB", if: :image_changed?

  validates_property :format, of: :image, in: ['jpeg', 'png', 'gif'],
                      message: "the formats allowed are: .jpeg, .png, .gif", if: :image_changed?
end

Danh sách các phương thức validations của Dragonfly:

class Photo
  extend Dragonfly::Model::Validations

  validates_presence_of :image
  validates_size_of :image, maximum: 500.kilobytes

  # Kiểm tra phần mở rộng của file
  validates_property :ext, of: :image, as: 'jpg'
  # ..hoặc..
  validates_property :mime_type, of: :image, as: 'image/jpeg'

  # Kiểm tra độ rộng của ảnh
  validates_property :width, of: :image, in: (0..400), message: "should not be more than 400px"

  # ..hoặc chỉ check khi image_change?(Sử dụng khi update ảnh)
  validates_property :format, of: :image, as: 'png', if: :image_changed?
end

Bước tiếp theo, ta cài đặt thêm các action edit, show, destroy trong PhotosController để bổ sung tính năng cho phép người dùng cập nhật, xóa và xem chi tiết một ảnh như dưới đây:

#app/controllers/photos_controller.rb

class PhotosController < ApplicationController
  before_action :set_photos, only: [:show, :edit, :update, :destroy]

  def index
    @photos = Photo.all
  end

  def new
    @photo = Photo.new
  end

  def create
    @photo = Photo.new(photo_params)
    if @photo.save
      redirect_to @photo
    else
      render :new
    end
  end

  def show
  end

  def edit
  end

  def update
    if @photo.update(photo_params)
      redirect_to @photo, notice: "photo successfully updated"
    else
      render :edit
    end
  end

  def destroy
    @photo.destroy
    redirect_to photos_url, notice: 'photo was successfully destroyed.'
  end

  private

  def photo_params
    params.require(:photo).permit(:image, :title)
  end

  def set_photos
    @photo = Photo.find(params[:id])
  end
end
  • Form cập nhật một ảnh
#app/views/photos/edit.html.erb

<%= form_for @photo do |f| %>
  <% if @photo.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@photo.errors.count, "error") %> prohibited this photo from being saved:</h2>

      <ul>
      <% @photo.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>
  <div>
    <%= f.label :title %>
    <%= f.text_field :title %>
  </div>
  <div>
    <%= f.label :image %>
    <%= f.file_field :image %>
  </div>
  <div>
    <%= f.submit :submit %>
  </div>
<% end %>

<%= link_to "Show", @photo %> |
<%= link_to "Back", photos_path %>
  • Xem chi tiết một ảnh
#app/views/photos/show.html.erb

<div>
  <strong>Title:</strong>
  <%= @photo.title %>
</div>
<div>
  <strong>Image:</strong>
  <%= image_tag @photo.image.thumb('400x200#').url if @photo.image_stored? %>
</div>

<%= link_to 'Edit', edit_photo_path(@photo) %> |
<%= link_to 'Back', photos_path %>

Cuối cùng, ta cần thay đổi routes.rb để có thể truy cập được các chức năng vừa thêm:

#config/routes.rb

Rails.application.routes.draw do
  resources :photos

  root to: "photos#index"
end

Kết luận#

Trong bài viết này, tôi đã chỉ cách sử dụng cơ bản của Dragonfly qua một ứng dụng Rails đơn giản. Để tìm hiểu thêm, cũng như sử dụng các tính năng khác của gem, các bạn tham khảo documentation

Hy vọng bài viết đã giúp ích cho mọi người (bow) !


All Rights Reserved