Có rất nhiều gem tải lên tệp trên mạng như CarrierWave, Paperclip và Dragonfly. Tất cả chúng đều có chi tiết cụ thể và có thể bạn đã sử dụng ít nhất một trong những loại đá quý này.
Tuy nhiên, hôm nay, tôi muốn giới thiệu một giải pháp tương đối mới, nhưng rất hay có tên là Shrine, được tạo ra bởi Janko Marohnić. Trái ngược với một số đá quý tương tự khác, nó có cách tiếp cận mô-đun, nghĩa là mọi tính năng đều được đóng gói dưới dạng mô-đun (hoặc plugin trong thuật ngữ của Shrine). Bạn muốn hỗ trợ xác thực? Thêm một plugin. Bạn muốn thực hiện một số xử lý tệp? Thêm một plugin! Tôi thực sự thích cách tiếp cận này vì nó cho phép bạn dễ dàng kiểm soát các tính năng nào sẽ khả dụng cho mô hình nào.
Trong bài viết này, tôi sẽ chỉ cho bạn cách:
- tích hợp Shrine vào ứng dụng Rails
- định cấu hình nó (toàn cầu và cho mỗi người tải lên)
- thêm khả năng tải tệp lên
- xử lý tệp
- thêm các quy tắc xác thực
- lưu trữ siêu dữ liệu bổ sung và sử dụng lưu trữ tệp trên đám mây với Amazon S3
Mã nguồn của bài viết này có sẵn trên GitHub.
Bạn có thể tìm thấy bản demo hoạt động tại đây.
Tích hợp đền
Để bắt đầu, hãy tạo một ứng dụng Rails mới mà không có bộ thử nghiệm mặc định:
rails new FileGuru -T
Tôi sẽ sử dụng Rails 5 cho bản demo này, nhưng hầu hết các khái niệm cũng áp dụng cho phiên bản 3 và 4.
Thả đá quý Shrine vào Gemfile của bạn:
gem "shrine"
Sau đó chạy:
bundle install
Bây giờ chúng ta sẽ yêu cầu một mô hình mà tôi sẽ gọi là Photo
. Shrine lưu trữ tất cả thông tin liên quan đến tệp trong một cột văn bản đặc biệt kết thúc bằng _data
hậu tố. Tạo và áp dụng di chuyển tương ứng:
rails g model Photo title:string image_data:text rails db:migrate
Lưu ý rằng đối với các phiên bản Rails cũ hơn, lệnh sau phải là:
rake db:migrate
Các tùy chọn cấu hình cho Shrine có thể được đặt trên toàn cầu và theo từng mô hình. Tất nhiên, cài đặt chung đã được thực hiện bên trong tệp trình khởi tạo. Ở đó, tôi sẽ kết nối các tệp và plugin cần thiết . Các plugin được sử dụng trong Shrine để trích xuất các phần chức năng thành các mô-đun riêng biệt, cho phép bạn toàn quyền kiểm soát tất cả các tính năng có sẵn. Ví dụ:có các plugin để xác thực, xử lý hình ảnh, tệp đính kèm trong bộ nhớ đệm và hơn thế nữa.
Hiện tại, hãy thêm hai plugin:một để hỗ trợ ActiveRecord và một plugin khác để thiết lập ghi nhật ký. Chúng sẽ được đưa vào toàn cầu. Ngoài ra, hãy thiết lập lưu trữ hệ thống tệp:
config / initializers / Temple.rb
require "shrine" require "shrine/storage/file_system" Shrine.plugin :activerecord Shrine.plugin :logging, logger: Rails.logger Shrine.storages = { cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"), store: Shrine::Storage::FileSystem.new("public", prefix: "uploads/store"), }
Logger sẽ chỉ xuất ra một số thông tin gỡ lỗi bên trong bảng điều khiển cho bạn biết bạn đã dành bao nhiêu thời gian để xử lý một tệp. Điều này có thể hữu ích.
2015-10-09T20:06:06.676Z #25602: STORE[cache] ImageUploader[:avatar] User[29543] 1 file (0.1s) 2015-10-09T20:06:06.854Z #25602: PROCESS[store]: ImageUploader[:avatar] User[29543] 1-3 files (0.22s) 2015-10-09T20:06:07.133Z #25602: DELETE[destroyed]: ImageUploader[:avatar] User[29543] 3 files (0.07s)
Tất cả các tệp đã tải lên sẽ được lưu trữ bên trong tệp công khai / tệp tải lên danh mục. Tôi không muốn theo dõi các tệp này trong Git, vì vậy hãy loại trừ thư mục này:
.gitignore
public/uploads
Bây giờ, hãy tạo một lớp "người tải lên" đặc biệt sẽ lưu trữ các cài đặt dành riêng cho mô hình. Hiện tại, lớp này sẽ trống:
models / image_uploader.rb
class ImageUploader < Shrine end
Cuối cùng, bao gồm lớp này bên trong Photo
mô hình:
models / photo.rb
include ImageUploader[:image]
[:image]
thêm một thuộc tính ảo sẽ được sử dụng khi xây dựng một biểu mẫu. Dòng trên có thể được viết lại thành:
include ImageUploader.attachment(:image) # or include ImageUploader::Attachment.new(:image)
Tốt đẹp! Giờ đây, mô hình đã được trang bị chức năng của Shrine và chúng ta có thể tiến hành bước tiếp theo.
Bộ điều khiển, Chế độ xem và Đường dẫn
Đối với mục đích của bản trình diễn này, chúng tôi sẽ chỉ cần một bộ điều khiển để quản lý ảnh. index
trang sẽ đóng vai trò là trang gốc:
pages_controller.rb
class PhotosController < ApplicationController def index @photos = Photo.all end end
Chế độ xem:
views / photos / index.html.erb
<h1>Photos</h1> <%= link_to 'Add Photo', new_photo_path %> <%= render @photos %>
Để hiển thị @photos
mảng, một phần là bắt buộc:
views / photos / _photo.html.erb
<div> <% if photo.image_data? %> <%= image_tag photo.image_url %> <% end %> <p><%= photo.title %> | <%= link_to 'Edit', edit_photo_path(photo) %></p> </div>
image_data?
là một phương thức được ActiveRecord trình bày để kiểm tra xem bản ghi có hình ảnh hay không.
image_url
là một phương thức Shrine chỉ đơn giản là trả về một đường dẫn đến hình ảnh ban đầu. Tất nhiên, thay vào đó, tốt hơn là hiển thị một hình thu nhỏ nhỏ, nhưng chúng tôi sẽ giải quyết vấn đề đó sau.
Thêm tất cả các tuyến đường cần thiết:
config / route.rb
resources :photos, only: [:new, :create, :index, :edit, :update] root 'photos#index'
Vậy là xong — phần cơ bản đã xong, và chúng ta có thể chuyển sang phần thú vị!
Tải lên tệp
Trong phần này, tôi sẽ chỉ cho bạn cách thêm chức năng để thực sự tải lên tệp. Các hành động của bộ điều khiển rất đơn giản:
photos_controller.rb
def new @photo = Photo.new end def create @photo = Photo.new(photo_params) if @photo.save flash[:success] = 'Photo added!' redirect_to photos_path else render 'new' end end
Bí quyết duy nhất là đối với các thông số mạnh, bạn phải cho phép image
thuộc tính ảo, không phải image_data
.
photos_controller.rb
private def photo_params params.require(:photo).permit(:title, :image) end
Tạo new
xem:
views / photos / new.html.erb
<h1>Add photo</h1> <%= render 'form' %>
Một phần của biểu mẫu cũng rất nhỏ:
views / photos / _form.html.erb
<%= form_for @photo do |f| %> <%= render "shared/errors", object: @photo %> <%= f.label :title %> <%= f.text_field :title %> <%= f.label :image %> <%= f.file_field :image %> <%= f.submit %> <% end %>
Một lần nữa, xin lưu ý rằng chúng tôi đang sử dụng image
thuộc tính, không phải image_data
.
Cuối cùng, thêm một phần khác để hiển thị lỗi:
views / shared / _errors.html.erb
<% if object.errors.any? %> <h3>The following errors were found:</h3> <ul> <% object.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> <% end %>
Đây là phần lớn tất cả — bạn có thể bắt đầu tải hình ảnh lên ngay bây giờ.
Xác thực
Tất nhiên, nhiều công việc phải được thực hiện để hoàn thành ứng dụng demo. Vấn đề chính là người dùng có thể tải lên hoàn toàn bất kỳ loại tệp nào với bất kỳ kích thước nào, điều này không đặc biệt lớn. Do đó, hãy thêm một plugin khác để hỗ trợ xác thực:
config / inititalizers / temple.rb
Shrine.plugin :validation_helpers
Thiết lập logic xác thực cho ImageUploader
:
models / image_uploader.rb
Attacher.validate do validate_max_size 1.megabyte, message: "is too large (max is 1 MB)" validate_mime_type_inclusion ['image/jpg', 'image/jpeg', 'image/png'] end
Tôi chỉ cho phép tải lên các hình ảnh JPG và PNG có dung lượng dưới 1MB. Chỉnh sửa các quy tắc này khi bạn thấy phù hợp.
Các loại MIME
Một điều quan trọng khác cần lưu ý là, theo mặc định, Shrine sẽ xác định kiểu MIME của tệp bằng cách sử dụng tiêu đề HTTP Loại Nội dung. Tiêu đề này được trình duyệt chuyển qua và chỉ được đặt dựa trên phần mở rộng của tệp, điều này không phải lúc nào cũng mong muốn.
Nếu bạn muốn xác định kiểu MIME dựa trên nội dung của tệp, thì hãy sử dụng một plugin có tên xác định_mime_type. Tôi sẽ đưa nó vào bên trong lớp trình tải lên, vì các mô hình khác có thể không yêu cầu chức năng này:
models / image_uploader.rb
plugin :determine_mime_type
Plugin này sẽ sử dụng tiện ích tệp của Linux theo mặc định.
Lưu hình ảnh đính kèm
Hiện tại, khi người dùng gửi biểu mẫu có dữ liệu không chính xác, biểu mẫu sẽ được hiển thị lại với các lỗi hiển thị ở trên. Tuy nhiên, vấn đề là hình ảnh đính kèm sẽ bị mất và người dùng sẽ cần chọn lại hình ảnh đó. Điều này rất dễ sửa chữa bằng cách sử dụng một plugin khác có tên là cached_attachment_data:
models / image_uploader.rb
plugin :cached_attachment_data
Bây giờ, chỉ cần thêm một trường ẩn vào biểu mẫu của bạn.
views / photos / _form.html.erb
<%= f.hidden_field :image, value: @photo.cached_image_data %> <%= f.label :image %> <%= f.file_field :image %>
Chỉnh sửa ảnh
Bây giờ hình ảnh có thể được tải lên, nhưng không có cách nào để chỉnh sửa chúng, vì vậy hãy sửa chữa nó ngay lập tức. Các hành động của bộ điều khiển tương ứng hơi tầm thường:
photos_controller.rb
def edit @photo = Photo.find(params[:id]) end def update @photo = Photo.find(params[:id]) if @photo.update_attributes(photo_params) flash[:success] = 'Photo edited!' redirect_to photos_path else render 'edit' end end
_form
giống nhau một phần sẽ được sử dụng:
views / photos / edit.html.erb
<h1>Edit Photo</h1> <%= render 'form' %>
Đẹp, nhưng chưa đủ:người dùng vẫn không thể xóa hình ảnh đã tải lên. Để cho phép điều này, chúng tôi sẽ cần — đoán xem — một plugin khác:
models / image_uploader.rb
plugin :remove_attachment
Nó sử dụng một thuộc tính ảo có tên là :remove_image
, vì vậy hãy cho phép nó bên trong bộ điều khiển:
photos_controller.rb
def photo_params params.require(:photo).permit(:title, :image, :remove_image) end
Bây giờ, chỉ cần hiển thị một hộp kiểm để xóa hình ảnh nếu bản ghi có tệp đính kèm tại chỗ:
views / photos / _form.html.erb
<% if @photo.image_data? %> Remove attachment: <%= f.check_box :remove_image %> <% end %>
Tạo hình ảnh thu nhỏ
Hiện tại, chúng tôi hiển thị hình ảnh gốc, đây không phải là cách tốt nhất để xem trước:ảnh có thể lớn và chiếm quá nhiều dung lượng. Tất nhiên, bạn có thể chỉ cần sử dụng CSS width
và height
nhưng đó cũng là một ý tưởng tồi. Bạn thấy đấy, ngay cả khi hình ảnh được đặt thành nhỏ bằng cách sử dụng các kiểu, người dùng vẫn cần tải xuống tệp gốc, tệp này có thể khá lớn.
Do đó, tốt hơn là tạo một hình ảnh xem trước nhỏ ở phía máy chủ trong quá trình tải lên ban đầu. Điều này liên quan đến hai plugin và hai viên ngọc bổ sung. Đầu tiên, hãy thả những viên đá quý:
gem "image_processing" gem "mini_magick", ">= 4.3.5"
Image_processing là một viên ngọc đặc biệt được tạo ra bởi tác giả của Shrine. Nó trình bày một số phương pháp trợ giúp cấp cao để thao tác trên hình ảnh. Đến lượt nó, viên ngọc này dựa trên mini_magick, một trình bao bọc Ruby cho ImageMagick. Như bạn đã đoán, bạn sẽ cần ImageMagick trên hệ thống của mình để chạy bản demo này.
Cài đặt những viên ngọc mới này:
bundle install
Bây giờ hãy bao gồm các plugin cùng với các phụ thuộc của chúng:
models / image_uploader.rb
require "image_processing/mini_magick" class ImageUploader < Shrine include ImageProcessing::MiniMagick plugin :processing plugin :versions # other code... end
Xử lý là plugin cho phép chúng ta thao tác một hình ảnh (ví dụ:thu nhỏ, xoay, chuyển đổi sang định dạng khác, v.v.). Đổi lại, các phiên bản cho phép chúng ta có một hình ảnh trong các biến thể khác nhau. Đối với bản demo này, hai phiên bản sẽ được lưu trữ:"original" và "thumb" (đã thay đổi kích thước thành 300x300
).
Đây là mã để xử lý một hình ảnh và lưu trữ hai phiên bản của nó:
models / image_uploader.rb
class ImageUploader < Shrine process(:store) do |io, context| { original: io, thumb: resize_to_limit!(io.download, 300, 300) } end end
resize_to_limit!
là một phương thức được cung cấp bởi đá quý image_processing. Nó chỉ đơn giản là thu nhỏ hình ảnh xuống 300x300
nếu nó lớn hơn và không làm gì nếu nó nhỏ hơn. Hơn nữa, nó vẫn giữ nguyên tỷ lệ khung hình ban đầu.
Bây giờ khi hiển thị hình ảnh, bạn chỉ cần cung cấp :original
hoặc :thumb
đối số cho image_url
phương pháp:
views / photos / _photo.html.erb
<div> <% if photo.image_data? %> <%= image_tag photo.image_url(:thumb) %> <% end %> <p><%= photo.title %> | <%= link_to 'Edit', edit_photo_path(photo) %></p> </div>
Điều tương tự có thể được thực hiện bên trong biểu mẫu:
views / photos / _form.html.erb
<% if @photo.image_data? %> <%= image_tag @photo.image_url(:thumb) %> Remove attachment: <%= f.check_box :remove_image %> <% end %>
Để tự động xóa các tệp đã xử lý sau khi tải lên hoàn tất, bạn có thể thêm một plugin có tên delete_raw:
models / image_uploader.rb
plugin :delete_raw
Siêu dữ liệu của hình ảnh
Ngoài việc thực sự hiển thị một hình ảnh, bạn cũng có thể tìm nạp siêu dữ liệu của nó. Ví dụ:hãy hiển thị kích thước và kiểu MIME của ảnh gốc:
views / photos / _photo.html.erb
<div> <% if photo.image_data? %> <%= image_tag photo.image_url(:thumb) %> <p> Size <%= photo.image[:original].size %> bytes<br> MIME type <%= photo.image[:original].mime_type %><br> </p> <% end %> <p><%= photo.title %> | <%= link_to 'Edit', edit_photo_path(photo) %></p> </div>
Còn về kích thước của nó? Rất tiếc, chúng không được lưu trữ theo mặc định, nhưng điều này có thể thực hiện được với một plugin có tên store_dimensions.
Kích thước của hình ảnh
Plugin store_dimensions dựa trên đá quý Fastimage, vì vậy hãy kết nối nó ngay bây giờ:
gem 'fastimage'
Đừng quên chạy:
bundle install
Bây giờ chỉ cần bao gồm plugin:
models / image_uploader.rb
plugin :store_dimensions
Và hiển thị các kích thước bằng width
và height
phương pháp:
views / photos / _photo.html.erb
<div> <% if photo.image_data? %> <%= image_tag photo.image_url(:thumb) %> <p> Size <%= photo.image[:original].size %> bytes<br> MIME type <%= photo.image[:original].mime_type %><br> Dimensions <%= "#{photo.image[:original].width}x#{photo.image[:original].height}" %> </p> <% end %> <p><%= photo.title %> | <%= link_to 'Edit', edit_photo_path(photo) %></p> </div>
Ngoài ra, có một dimensions
phương thức có sẵn trả về một mảng chứa chiều rộng và chiều cao (ví dụ:[500, 750]
).
Chuyển sang Đám mây
Các nhà phát triển thường chọn các dịch vụ đám mây để lưu trữ các tệp đã tải lên và Shrine có khả năng như vậy. Trong phần này, tôi sẽ chỉ cho bạn cách tải tệp lên Amazon S3.
Bước đầu tiên, hãy đưa thêm hai viên ngọc nữa vào Gemfile :
Nhómgem "aws-sdk", "~> 2.1" group :development do gem 'dotenv-rails' end
aws-sdk là bắt buộc để hoạt động với SDK của S3, trong khi dotenv-rails sẽ được sử dụng để quản lý các biến môi trường trong quá trình phát triển.
bundle install
Trước khi tiếp tục, bạn nên lấy một cặp khóa để truy cập S3 qua API. Để có được nó, hãy đăng nhập (hoặc đăng ký) vào Bảng điều khiển dịch vụ web của Amazon và điều hướng đến Thông tin đăng nhập bảo mật> Người dùng . Tạo người dùng có quyền thao tác các tệp trên S3. Đây là chính sách đơn giản trình bày quyền truy cập đầy đủ vào S3:
{ "Version": "2016-11-14", "Statement": [ { "Effect": "Allow", "Action": "s3:*", "Resource": "*" } ] }
Tải xuống cặp khóa của người dùng đã tạo. Ngoài ra, bạn có thể sử dụng khóa truy cập root, nhưng tôi thực sự không khuyến khích bạn không làm điều đó vì nó rất không an toàn.
Tiếp theo, tạo một thùng S3 để lưu trữ các tệp của bạn và thêm một tệp vào thư mục gốc của dự án để lưu trữ cấu hình của bạn:
.env
S3_KEY=YOUR_KEY S3_SECRET=YOUR_SECRET S3_BUCKET=YOUR_BUCKET S3_REGION=YOUR_REGION
Không bao giờ để lộ tệp này công khai và đảm bảo bạn loại trừ tệp khỏi Git:
.gitignore
.env
Bây giờ, hãy sửa đổi cấu hình toàn cầu của Shrine và giới thiệu một bộ nhớ mới:
config / initializers / Temple.rb
require "shrine" require "shrine/storage/s3" s3_options = { access_key_id: ENV['S3_KEY'], secret_access_key: ENV['S3_SECRET'], region: ENV['S3_REGION'], bucket: ENV['S3_BUCKET'], } Shrine.storages = { cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"), store: Shrine::Storage::S3.new(prefix: "store", **s3_options), }
Đó là nó! Không có thay đổi nào đối với các phần khác của ứng dụng và bạn có thể thử nghiệm bộ nhớ mới này ngay lập tức. Nếu bạn nhận được lỗi từ S3 liên quan đến khóa không chính xác, hãy đảm bảo rằng bạn đã sao chép chính xác khóa và khóa bí mật, không có bất kỳ dấu cách nào ở cuối và các ký hiệu đặc biệt không nhìn thấy.
Kết luận
Chúng ta đã đến phần cuối của bài viết này. Hy vọng rằng bây giờ bạn cảm thấy tin tưởng vào việc sử dụng Shrine và mong muốn sử dụng nó trong một trong những dự án của bạn. Chúng tôi đã thảo luận về nhiều tính năng của viên ngọc này, nhưng thậm chí còn nhiều hơn thế, như khả năng lưu trữ ngữ cảnh bổ sung cùng với các tệp và cơ chế tải lên trực tiếp.
Do đó, hãy duyệt qua tài liệu của Shrine và trang web chính thức của nó, trong đó mô tả kỹ lưỡng tất cả các plugin có sẵn. Nếu bạn còn câu hỏi khác về viên ngọc này, đừng ngần ngại đăng chúng. Tôi cảm ơn bạn đã ở lại với tôi, và tôi sẽ gặp lại bạn trong thời gian sớm nhất!