12/08/2018, 16:30

Cách tải lên một tập tin khi dùng gem Shrine với Rails

Sau đây là bài viết về gem Sharine với rails mà mình có tìm hiểu được trong dự án mới. hãy tạo một ứng dụng Rails mới mà không có bộ kiểm thử mặc định: rails new NewApp -T Mình 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 có thể áp dụng cho các phiên bản 3 và ...

Sau đây là bài viết về gem Sharine với rails mà mình có tìm hiểu được trong dự án mới.

hãy tạo một ứng dụng Rails mới mà không có bộ kiểm thử mặc định:

rails new NewApp -T

Mình 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 có thể áp dụng cho các phiên bản 3 và 4. Thêm gem Shrine vào Gemfile của bạn:

gem "shrine"

sau đó chạy:

bundle install

Bây giờ chúng ta sẽ tạo một model mà mình sẽ gọi là Photo. Shrine lưu trữ tất cả các thông tin liên quan đến tập tin trong một cột kiểu text đặc biệt kết thúc với một hậu tố _data.

rails g model Photo title:string image_data:text
rails db:migrate

Thêm hai plugin: một để hỗ trợ ActiveRecord và một cái khác để thiết lập đăng nhập. Chúng sẽ được bao gồm trên toàn cục. Ngoài ra, thiết lập hệ thống lưu trữ tập tin: config/initializers/shrine.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"),
}

Bây giờ hãy tạo một lớp "uploader" đặc biệt sẽ lưu trữ các cài đặt cho model cụ thể. Còn bây giờ, lớp này sẽ rỗng: Tạo 1 model models/image_uploader.rb

class ImageUploader < Shrine
end

Cuối cùng, hãy bao gồm lớp này bên trong model Photo: Tạo 1 model models/photo.rb và sau đó

include ImageUploader[:image]

[:image] thêm một thuộc tính ảo sẽ được sử dụng khi cấu trúc một form. Dòng trên có thể được viết lại như sau:

include ImageUploader.attachment(:image)  
# or
include ImageUploader::Attachment.new(:image)

Với mục đích của demo này, chúng ta chỉ cần một controller để quản lý các tấm ảnh. Trang index sẽ đóng vai trò như là gốc: Tạo 1 app/controllers/photos_controller.rb

class PhotosController < ApplicationController
  def index
    @photos = Photo.all
  end
end

View: tạo 1 view app/views/photos/index.html.erb

<h1>Photos</h1>

<%= link_to 'Add Photo', new_photo_path %>

<%= render @photos %>

Để kết xuất mảng @photos, mình tạo 1 partial: app/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 đưa ra bởi Shrine để kiểm tra liệu một bản ghi có chứa một hình ảnh hay không. image_url là một phương thức khác của Shrine đơn giản trả về một đường dẫn đến hình ảnh ban đầu. Tất nhiên, tốt hơn là hiển thị một thumbnail (hình thu nhỏ), nhưng chúng ta sẽ làm việc với nó sau. Thêm tất cả các routes cần thiết:

Rails.application.routes.draw do
  resources :photos, only: [:new, :create, :index, :edit, :update]

  root 'photos#index'
end

Trong phần này, mình sẽ hướng dẫn cho bạn cách thêm tính năng để thật sự tải lên các tập tin. Các hành động của controller rất đơn giản: Tại app/controllers/photos_controller.rb chúng ta thêm

  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

Điều duy nhất mà mình nắm bắt được đó là đối với các tham số mạnh bạn phải cho phép thuộc tính ảo image, chứ không phải image_data. Chúng ta thêm

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

vào app/controllers/photos_controller.rb Tạo view new: app/views/photos/new.html.erb

<h1>Add photo</h1>

<%= render 'form' %>

tiếp theo tạo 1 render partial from app/views/photos/_form.html.erb

<%= form_for @photo do |f| %>
  <%= render "shared/errors", object: @photo %>

  <%= f.label :title %>
  <%= f.text_field :title %>

  <%= f.hidden_field :image, value: @photo.cached_image_data %>
  <%= f.label :image %>
  <%= f.file_field :image %>

  <%= f.submit %>
<% end %>

Chúng ta thêm

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

vào app/controllers/photos_controller.rb tiếp theo tạo views/photos/edit.html.erb

<h1>Edit Photo</h1>
 
<%= render 'form' %>

Theo như những gì đã làm vẫn không thể xóa hình ảnh đã tải lên. Để cho phép điều này, chúng ta sẽ cần một plugin: tại models/image_uploader.rb thêm

plugin :remove_attachment

Nó sử dụng một thuộc tính ảo được gọi là :remove_image, vì vậy hãy cho phép nó bên trong controller app/controllers/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 checkbox để xoá một hình ảnh nếu một bản ghi có một tập tin đính kèm ở đó: views/photos/_form.html.erb

<% if @photo.image_data? %>
    Remove attachment: <%= f.check_box :remove_image %>
<% end %>

Và bây giờ chúng ta có thể thêm, sửa, xóa một hình ảnh bất kì.

Hiện tại, chúng ta hiển thị hình ảnh gốc, đây không phải là cách làm tốt nhất đối với việc xem trước: ảnh có thể lớn và chiếm quá nhiều bộ nhớ. Tất nhiên, bạn có thể chỉ cần sử dụng các thuộc tính awidth và height của CSS, nhưng đó cũng là một ý tưởng không tốt. Bạn thấy đó, ngay cả khi hình ảnh được thiết lập cho nhỏ lại bằng các phong cách CSS, thì người dùng vẫn sẽ cần phải tải về tập tin ban đầu, có thể là 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 gem bổ sung. Trước nhất, thêm gem:

gem "image_processing"
gem "mini_magick", ">= 4.3.5"

image_processing là một gem đặc biệt được tạo ra bởi tác giả của Shrine. Nó đưa ra một số phương thức trợ giúp cấp cao để thao tác lên các hình ảnh. Gem này dựa vào mini_magick, một wrapper của Ruby cho ImageMagick. Như bạn có thể đoán được, bạn sẽ cần ImageMagick trên hệ thống của bạn để chạy bản demo này.

Cài đặt các gem 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

Processing là plugin để thật sự thao tác lên một hình ảnh (ví dụ như thu nhỏ, xoay, chuyển đổi sang định dạng khác, v.v.). Versions lần lượt cho phép chúng ta có một hình ảnh ở trong các biến thể khác nhau. Đối với demo này, có hai phiên bản sẽ được lưu trữ: "original" và "thumb" (thay đổi kích thước thành 300x300).

Dưới đây là code để 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

Bây giờ khi hiển thị hình ảnh, bạn chỉ cần cung cấp đối số hoặc :thumbnail hoặc :original cho phương thức image_url: 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 form: 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 tin đã được xử lý sau khi đã tải lên, bạn có thể thêm plugin được gọi là delete_raw: models/imageuploader.rb*

plugin :delete_raw

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ó thì sao? Thật không may, mặc định chúng không được lưu trữ, nhưng điều này là có thể với một plugin có tên là store_dimensions.

Kích thước của Hình ảnh Plugin store_dimensions dựa trên gem fastimage, vì vậy, hãy móc nối nó ngay bây giờ:

gem 'fastimage'

sau đó:

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 cách sử dụng các phương thức awidth và height:

views/photos/_photo.html.erb

<div>
  <% if photo.image_data? %>
    <%= image_tag photo.image_url(:thumb) %>
    <p>
      Size <%= photo.image %> bytes<br>
      MIME type <%= photo.image %><br>
      Dimensions <%= "#{photo.image}" %>
    </p>
  <% end %>
  <p><%= photo.title %> | <%= link_to 'Edit', edit_photo_path(photo) %></p>
</div>

Trên đây là bài viết về gem Shrine mà mình có tìm hiểu được, nếu có vấn đề gì vui lòng comment bên dưới để bài viết mình được tốt hơn Link tham khảo: https://github.com/janko-m/shrine http://shrinerb.com/

0