Upload với Rails và Paperclip
() translation by (you can also view the original English article)
Đây là bài cuối cùng trong loạt bài "Uploading với Rails". Trong một vài tháng trước, chúng ta đã thảo luận về Shrine, Dragonfly và Carrierwave gem. Hôm nay, chúng ta sẽ bàn tới Paperclip của công ty Thoughtbot, nơi đang quản lý các gem như FactoryGirl và Bourbon.
Paperclip có lẽ là giải pháp quản lý kèm theo phổ biến nhất cho Rails (hơn 13 triệu lượt download), và có nguyên do rõ ràng cho điều này: nó có nhiều tính năng, một cộng đồng tuyệt vời và hệ thống tài liệu dễ hiểu. Vì vậy hi vọng bạn sẽ thấy hào hứng khi tìm hiểu về gem này!
Trong bài viết này bạn sẽ tìm hiểu làm thế nào để:
- Chuẩn bị cho việc cài đặt Paperclip
- Tích hợp Paperclip vào một ứng dụng Rails
- Thêm phê chuẩn đính kèm
- Tạo ảnh nhỏ (thumbnail) và xử lý hình ảnh
- Ẩn đường dẫn (URL)
- Lưu trữ file đính kèm trên Amazon S3
- Bảo vệ an toàn file trên đám mây bằng cách đưa ra các logic cấp phép
Mã nguồn cho bài viết này có sẵn trên GitHub.
Chuẩn bị
Trước khi chúng ta tìm hiểu sâu vào phần mã nguồn, trước tiên hãy thảo luận về một số điều cần lưu ý mà bạn cần biết để có thể làm việc với Paperclip thành công:
- Phiên bản mới nhất của Paperclip hỗ trợ Rails 4.2+ và Ruby 2.1+. Gem này cũng có thể sử dụng mà không cần Rails.
- ImageMagick phải được cài đặt trên máy tính của bạn (nó có sẵn cho tất cả các nền tảng lớn), và Paperclip có thể truy cập vào nó.
- Lệnh
file
có sẵn từ dòng lệnh. Đối với hệ điều hành Windows, phần này có sẵn thông qua Development Kit cho các nhà phát triển, vì vậy hãy làm theo những hướng dẫn này nếu bạn chưa cài đặt DevKit.
Khi bạn đã sẵn sàng, hãy tiếp tục và tạo một ứng dụng Rails mới (Tôi sẽ sử dụng Rails 5.0.2) không sử dụng bộ thử nghiệm mặc định:
1 |
rails new UploadingWithPaperclip -T
|
Tích hợp Paperclip
Đưa vào gem Paperclip:
Gemfile
1 |
gem "paperclip", "~> 5.1" |
Cài đặt nó:
1 |
bundle install
|
Giả sử chúng ta đang tạo một ứng dụng quản lý sách trong đó có một danh sách các cuốn sách. Mỗi cuốn sách có một tiêu đề, một phần mô tả và tên tác giả, cùng với một hình ảnh bìa. Để bắt đầu, tổng hợp và áp dụng sự di trú sau:
1 |
rails g model Book title:string description:text image:attachment author:string |
2 |
rails db:migrate |
Lưu ý loại attachment
được trình bày cho chúng tabằng Paperclip. Nó sẽ tạo ra bốn trường cho chúng ta:
tên_file_ảnh
kích_thước_file_ảnh
loại_nội_dung_ảnh
thời_gian_cập_nhật_ảnh
Trái ngược với các gem Shrine và Carrierwave, Paperclip không có một tập tin riêng biệt với các cấu hình. Tất cả cài đặt được xác định bên trong các mô hình bằng cách sử dụng phương pháp has_attached_file
, vì vậy ta hãy thêm nó ngay bây giờ:
models/book.rb
1 |
has_attached_file :image |
Trước khi tiếp tục tới phần chính, chúng ta hãy tạo ra một bộ điều khiển cùng với một số view và route.
Tạo Controller, Views, and Routes
Phần điều khiển của chúng ta rất cơ bản:
books_controller.rb
1 |
class BooksController < ApplicationController |
2 |
before_action :set_book, only: [:show, :download] |
3 |
|
4 |
def index |
5 |
@books = Book.order('created_at DESC') |
6 |
end
|
7 |
|
8 |
def new |
9 |
@book = Book.new |
10 |
end
|
11 |
|
12 |
def show |
13 |
end
|
14 |
|
15 |
def create |
16 |
@book = Book.new(book_params) |
17 |
if @book.save |
18 |
redirect_to books_path |
19 |
else
|
20 |
render :new |
21 |
end
|
22 |
end
|
23 |
|
24 |
private
|
25 |
|
26 |
def book_params |
27 |
params.require(:book).permit(:title, :description, :image, :author) |
28 |
end
|
29 |
|
30 |
def set_book |
31 |
@book = Book.find(params[:id]) |
32 |
end
|
33 |
end
|
Đây là một index view và một partial:
views/books/index.html.erb
1 |
<h1>Bookshelf</h1> |
2 |
|
3 |
<%= link_to 'Add book', new_book_path %> |
4 |
<ul>
|
5 |
<%= render @books %> |
6 |
</ul>
|
views/books/_book.html.erb
1 |
<li>
|
2 |
<strong><%= link_to book.title, book_path(book) %></strong> by <%= book.author %> |
3 |
</li>
|
Bây giờ tới các route:
config/routes.rb
1 |
Rails.application.routes.draw do |
2 |
resources :books |
3 |
root to: 'books#index' |
4 |
end |
Rất tốt! Bây giờ hãy xử lý tới phần chính và tạo mã action mới và một biểu mẫu.
Tải lên tập tin
Nhìn chung, tải tập tin với Paperclip rất dễ. Bạn chỉ cần cho phép thuộc tính tương ứng (trong trường hợp này, chính là thuộc tính image
và chúng ta đã cho phép nó) và đưa ra một trường tập tin trong biểu mẫu của bạn. Hãy cũng làm điều này bây giờ:
views/books/new.html.erb
1 |
<h1>Add book</h1> |
2 |
|
3 |
<%= render 'form', book: @book %> |
views/books/_form.html.erb
1 |
<%= form_for book do |f| %> |
2 |
<div>
|
3 |
<%= f.label :title %> |
4 |
<%= f.text_field :title %> |
5 |
</div>
|
6 |
|
7 |
<div>
|
8 |
<%= f.label :author %> |
9 |
<%= f.text_field :author %> |
10 |
</div>
|
11 |
|
12 |
<div>
|
13 |
<%= f.label :description %> |
14 |
<%= f.text_area :description %> |
15 |
</div>
|
16 |
|
17 |
<div>
|
18 |
<%= f.label :image %> |
19 |
<%= f.file_field :image %> |
20 |
</div>
|
21 |
|
22 |
<%= f.submit %> |
23 |
<% end %> |
Với thiết lập này, bạn đã có thể bắt đầu thực hiện các nội dung tải lên, nhưng đó là một ý tưởng tốt để đưa ra một số xác nhận nữa.
Thêm xác nhận (Validations)
Validations trong Paperclip có thể được viết bằng cách sử dụng các helper cũ như validates_attachment_presence
và validates_attachment_content_type
hoặc bằng cách sử dụng phương pháp validates_attachment
để xác định nhiều quy tắc cùng một lúc. Chúng ta hãy sử dụng lựa chọn cuối:
models/book.rb
1 |
validates_attachment :image, |
2 |
content_type: { content_type: /\Aimage\/.*\z/ }, |
3 |
size: { less_than: 1.megabyte } |
Như bạn có thể thấy thì phần code trên đây khá đơn giản. Chúng ta yêu cầu các tập tin phải là một hình ảnh có kích thước nhỏ hơn 1 megabyte. Lưu ý rằng nếu xác nhận thất bại, không có phần xử lý tiếp theo nào sẽ được thực hiện. Paperclip đã có một số tin nhắn báo lỗi được cài đặt cho ngôn ngữ tiếng Anh, nhưng nếu bạn muốn hỗ trợ các ngôn ngữ khác, hãy thêm gem paperclip-i18n vào phần Gemfile của bạn.
Một điều quan trọng cần đề cập đến là Paperclip yêu cầu bạn xác nhận loại nội dung hoặc tên tập tin của tất cả phần đính kèm, nếu không nó sẽ đưa ra lỗi. Nếu bạn 100% chắc chắn rằng bạn không cần một xác nhận (validations) như vậy (hiếm khi xảy ra), hãy sử dụng do_not_validate_attachment_file_type
để thể hiện một cách rõ ràng các trường không cần phải kiểm tra.
Sau khi thêm validations, hãy cài đặt hiển thị thông báo lỗi trong mẫu của chúng ta:
views/shared/_errors.html.erb
1 |
<% if object.errors.any? %> |
2 |
<h3>Some errors were found:</h3> |
3 |
<ul>
|
4 |
<% object.errors.full_messages.each do |message| %> |
5 |
<li><%= message %></li> |
6 |
<% end %> |
7 |
</ul>
|
8 |
<% end %> |
views/books/_form.html.erb
1 |
<%= render 'shared/errors', object: book %> |
Hiển thị hình ảnh
Được rồi, vậy bây giờ hình ảnh được tải lên sẽ được hiển thị bằng cách nào đó. Điều này được thực hiện bằng cách sử dụng helper image_tag
và một phương pháp url
. Tạo một view hiển thị:
views/books/show.html.erb
1 |
<h1><%= @book.title %> by <%= @book.author %></h1> |
2 |
|
3 |
<%= image_tag(@book.image.url) if @book.image.exists? %> |
4 |
|
5 |
<p><%= @book.description %></p> |
Chúng ta đang hiển thị một hình ảnh nếu nó thực sự tồn tại trên ổ đĩa. Hơn nữa, nếu bạn đang sử dụng lưu trữ đám mây, thì Paperclip sẽ thực hiện một yêu cầu kết nối mạng và kiểm tra sự tồn tại của tập tin. Tất nhiên, thao tác này có thể mất hời gian, vì vậy bạn có thể sử dụng present?
hoặc file?
như là các phương pháp thay thế: đơn giản là chúng sẽ đảm bảo rằng các trường image_file_name
là thích ứng với một số nội dung.
Ẩn đường dẫn (URL)
Theo mặc định, tất cả các tập tin đính kèm được lưu trữ bên trong các thư mục public/system, do đó, có thể bạn sẽ muốn loại bỏ nó từ hệ thống điều khiển phiên bản:
.gitignore
1 |
public/system |
Tuy nhiên, hiển thị một đường dẫn (URL) đầy đủ cho các tập tin không phải lúc nào cũng là một ý tưởng tốt, và bạn có thể cần phải ẩn nó đi bằng cách nào đó. Cách dễ nhất để cho phép ẩn đi là cung cấp hai tham số đối với has_attached_file method
:
models/book.rb
1 |
url: "/system/:hash.:extension", |
2 |
hash_secret: "longSecretString" |
Các giá trị thích hợp sẽ được nội suy vào url
tự động. hash_secret
là một trường cần thiết, và là cách dễ nhất để tạo ra nó là bằng cách sử dụng:
1 |
rails secret |
Làm việc với các style
Trong nhiều trường hợp, hình ảnh thủ nhỏ thường được hiển thị với một số định dạng trước về chiều rộng và chiều cao để tiết kiệm băng thông. Paperclip giải quyết điều này bằng cách sử dụng các styles: mỗi style có một tên và một loạt các quy tắc, như kích thước, định dạng, chất lượng, vv.
Giả sử rằng chúng tôi muốn hình ảnh ban đầu và hình thu nhỏ của nó được chuyển đổi sang định dạng JPEG. Hình thu nhỏ nên được cắt thành kích thước 300x300px:
models/book.rb
1 |
has_attached_file :image, |
2 |
styles: { |
3 |
thumb: ["300x300#", :jpeg], |
4 |
original: [:jpeg] |
5 |
} |
#
là một thiết lập hình học có nghĩa: "cắt bớt nếu cần thiết trong khi duy trì tỷ lệ các chiều."
Chúng ta cũng có thể cung cấp các tùy chọn chuyển đổi bổ sung cho từng style. Ví dụ, chúng ta hãy đưa ra 70% chất lượng cho hình thu nhỏ trong khi loại bỏ tất cả phần siêu dữ liệu và 90% cho chất lượng hình ảnh ban đầu để làm cho nó nhỏ hơn một chút:
models/book.rb
1 |
has_attached_file :image, |
2 |
styles: { |
3 |
thumb: ["300x300#", :jpeg], |
4 |
original: [:jpeg] |
5 |
}, |
6 |
convert_options: { |
7 |
thumb: "-quality 70 -strip", |
8 |
original: "-quality 90" |
9 |
} |
Tốt rồi! Hiển thị hình thu nhỏ và cung cấp các liên kết đến hình ảnh ban đầu:
views/books/show.html.erb
1 |
<%= link_to(image_tag(@book.image.url(:thumb)), @book.image.url, target: '_blank') if @book.image.exists? %> |
Lưu ý rằng không giống như Carrierwave, Paperclip không cho phép bạn viết @book.image.thumb.url
.
Nếu vì một số lý do nào đó mà bạn muốn cập nhật một cách thủ công hình ảnh được tải lên, thì bạn có thể sử dụng các lệnh sau đây để làm mới hình thu nhỏ, thêm style bị thiếu hoặc làm mới tất cả hình ảnh:
rake paperclip:refresh:thumbnails CLASS=Book
rake paperclip:refresh:missing_styles CLASS=Book
rake paperclip:refresh CLASS=Book
Các tập tin lưu trữ trên đám mây
Giống như tất cả các giải pháp tương tự, Paperclip cho phép bạn tải tập tin lên đám mây. Ngoài ra, nó còn hỗ trợ cho các bộ điều hợp mạng S3 và Fog và có cả các gem của bên thứ ba cho Azure và Dropbox. Trong phần này, tôi sẽ chỉ cho bạn cách để tích hợp Paperclip với Amazon S3. Đầu tiên, đưa vào gem aws-sdk:
1 |
gem 'aws-sdk' |
Cài đặt nó:
1 |
bundle install
|
Tiếp theo, cung cấp một bộ lựa chọn mới cho phương pháp has_attached_file
:
models/book.rb
1 |
has_attached_file :image, |
2 |
styles: { |
3 |
thumb: ["300x300#", :jpeg], |
4 |
original: [:jpeg] |
5 |
}, |
6 |
convert_options: { |
7 |
thumb: "-quality 70 -strip", |
8 |
original: "-quality 90" |
9 |
}, |
10 |
storage: :s3, |
11 |
s3_credentials: { |
12 |
access_key_id: ENV["S3_KEY"], |
13 |
secret_access_key: ENV["S3_SECRET"], |
14 |
bucket: ENV["S3_BUCKET"] |
15 |
}, |
16 |
s3_region: ENV["S3_REGION"] |
Ở đây, tôi là sử dụng gem dotenv vịn để thiết lập các biến môi trường. Bạn có thể cung cấp tất cả các giá trị trực tiếp bên trong các model, nhưng không làm cho nó có sẵn một cách công khai.
Điều thú vị là s3_credentials
cũng chấp nhận một đường dẫn đến tập tin YAML chứa các keys của bạn và một tên bucket. Hơn nữa, bạn có thể thiết lập các giá trị khác nhau cho các môi trường khác nhau như thế này:
1 |
development: |
2 |
access_key_id: key1 |
3 |
secret_access_key: secret1 |
4 |
production: |
5 |
access_key_id: key2 |
6 |
secret_access_key: secret2 |
Chính là nó! Tất cả các tệp bạn tải lên bây giờ sẽ nằm trong bucket S3 của bạn.
Bảo vệ các tập tin trong đám mây
Giả sử bạn không muốn tập tin được tải lên của bạn có sẵn cho tất cả mọi người. Theo mặc định, tất cả các nội dung tải lên vào các đám mây được đánh dấu công khai, có nghĩa là bất cứ ai cũng có thể mở các tập tin thông qua liên kết trực tiếp. Nếu bạn muốn giới thiệu một số phép logic và kiểm tra xem những ai có thể xem các tập tin, thiết lập tùy chọn s3_permissions
thành :private
như thế này:
1 |
has_attached_file :image, |
2 |
styles: { |
3 |
thumb: ["300x300#", :jpeg], |
4 |
original: [:jpeg] |
5 |
}, |
6 |
convert_options: { |
7 |
thumb: "-quality 70 -strip", |
8 |
original: "-quality 90" |
9 |
}, |
10 |
storage: :s3, |
11 |
s3_credentials: { |
12 |
access_key_id: ENV["S3_KEY"], |
13 |
secret_access_key: ENV["S3_SECRET"], |
14 |
bucket: ENV["S3_BUCKET"] |
15 |
}, |
16 |
s3_region: ENV["S3_REGION"], |
17 |
s3_permissions: :private |
Bây giờ, ngoại trừ bạn thì không ai sẽ có thể xem các tập tin. Do đó, hãy tạo một hành động mới download
cho BooksController
:
books_controller.rb
1 |
def download |
2 |
redirect_to @book.image.expiring_url |
3 |
end |
Hành động này chỉ đơn giản là sẽ chuyển hướng người dùng đến hình ảnh thông qua một liên kết hết hạn. Sử dụng cách tiếp cận này, bây giờ bạn có thể giới thiệu bất kỳ logic cho phép sử dụng các gem như như CanCanCan hay Pundit.
Đừng quên để thiết lập route phụ:
config/routes.rb
1 |
resources :books do |
2 |
member do |
3 |
get 'download' |
4 |
end |
5 |
end |
Phần trợ giúp nên được sử dụng như thế này:
1 |
link_to('View image', download_book_path(@book), target: '_blank') |
Kết luận
Chúng tôi đã đến phần cuối cùng của bài viết này! Hôm nay chúng tôi đã tìm hiểu về Paperclip, một giải pháp quản lý tập tin đính kèm cho Rails, với các ví dụ thực tế và thảo luận về các khái niệm chính. Vẫn còn nhiều điều về gem này, vì vậy hãy xem thêm tài liệu hướng dẫn.
Ngoài ra, tôi khuyên bạn nên ghé thăm trang wiki của Paperclip vì nó có một danh sách các mục hướng dẫn "làm thế nào" và một loạt các liên kết đến các gem của bên thứ ba hỗ trợ Azure và Cloudinary và cho phép bạn dễ dàng tối thiểu hóa các tập tin được tải lên.
Cảm ơn vì đã đồng hành cùng với tôi, và hẹn gặp lại bạn!