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
Trang new, khi mà ta upload file không phải ảnh và có dung lượng quá lớn
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
Tiếp tục chọn "Create Bucket", và chọn server location
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
Ta click vào ảnh mà ta upload lên S3, nó sẽ dẫn tới trang như sau
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/