12/08/2018, 13:15

Upload file với gem Dragonfly

Xin chào các bạn (lay2) Trong một Web Application, upload file là một chức năng gần như không thể thiếu. Ví dụ như upload ảnh làm avatar, share video, music, hay upload các file csv, excel để xử lý,... nói chung là không thể thiếu được (yaoming) Trong Ruby on Rails, khi nhắc đến Upload, người ...

Xin chào các bạn (lay2)

Trong một Web Application, upload file là một chức năng gần như không thể thiếu.

Ví dụ như upload ảnh làm avatar, share video, music, hay upload các file csv, excel để xử lý,... nói chung là không thể thiếu được (yaoming)

Trong Ruby on Rails, khi nhắc đến Upload, người ta nghĩ tới ngay tới các gem như Carrierwave hay Paperclip bởi sự thông dụng của nó.

Hôm nay, tôi sẽ giới thiệu tới các bạn một gem khác tên là Dragonfly.

Ưu điểm:

  • Cực kỳ dễ sử dụng (ít nhất là với Carrierwave và Paperclip kappa)
  • Tích hợp ngon lành với ImageMagick - Là thư viện để xử lý ảnh mạnh mẽ (các bạn có thể đọc thêm ở đây)
  • Tự động lưu trữ thông tin về file upload.
  • Hoạt động tốt với file upload có dung lượng lớn.
  • Các validation và callback rất chi tiết.
  • ...

Trong bài viết dưới đây, tôi sẽ hướng dẫn các bạn sử dụng con "chuồn chuồn" này để upload ảnh lên local và Amazon S3. (len)

I. Demo

Các công việc phải làm:

  • Tạo web app mới, add các gem cần thiết.
  • Config và generate file.
  • Tạo layout cơ bản.
  • Xử lý ở Controller và Model để upload file lên local.
  • Xử lý để upload file lên Server của Amazon

Công cụ sử dụng:

  • Rails 4.2.1
  • Ruby 2.1.5
  • Mysql 5.6

Let's start (honho)

1. Khởi tạo

Tạo một rails app mới

rails new dragonfly_uploader

Add gem Dragonfly và bootstrap

gem "dragonfly"
gem "bootstrap-sass"

Các bạn nhớ bundle install và import cho bootstrap

# stylesheets/application.scss
@import "bootstrap-sprockets";
@import "bootstrap";

Làm cái khung cho layout phát

# views/layouts/application.html.erb
<nav class="nav navbar-inverse">
  <div class="container" >
    <div class="navbar-header">
      <%= link_to "Dragonfly", root_path, class: "navbar-brand" %>
    </div>
    <div id="navbar">
      <ul class="nav navbar-nav">
      </ul>
    </div>
  </div>
</nav>

<div class="container">
  <% flash.each do |message_type, message| %>
    <div class="alert alert-<%= message_type %>">
      <%= message %>
    </div>
  <% end %>

  <%= yield %>

</div>

Generate các file cần thiết của Dragonfly

rails g dragonfly

Sau khi chạy dòng lệnh trên, hệ thống sẽ tự động generate cho ta file config config/initializers/dragonfly.rb

Ở trong file này bạn có thể thấy có dòng

root_path: Rails.root.join('public/system/dragonfly', Rails.env)

chính là thư mục sẽ lưu trữ file upload lên mặc định. Bạn có thể đổi đường dẫn nếu muốn.

Như đã giới thiệu ở phần mở đầu, gem dragonfly có tích hợp cả thư viện imagemagick để xử lý ảnh. Tuy nhiên, nếu bạn muốn sử dụng nó, cần phải add thêm dòng sau vào file config

plugin :imagemagick

Tiếp đến, chúng ta cần tạo Model và Table để lưu trữ dữ liệu. Tôi sẽ sử dụng 3 trường

  • title: string: lưu tiêu đề của ảnh upload lên
  • image_uid: string: lưu đường dẫn tới file ảnh
  • image_size: integer: lưu thông tin về file ảnh
rails g model photo title:string image_uid:string image_size:integer

Trong model photo mới được tạo ra, ta tích hợp nó với gem dragonfly bằng cách thêm dòng code sau:

# photo.rb
dragonfly_accessor :image

chú ý rằng ta phải đặt tên theo đúng chuẩn của nó. Nếu bạn định nghĩa dragonfly_accessor :image tức là nó sẽ tự động tìm đến trường trong table có tên là image_uid.

Setup và config môi trường có vẻ đã xong xuôi (dance2)

2. Xử lý upload

Để xử lý upload, trước tiên ta generate ra controller đã, tôi đặt tên là PhotosController.

rails g controller photos index new create

PhotosController với 3 method là index, new và create. Vậy thì cứ theo quy tắc của RESTful mà add chức năng tương ứng vào từng method đó thôi (yaoming)

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
      flash[:success] = "Photo saved!"
      redirect_to photos_path
    else
      render :new
    end
  end

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

Tại trang index sẽ show ra tất cả các Photo, trang new sẽ là trang tạo mới và upload ảnh. Ảnh upload được save ở method create.

Xử lý phần View một chút

Trang index show ra list các Photos đã upload lên

# index.html.erb
<h1>List photos</h1>

<%= link_to "Upload your photo", new_photo_path, class: "btn btn-lg btn-primary" %>

<ul class="row" id="photos-list">
  <% @photos.each do |photo| %>
    <li class="col-xs-3">
      <%= link_to image_tag(photo.image.thumb('180x180#').url, alt: photo.title, class: 'img-thumbnail'),
                  photo.image.url, target: '_blank' %>
      <p><%= photo.title %></p>
    </li>
  <% end %>
</ul>

Ta add input vào trang new để điền title và upload file

# new.html.erb
<h1>New photo</h1>

<%= form_for @photo do |f| %>
  <div class="form-group">
    <%= f.label :title %>
    <%= f.text_field :title, class: "form-control", required: true %>
  </div>

  <div class="form-group">
    <%= f.label :image %>
    <%= f.file_field :image, required: true %>
  </div>

  <%= f.submit "Submit", class: "btn btn-primary btn-lg" %>
<% end %>

Về cơ bản, từ đây, ta có đã có thể upload ảnh được rồi. Tuy nhiên hiện tại chưa có validate gì đối với file upload cả. gem dragonfly sẽ hỗ trợ đắc lực trong phần này.

Trong file config/initializers/dragonfly.rb có đoạn

if defined?(ActiveRecord::Base)
  ActiveRecord::Base.extend Dragonfly::Model
  ActiveRecord::Base.extend Dragonfly::Model::Validations
end

là để extend các validate. Giờ trong model, ta chỉ cần gọi chúng ra để sử dụng.

  validates :title, presence: true
  validates :image, presence: true

  validates :image, presence: true
  validates_size_of :image, maximum: 500.kilobytes, message: "It is too big, kya!!"

  validates_property :format, of: :image, in: [:jpeg, :jpg, :png, :bmp],
                     message: "It's not my type", case_sensitive: false

Các validation của gem dragonfly bạn có thể tham khảo thêm ở đây

3. Xử lý ảnh

Như đã nói ở phần trên, dragonfly tích hợp thư viện imagemagick để xử lý ảnh. Giả dụ như ta muốn convert tất cả các file ảnh up lên thành jpg chẳng hạn

Sử dụng callback trong model

dragonfly_accessor :image do
  after_assign do |img|
    img.encode!('jpg', '-quality 80') if img.image?
  end
end

Hoặc như ta muốn rotate ảnh 90 độ (yaoming)

dragonfly_accessor :image do
  after_assign do |img|
    img.rotate!(90) if img.image?
  end
end

Ngoài ra imagemagick cũng có thể get về các thông tin về ảnh upload lên dễ dàng nhờ các method mà nó cung cấp.

Ví dụ như: image.awidth, image.height, image.aspect_ratio, image.format, ...

Các bạn có thể đọc thêm về các hàm ở đây

4. Kết quả upload file lên local

OK, vậy là ta đã hoàn tất việc upload ảnh lên server của chúng ta, sau đây là kết quả.

Trang index, hiển thị đường dẫn tới trang upload và hiển thị list các ảnh đã upload

index.png



Trang new, khi mà ta upload file không phải ảnh và có dung lượng quá lớn

new.png

5. Upload ảnh lên server của Amazon (Amazon S3)

S3 là dịch vụ lưu trữ dữ liệu trực tuyến của Amazon, người dùng có thể sử dụng các interface được cung cấp để lưu trữ dữ liệu server được cấp. Người dùng có thể truy cập vào dữ liệu của mình từ bất cứ nơi đâu, bất cứ lúc nào thông qua giao diện web.

Mặc định, dragonfly sẽ upload ảnh của bạn lên local, theo đường dẫn public/system/dragonfly.

Ở phần này, tôi sẽ hướng dẫn các bạn upload lên bên thứ 3 là server Amazon.

Để làm được điều đó, ta add thêm gem dragonfly-s3_data_store

gem "dragonfly-s3_data_store"

dragonfly cũng có gem hỗ trợ upload lên những nơi khác như

  • Dropbox: gem dragonfly-dropbox_data_store
  • Cloudinary: gem dragonfly-cloudinary
  • Couch: gem dragonfly-couch_data_store

Và tất nhiên, để kết nối được đến Amazon S3, ta cần có tài khoản ở đó. Các bạn có thể truy cập https://aws.amazon.com/ để đăng ký tài khoản. (cần có 1 cái thẻ debit nhé (yaoming))

Sau khi tạo tài khoản thành công, bạn vào AWS management, chọn service S3 như trong hình

amazon.png



Tiếp tục chọn "Create Bucket", và chọn server location

create_bucket.png

Giờ hãy mở mục "Your profile - Security Credentials" -> "Access Keys" -> "Create New Access Key" -> "Download" về.

Browser sẽ down về 1 file csv, trong đó có chứa AccessKeyID và SecretKey để bạn nhúng vào trong ứng dụng.

Giờ đã có đầy đủ công cụ trong tay, trong file config/initializers/dragonfly.rb ta config lại một chút.

 datastore :s3,
            bucket_name: ENV["BUCKET_NAME"],
            access_key_id: ENV["AWS_KEY_ID"],
            secret_access_key: ENV["SECRET_KEY"],
            region: "ap-southeast-1",
            url_scheme: 'https'

Trong đó:

  • ENV["BUCKET_NAME"]: là tên của bucket mình khởi tạo ở trong AWS management.
  • ENV["AWS_KEY_ID"]: là key_id trong file csv mình download về.
  • ENV["SECRET_KEY"]: là secret_key trong file csv
  • region: là vị trí server của mình, của tôi là "ap-southeast-1"

Ở trang index hiện tại, ta đang cho url của ảnh là server local của mình, cần phải đổi lại.

<%= link_to image_tag(photo.image.thumb('180x180#').url, alt: photo.title, class: 'img-thumbnail'), photo.image.remote_url %>

Ta dùng photo.image.remote_url thay thế cho photo.image.url là xong.

Kết quả sau khi upload ảnh

index2.png

Ta click vào ảnh mà ta upload lên S3, nó sẽ dẫn tới trang như sau

pic_on_s3.png

File đã được upload thành công!

GGWP (honho)

Source

  • Github: https://github.com/NguyenTanDuc/dragonfly/tree/develop

Nguồn tham khảo

  • https://github.com/markevans/dragonfly
  • https://github.com/markevans/dragonfly-s3_data_store
  • http://astrails.com/blog/2014/7/28/dragonfly-imagemagick-and-memory-bloat
  • http://www.sitepoint.com/file-uploads-dragonfly/
0