Advertisement
  1. Code
  2. Ruby on Rails

Tải lên Với Rails và Carrierwave

Scroll to top
Read Time: 15 min

() translation by (you can also view the original English article)

Đây là một bài viết khác trong loạt bài "Tải lên với Rails". Hôm nay chúng ta sẽ gặp gỡ Carrierwave—một trong những giải pháp tải lên tập tin phổ biến nhất cho Rails. Tôi thích Carrierwave bởi vì nó rất dễ làm quen, nó có rất nhiều tính năng và nó cung cấp hàng chục bài hướng dẫn "cách làm thế nào để" được viết bởi các thành viên của cộng đồng, vì vậy bạn sẽ không bị lạc lõng.

Trong bài này, bạn sẽ học cách:

  • Tích hợp Carrierwave vào ứng dụng Rails của bạn
  • Thêm xác thực
  • Duy trì các tập tin xuyên suốt các yêu cầu
  • Xoá các tập tin
  • Tạo các thumbnail (hình thu nhỏ)
  • Tải lên các tập tin từ xa
  • Giới thiệu về tải lên nhiều tập tin
  • Thêm hỗ trợ cho lưu trữ đám mây

Mã nguồn cho bài viết này có trên GitHub. Hãy đọc vui nhé!

Đặt Nền móng

Như thường lệ, bắt đầu bằng cách tạo một ứng dụng Rails mới:

1
rails new UploadingWithCarrierwave -T

Đối với bản demo này tôi sẽ sử dụng Rails 5.0.2. Xin lưu ý rằng Carrierwave 1 chỉ hỗ trợ Rails 4+ và Ruby 2. Nếu bạn vẫn đang sử dụng Rails 3, hãy sử dụng phiên bản Carrierwave 0.11.

Để thử nghiệm Carrierwave, chúng ta sẽ tạo ra một ứng dụng blog rất đơn giản với một model Post duy nhất. Nó sẽ có các thuộc tính chính sau:

  • title (string)
  • body (text)
  • image (string)—trường này sẽ chứa một hình ảnh (chính xác là một tên của tập tin) gắn với bài viết

Tạo ra và áp dụng một migration (di trú) mới:

1
rails g model Post title:string body:text image:string
2
rails db:migrate

Thiết lập một số route (tuyến):

config/routes.rb

1
resources :posts
2
root to: 'posts#index'

Ngoài ra, tạo một controller rất cơ bản:

posts_controller.rb

1
class PostsController < ApplicationController
2
  before_action :set_post, only: [:show, :edit, :update]
3
4
  def index
5
    @posts = Post.order('created_at DESC')
6
  end
7
8
  def show
9
  end
10
11
  def new
12
    @post = Post.new
13
  end
14
15
  def create
16
    @post = Post.new(post_params)
17
    if @post.save
18
      redirect_to posts_path
19
    else
20
      render :new
21
    end
22
  end
23
24
  def edit
25
  end
26
27
  def update
28
    if @post.update_attributes(post_params)
29
      redirect_to post_path(@post)
30
    else
31
      render :edit
32
    end
33
  end
34
35
  private
36
37
  def post_params
38
    params.require(:post).permit(:title, :body, :image)
39
  end
40
41
  def set_post
42
    @post = Post.find(params[:id])
43
  end
44
end

Bây giờ chúng ta hãy tạo ra view index:

views/posts/index.html.erb

1
<h1>Posts</h1>
2
3
<%= link_to 'Add post', new_post_path %>
4
5
<%= render @posts %>

Và một phần tương ứng:

views/posts/_post.html.erb

1
<h2><%= link_to post.title, post_path(post) %></h2>
2
3
<p><%= truncate(post.body, length: 150) %></p>
4
5
<p><%= link_to 'Edit', edit_post_path(post) %></p>
6
<hr>

Ở đây tôi đang sử dụng phương thức truncate của Rails để chỉ hiển thị 150 ký hiệu đầu tiên từ bài viết. Trước khi chúng ta tạo ra các view khác và một form, trước tiên hãy tích hợp Carrierwave vào ứng dụng.

Tích hợp Carrierwave

Thêm một gem mới vào trong Gemfile:

Gemfile

1
gem 'carrierwave', '~> 1.0'

Chạy:

1
bundle install

Carrierwave lưu trữ cấu hình của nó bên trong các uploader được bao gồm trong các model của bạn. Để tạo một uploader, hãy sử dụng lệnh sau:

1
rails generate uploader Image

Bây giờ, bên trong app/uploaders, bạn sẽ thấy một tập tin mới có tên là image_uploader.rb. Lưu ý rằng nó có một số comment ​và ví dụ hữu ích, vì vậy bạn có thể sử dụng nó để bắt đầu. Trong demo này chúng ta sẽ sử dụng ActiveRecord, nhưng Carrierwave cũng hỗ trợ Mongoid, Sequel và DataMapper.

Tiếp theo, chúng ta cần bao gồm hoặc gắn uploader này vào model:

models/post.rb

1
mount_uploader :image, ImageUploader

Uploader đã có các cài đặt mặc định đúng chuẩn, nhưng ít nhất chúng ta cần phải chọn nơi tập tin tải lên sẽ được lưu trữ. Lúc này, hãy sử dụng bộ nhớ tập tin (file storage):

uploaders/image_uploader.rb

1
storage :file

Mặc định, các tập tin sẽ được đặt bên trong thư mục public/uploads, vì vậy cách tốt nhất là loại trừ nó khỏi hệ thống quản lý mã nguồn:

.gitignore

1
public/uploads

Bạn cũng có thể sửa đổi phương thức store_dir bên trong uploader để chọn một số vị trí khác.

Tại thời điểm này, chúng ta có thể tạo một view và một form mới để bắt đầu tải lên các tập tin:

views/posts/new.html.erb

1
<h1>Add post</h1>
2
3
<%= render 'form', post: @post %>

views/posts/_form.html.erb

1
<%= form_for post do |f| %>
2
  <div>
3
    <%= f.label :title %>
4
    <%= f.text_field :title %>
5
  </div>
6
7
  <div>
8
    <%= f.label :body %>
9
    <%= f.text_area :body %>
10
  </div>
11
12
  <div>
13
    <%= f.label :image %>
14
    <%= f.file_field :image %>
15
  </div>
16
17
  <%= f.submit %>
18
<% end %>

Lưu ý rằng PostsController không cần phải được sửa đổi vì chúng ta đã cho phép thuộc tính image.

Cuối cùng, hãy tạo view edit (chỉnh sửa):

views/posts/edit.html.erb

1
<h1>Edit post</h1>
2
3
<%= render 'form', post: @post %>

Xong rồi! Bạn có thể khởi động máy chủ và thử tạo ra một bài viết với một hình ảnh. Vấn đề là hình ảnh này không hiển thị ở bất kỳ đâu, vì vậy chúng ta hãy sang phần tiếp theo và thêm một trang hiển thị!

Hiện thị Hình ảnh

Như vậy, view duy nhất mà chúng ta chưa tạo ra là show (hiển thị). Hãy thêm nó ngay bây giờ:

views/posts/show.html.erb

1
<%= link_to 'All posts', posts_path %>
2
<h1><%= @post.title %></h1>
3
4
<%= image_tag(@post.image.url, alt: 'Image') if @post.image? %>
5
6
<p><%= @post.body %></p>
7
8
<p><%= link_to 'Edit', edit_post_path(@post) %></p>

Như bạn có thể thấy, việc hiển thị một tập tin đính kèm thật sự dễ dàng: tất cả những gì bạn cần làm là nói @post.image.url để lấy URL của một hình ảnh. Để có được một đường dẫn đến tập tin, sử dụng phương thức current_path. Lưu ý rằng Carrierwave còn cung cấp một phương thức image? để chúng ta kiểm tra xem một tập tin đính kèm có hay không (bản thân phương thức image sẽ không bao giờ trả về nil, ngay cả khi tập tin không tồn tại).

Bây giờ, sau khi điều hướng đến bài viết, bạn sẽ thấy một hình ảnh, nhưng nó có thể xuất hiện quá lớn: sau tất cả, chúng ta vẫn chưa giới hạn kích thước. Tất nhiên, chúng ta có thể thu nhỏ hình ảnh xuống bằng một số thuộc tính CSS, nhưng tốt hơn là tạo một hình thumbnail sau khi tập tin đã được tải lên. Tuy nhiên, điều này đòi hỏi một số bước bổ sung.

Tạo Thumbnail

Để cắt xén và co giãn kích thước hình ảnh, chúng ta cần một công cụ riêng biệt. Mặc định Carrierwave có hỗ trợ các gem RMagickMiniMagick, lần lượt được sử dụng để thao tác lên hình ảnh với sự giúp đỡ của ImageMagick. ImageMagick là một giải pháp mã nguồn mở cho phép bạn chỉnh sửa các hình ảnh hiện có và tạo ra những hình ảnh mới, do đó, trước khi tiến hành bạn cần phải tải về và cài đặt nó. Tiếp theo, bạn được tự do chọn một trong hai gem. Tôi sẽ chọn MiniMagick, bởi vì nó dễ cài đặt hơn và nó có hỗ trợ tốt hơn:

Gemfile

1
gem 'mini_magick'

Chạy:

1
bundle install

Sau đó bao gồm MiniMagick vào uploader của bạn:

uploaders/image_uploader.rb

1
include CarrierWave::MiniMagick

Bây giờ chúng ta chỉ cần giới thiệu một phiên bản mới cho uploader của chúng ta. Khái niệm các phiên bản (hoặc kiểu) được sử dụng trong nhiều thư viện tải lên tập tin; nó đơn giản có nghĩa là các tập tin bổ sung dựa trên tập tin đính kèm ban đầu sẽ được tạo với, ví dụ như các kích thước hoặc định dạng khác nhau. Giới thiệu một phiên bản mới gọi là thumb:

uploaders/image_uploader.rb

1
version :thumb do
2
    process resize_to_fill: [350, 350]
3
end

Bạn có thể có nhiều phiên bản tùy ý và hơn thế nữa, các phiên bản thậm chí có thể được xây dựng trên các phiên bản khác:

uploaders/image_uploader.rb

1
version :small_thumb, from_version: :thumb do
2
    process resize_to_fill: [20, 20]
3
end

Nếu bạn đã tải lên một số hình ảnh, thì chúng sẽ không có thumbnail. Tuy nhiên, điều đó không thành vấn đề, vì bạn có thể tạo lại chúng từ dòng lệnh của Rails:

1
rails c
2
Post.find_each {|post| post.image.recreate_versions!(:thumb) if post.image?}

Sau cùng, hiển thị hình thumbnail của bạn với một liên kết đến hình ảnh gốc:

views/posts/show.html.erb

1
<%= link_to(image_tag(@post.image.thumb.url, alt: 'Image'), @post.image.url, target: '_blank') if @post.image? %>

Khởi động máy chủ và quan sát kết quả!

Thêm Kiểm tra

Hiện tại, việc tải lên của chúng ta đã hoạt động, tuy nhiên chúng ta chưa kiểm tra đầu vào từ người dùng, đó tất nhiên là không tốt. Miễn là chúng ta chỉ làm việc với hình ảnh, thì chúng ta có thể chọn danh sách trắng các phần mở rộng tập tin .png, .jpg và .gif:

uploaders/image_uploader.rb

1
def extension_whitelist
2
    %w(jpg jpeg gif png)
3
end

Bạn cũng có thể thêm các kiểm tra loại nội dung bằng cách định nghĩa phương thức content_type_whitelist:

uploaders/image_uploader.rb

1
def content_type_whitelist
2
    /image\//
3
end

Ngoài ra, có thể có danh sách đen một số loại tập tin, ví dụ tập tin có thể thực thi, bằng cách định nghĩa phương thức content_type_blacklist.

Ngoài việc kiểm tra kiểu và đuôi mở rộng của tập tin, chúng ta hãy bắt buộc nó phải nhỏ hơn 1 megabyte. Để thực hiện việc này, chúng ta sẽ require (yêu cầu) một gem bổ sung hỗ trợ việc kiểm tra tập tin cho ActiveModel:

Gemfile

1
gem 'file_validators'

Cài đặt nó:

1
bundle install

Bây giờ đưa ra các kiểm tra mong muốn (lưu ý rằng tôi cũng thêm kiểm tra cho các thuộc tính titlebody):

models/post.rb

1
validates :title, presence: true, length: {minimum: 2}
2
validates :body, presence: true
3
validates :image, file_size: { less_than: 1.megabytes }

Việc tiếp theo cần làm là thêm hỗ trợ dịch I18n cho các thông báo lỗi của Carrierwave:

config/locales/en.yml

1
en:
2
  errors:
3
    messages:
4
      carrierwave_processing_error: "Cannot resize image."
5
      carrierwave_integrity_error: "Not an image."
6
      carrierwave_download_error: "Couldn't download image."
7
      extension_whitelist_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}"
8
      extension_blacklist_error: "You are not allowed to upload %{extension} files, prohibited types: %{prohibited_types}"

Hiện tại, chúng ta không hiển thị lỗi kiểm tra ở bất kỳ đâu, vì vậy hãy tạo một phần chia sẻ:

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 %>

Sử dụng phần này bên trong form:

views/posts/_form.html.erb

1
<%= render 'shared/errors', object: post %>

Bây giờ hãy thử tải lên một số tập tin không hợp lệ và quan sát kết quả. Nó sẽ hoạt động, nhưng nếu bạn chọn một tập tin hợp lệ và không điền vào các tiêu đề hoặc thân, thì một lỗi sẽ được hiển thị. Tuy nhiên, trường dữ liệu sẽ bị xóa và người dùng sẽ cần phải chọn lại hình ảnh một lần nữa, đây không phải là điều thuận tiện cho lắm. Để khắc phục điều đó, chúng ta cần thêm một trường khác vào form.

Duy trì các Tập tin Xuyên suốt các Yêu cầu

Duy trì các tập tin trên các form hiện thị lại thật sự khá dễ dàng. Tất cả những gì bạn cần làm là thêm một trường ẩn mới và cho phép nó bên trong controller:

views/shared/_form.html.erb

1
<%= f.label :image %>
2
<%= f.file_field :image %><br>
3
<%= f.hidden_field :image_cache %>

posts_controller.rb

1
params.require(:post).permit(:title, :body, :image, :image_cache)

Bây giờ image_cache sẽ được tự động nhập và hình ảnh sẽ không bị mất. Điều đó có thể giúp ích trong việc hiển thị hình thumbnail để người dùng hiểu được hình ảnh đã được xử lý thành công:

views/shared/_form.html.erb

1
<% if post.image? %>
2
    <%= image_tag post.image.thumb.url %>
3
<% end %>

Xoá Hình ảnh

Một tính năng rất phổ biến khác là khả năng xoá các tập tin đính kèm khi chỉnh sửa một bản ghi. Với Carrierwave, việc cài đặt tính năng này không phải là vấn đề. Thêm một hộp checkbox mới vào form:

views/shared/_form.html.erb

1
<% if post.image? %>
2
    <%= image_tag post.image.thumb.url %>
3
    <div>
4
      <%= label_tag :remove_image do %>
5
        Remove image
6
        <%= f.check_box :remove_image %>
7
      <% end %>
8
    </div>
9
<% end %>

Và cho phép thuộc tính remove_image:

posts_controller.rb

1
params.require(:post).permit(:title, :body, :image, :remove_image, :image_cache)

Xong rồi! Để xóa hình ảnh một cách thủ công, hãy sử dụng phương thức remove_image!:

1
@post.remove_image!

Tải lên Từ xa

Carrierwave còn cung cấp một tính năng rất tuyệt vời khác: khả năng tải lên các tập tin từ xa bằng URL của chúng. Hãy tìm hiểu khả năng này ngay bây giờ bằng cách thêm một trường mới và cho phép thuộc tính tương ứng:

views/shared/_form.html.erb

1
<%= f.text_field :remote_image_url %>
2
<small>Enter URL to an image</small>

posts_controller.rb

1
params.require(:post).permit(:title, :body, :image, :remove_image, :image_cache, :remote_image_url)

Điều đó tuyệt làm sao? Bạn không cần phải thực hiện bất kỳ thay đổi nào cả, và bạn có thể kiểm tra tính năng này ngay lập tức!

Làm việc Với Tải lên Nhiều Tập tin

Giả sử chúng ta muốn bài viết của chúng ta có nhiều tập tin đính kèm. Với thiết lập hiện tại thì không thể, nhưng may mắn thay, Carrierwave cũng hỗ trợ một trường hợp như vậy. Để cài đặt tính năng này, bạn cần phải thêm trường serialized (cho SQLite) hoặc trường JSON (cho Postgres hoặc MySQL). Tôi thích tùy chọn thứ hai hơn, vì vậy chúng ta hãy chuyển sang một adapter cơ sở dữ liệu mới ngay bây giờ. Loại bỏ gem sqlite3 khỏi Gemfile và thay vào đó thêm pg:

Gemfile

1
gem 'pg'

Cài đặt nó:

1
bundle install

Chỉnh sửa cấu hình cơ sở dữ liệu như sau:

config/database.yml

1
default: &default
2
  adapter: postgresql
3
  pool: 5
4
  timeout: 5000
5
6
development:
7
  <<: *default
8
  database: upload_carrier_dev
9
  username: 'YOUR_USER'
10
  password: 'YOUR_PASSWORD'
11
  host: localhost

Tạo cơ sở dữ liệu Postgres tương ứng, sau đó tạo và áp dụng di trú:

1
rails g migration add_attachments_to_posts attachments:json
2
rails db:migrate

Nếu bạn thích gắn bó với SQLite hơn, hãy làm theo các hướng dẫn được liệt kê trong tài liệu của Carrloodave.

Bây giờ gắn kết các uploader (lưu ý dạng số nhiều!):

model/post.rb

1
mount_uploaders :attachments, ImageUploader

Tôi đang sử dụng cùng một uploader cho tập tin đính kèm, nhưng tất nhiên bạn có thể tạo một tập tin mới với một cấu hình khác.

Thêm trường chọn nhiều tập tin vào form của bạn:

views/shared/_form.html.erb

1
<div>
2
    <%= f.label :attachments %>
3
    <%= f.file_field :attachments, multiple: true %>
4
</div>

Miễn là trường attachments còn chứa một mảng, thì nó phải được cho phép theo cách sau:

posts_controller.rb

1
params.require(:post).permit(:title, :body, :image, :remove_image, :image_cache, :remote_image_url, attachments: [])

Cuối cùng, bạn có thể lặp qua các tập tin đính kèm của bài viết và hiện thị chúng như bình thường:

views/shared/show.html.erb

1
<% if @post.attachments? %>
2
  <ul>
3
    <% @post.attachments.each do |attachment| %>
4
      <li><%= link_to(image_tag(attachment.thumb.url, alt: 'Image'), attachment.url, target: '_blank') %></li>
5
    <% end %>
6
  </ul>
7
<% end %>

Lưu ý rằng mỗi tập tin đính kèm sẽ có một hình thumbnail như cấu hình trong ImageUploader của chúng ta. Tuyệt!

Sử dụng Lưu trữ trên Đám mây

Việc gắn bó với lưu trữ tập tin không phải lúc nào cũng thuận tiện và/hoặc có thể, ví dụ như trên Heroku, bạn không thể lưu các tập tin tuỳ biến. Do đó bạn có thể hỏi làm thế nào để cưới Carrierwave cho dịch vụ lưu trữ đám mây Amazon S3? Vâng, đó cũng là một nhiệm vụ khá dễ dàng. Carrierwave phụ thuộc vào gem fog-aws để cài đặt tính năng này:

Gemfile

1
gem "fog-aws"

Cài đặt nó:

1
bundle install

Hãy tạo một trình khởi chạy cho Carrierwave và cấu hình bộ nhớ đám mây trên toàn cục:

config/initializers/carrierwave.rb

1
CarrierWave.configure do |config|
2
  config.fog_provider = 'fog/aws'
3
  config.fog_credentials = {
4
      provider:              'AWS',
5
      aws_access_key_id:     ENV['S3_KEY'],
6
      aws_secret_access_key: ENV['S3_SECRET'],
7
      region:                ENV['S3_REGION'],
8
  }
9
  config.fog_directory  = ENV['S3_BUCKET']
10
end

Có một số tùy chọn khác sẵn có, có thể được tìm thấy trong tài liệu hướng dẫn.

Tôi đang sử dụng gem dotenv-rails để đặt các biến môi trường một cách an toàn, nhưng bạn có thể chọn bất kỳ tùy chọn nào khác. Tuy nhiên, hãy đảm bảo rằng cặp khóa S3 của bạn không được công khai, bởi vì nếu không, ai đó sẽ có thể tải lên bất cứ thứ gì vào bộ lưu trữ của bạn!

Tiếp theo, thay thế dòng storage:file bằng:

uploaders/image_uploader.rb

1
storage :fog

Ngoài S3, Carrierwave còn hỗ trợ tải lên Google StorageRackspace. Các dịch vụ này cũng dễ dàng thiết lập.

Tóm tắt

Đó là những gì cho bài viết ngày hôm nay! Chúng tôi đã giới thiệu tất cả các tính năng chính của Carrierwave, và bây giờ bạn có thể bắt đầu sử dụng nó trong các dự án của bạn. Nó có một số tùy chọn bổ sung có sẵn, do đó, hãy tham khảo tài liệu hướng dẫn.

Nếu bạn gặp vướng mắt, đừng ngần ngại gửi câu hỏi của bạn. Ngoài ra, có thể sẽ rất hữu ích khi bạn lướt qua trang wiki của Carrierwave, nơi lưu trữ các bài viết "làm thế nào để" hữu ích để trả lời nhiều câu hỏi phổ biến.

Lời cuối cùng, tôi cảm ơn bạn đã theo dõi, và viết code thật vui nhé!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.