Computer >> Máy Tính >  >> Lập trình >> Ruby

Tải lên tệp bằng Rails và Shrine

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 widthheight 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 widthheight 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óm
gem "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!