Đây là một bài viết khác trong loạt bài "Tải lên bằng 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 phổ biến nhất cho Rails. Tôi thích Carrierwave vì nó dễ bắt đầu, nó có rất nhiều tính năng và cung cấp hàng tá bài viết về "cách thực hiện" đượ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.
Trong bài viết 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 theo yêu cầu
- Xóa tệp
- Tạo hình thu nhỏ
- Tải tệp lên từ các vị trí từ xa
- Giới thiệu nhiều tệp tải lên
- Thêm hỗ trợ cho lưu trữ đám mây
Mã nguồn của bài viết này có sẵn trên GitHub. Chúc bạn đọc vui vẻ!
Đặt nền móng
Như mọi khi, hãy bắt đầu bằng cách tạo một ứng dụng Rails mới:
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 kết nối Carrierwave phiên bản 0.11.
Để xem Carrierwave hoạt động, chúng ta sẽ tạo một ứng dụng viết blog rất đơn giản với một Post
duy nhất người mẫu. 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à tên tệp) được đính kèm vào bài đăng
Tạo và áp dụng một quá trình di chuyển mới:
rails g model Post title:string body:text image:string rails db:migrate
Thiết lập một số tuyến đường:
config / route.rb
resources :posts root to: 'posts#index'
Ngoài ra, hãy tạo một bộ điều khiển rất cơ bản:
posts_controller.rb
class PostsController < ApplicationController before_action :set_post, only: [:show, :edit, :update] def index @posts = Post.order('created_at DESC') end def show end def new @post = Post.new end def create @post = Post.new(post_params) if @post.save redirect_to posts_path else render :new end end def edit end def update if @post.update_attributes(post_params) redirect_to post_path(@post) else render :edit end end private def post_params params.require(:post).permit(:title, :body, :image) end def set_post @post = Post.find(params[:id]) end end
Bây giờ, hãy tạo chỉ mục xem:
views / posts / index.html.erb
<h1>Posts</h1> <%= link_to 'Add post', new_post_path %> <%= render @posts %>
Và một phần tương ứng:
views / posts / _post.html.erb
<h2><%= link_to post.title, post_path(post) %></h2> <p><%= truncate(post.body, length: 150) %></p> <p><%= link_to 'Edit', edit_post_path(post) %></p> <hr>
Ở đây tôi đang sử dụng Rails truncate
phương pháp để chỉ hiển thị 150 ký hiệu đầu tiên từ bài đăng. Trước khi chúng tôi tạo các dạng xem khác và một phần biểu mẫu, trước tiên hãy tích hợp Carrierwave vào ứng dụng.
Tích hợp Carrierwave
Thả một viên ngọc mới vào Gemfile :
Gemfile
gem 'carrierwave', '~> 1.0'
Chạy:
bundle install
Carrierwave lưu trữ cấu hình của nó bên trong trình tải lên được đưa vào các mô hình của bạn. Để tạo trình tải lên, hãy sử dụng lệnh sau:
rails generate uploader Image
Bây giờ, bên trong ứng dụng / người tải lên , bạn sẽ tìm thấy một tệp mới có tên là image_uploader.rb . Lưu ý rằng nó có một số nhận xét và ví dụ hữu ích, vì vậy bạn có thể sử dụng nó để bắt đầu. Trong bản demo này, chúng tôi 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 kết người tải lên này vào mô hình:
models / post.rb
mount_uploader :image, ImageUploader
Trình tải lên đã có cài đặt mặc định lành mạnh, nhưng ít nhất chúng ta cần chọn nơi lưu trữ các tệp đã tải lên. Bây giờ, hãy sử dụng lưu trữ tệp:
người tải lên / image_uploader.rb
storage :file
Theo mặc định, các tệp sẽ được đặt bên trong công khai / tải lên thư mục, vì vậy tốt nhất là loại trừ nó khỏi hệ thống kiểm soát phiên bản:
.gitignore
public/uploads
Bạn cũng có thể sửa đổi store_dir
bên trong trình tải lên của bạn để chọn một số vị trí khác.
Tại thời điểm này, chúng tôi có thể tạo một dạng xem mới và một phần biểu mẫu để bắt đầu tải tệp lên:
views / posts / new.html.erb
<h1>Add post</h1> <%= render 'form', post: @post %>
views / posts / _form.html.erb
<%= form_for post do |f| %> <div> <%= f.label :title %> <%= f.text_field :title %> </div> <div> <%= f.label :body %> <%= f.text_area :body %> </div> <div> <%= f.label :image %> <%= f.file_field :image %> </div> <%= f.submit %> <% end %>
Lưu ý rằng PostsController
không cần phải sửa đổi vì chúng tôi đã cho phép image
thuộc tính.
Cuối cùng, tạo chế độ xem chỉnh sửa:
views / posts / edit.html.erb
<h1>Edit post</h1> <%= render 'form', post: @post %>
Đó là nó! Bạn có thể khởi động máy chủ và cố gắng tạo một bài đăng có hình ảnh. Vấn đề là hình ảnh này không hiển thị ở bất cứ đâu, vì vậy chúng ta hãy chuyển sang phần tiếp theo và thêm một trang hiển thị!
Hiển thị hình ảnh
Vì vậy, chế độ xem duy nhất mà chúng tôi chưa tạo là hiển thị . Thêm ngay bây giờ:
views / posts / show.html.erb
<%= link_to 'All posts', posts_path %> <h1><%= @post.title %></h1> <%= image_tag(@post.image.url, alt: 'Image') if @post.image? %> <p><%= @post.body %></p> <p><%= link_to 'Edit', edit_post_path(@post) %></p>
Như bạn có thể thấy, hiển thị tệp đính kèm thực 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 hình ảnh. Để lấy đường dẫn đến tệp, hãy sử dụng current_path
phương pháp. Lưu ý rằng Carrierwave cũng cung cấp hình ảnh image?
để chúng tôi kiểm tra xem có phần đính kèm hay không (image
chính phương thức sẽ không bao giờ trả về nil
, ngay cả khi tệp không có).
Bây giờ, sau khi điều hướng đến một bài đăng, bạn sẽ thấy một hình ảnh, nhưng nó có thể xuất hiện quá lớn:xét cho cùng, chúng tôi không giới hạn kích thước ở bất kỳ đâu. Tất nhiên, chúng tôi có thể thu nhỏ hình ảnh bằng một số quy tắc CSS, nhưng tốt hơn là tạo hình thu nhỏ sau khi tệp đã được tải lên. Tuy nhiên, điều này đòi hỏi một số bước bổ sung.
Tạo hình thu nhỏ
Để cắt và chia tỷ lệ hình ảnh, chúng ta cần một công cụ riêng biệt. Ngoài ra, Carrierwave có hỗ trợ các viên ngọc RMagick và MiniMagick, đến lượt nó, được sử dụng để điều khiển hình ảnh với sự trợ 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 hình ảnh hiện có và tạo hình ảnh mới, vì vậy trước khi tiếp tục, bạn cần tải xuống và cài đặt nó. Tiếp theo, bạn có thể tự do chọn một trong hai viên ngọc. Tôi sẽ gắn bó với MiniMagick, vì nó dễ cài đặt hơn nhiều và hỗ trợ tốt hơn:
Gemfile
gem 'mini_magick'
Chạy:
bundle install
Sau đó, đưa MiniMagick vào trình tải lên của bạn:
người tải lên / image_uploader.rb
include CarrierWave::MiniMagick
Bây giờ chúng tôi chỉ cần giới thiệu một phiên bản mới cho người tải lên của chúng tôi. Khái niệm về phiên bản (hoặc các kiểu) được sử dụng trong nhiều thư viện tải lên tệp; nó chỉ đơn giản có nghĩa là các tệp bổ sung dựa trên tệp đính kèm ban đầu sẽ được tạo, chẳng hạn như các kích thước hoặc định dạng khác nhau. Giới thiệu phiên bản mới có tên thumb
:
người tải lên / image_uploader.rb
version :thumb do process resize_to_fill: [350, 350] end
Bạn có thể có bao nhiêu phiên bản tùy thích 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:
người tải lên / image_uploader.rb
version :small_thumb, from_version: :thumb do process resize_to_fill: [20, 20] end
Nếu bạn đã tải lên một số hình ảnh, chúng sẽ không có sẵn hình thu nhỏ. Tuy nhiên, đây không phải là vấn đề vì bạn có thể tạo lại chúng từ bảng điều khiển Rails:
rails c Post.find_each {|post| post.image.recreate_versions!(:thumb) if post.image?}
Cuối cùng, hiển thị hình thu nhỏ của bạn với một liên kết đến hình ảnh gốc:
views / posts / show.html.erb
<%= 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 xác thực
Hiện tại, quá trình tải lên của chúng tôi hoạt động, nhưng chúng tôi hoàn toàn không xác thực thông tin đầu vào của người dùng, tất nhiên là không hợp lệ. Miễn là chúng tôi chỉ muốn làm việc với hình ảnh, hãy đưa các phần mở rộng .png, .jpg và .gif vào danh sách trắng:
người tải lên / image_uploader.rb
def extension_whitelist %w(jpg jpeg gif png) end
Bạn cũng có thể thêm kiểm tra loại nội dung bằng cách xác định content_type_whitelist
phương pháp:
người tải lên / image_uploader.rb
def content_type_whitelist /image\// end
Ngoài ra, có thể đưa vào danh sách cấm một số loại tệp, ví dụ tệp thực thi, bằng cách xác định content_type_blacklist
phương pháp.
Ngoài việc kiểm tra loại và phần mở rộng của tệp, hãy bắt buộc nó phải nhỏ hơn 1 megabyte. Để làm điều đó, chúng tôi sẽ yêu cầu xác thực tệp bổ sung hỗ trợ đá quý cho ActiveModel:
Gemfile
gem 'file_validators'
Cài đặt nó:
bundle install
Bây giờ giới thiệu các xác thực mong muốn (lưu ý rằng tôi cũng đang thêm kiểm tra cho title
và body
thuộc tính):
models / post.rb
validates :title, presence: true, length: {minimum: 2} validates :body, presence: true validates :image, file_size: { less_than: 1.megabytes }
Điều tiếp theo cần làm là thêm bản dịch I18n cho các thông báo lỗi của Carrierwave:
config / locales / en.yml
en: errors: messages: carrierwave_processing_error: "Cannot resize image." carrierwave_integrity_error: "Not an image." carrierwave_download_error: "Couldn't download image." extension_whitelist_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}" extension_blacklist_error: "You are not allowed to upload %{extension} files, prohibited types: %{prohibited_types}"
Hiện tại, chúng tôi không hiển thị lỗi xác thực ở bất kỳ đâu, vì vậy hãy tạo một phần được chia sẻ:
views / shared / _errors.html.erb
<% if object.errors.any? %> <h3>Some errors were found:</h3> <ul> <% object.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> <% end %>
Sử dụng phần này bên trong biểu mẫu:
views / posts / _form.html.erb
<%= render 'shared/errors', object: post %>
Bây giờ hãy thử tải lên một số tệp 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 hợp lệ và không điền vào tiêu đề hoặc nội dung, thì việc kiểm tra sẽ vẫn không thành công và một lỗi sẽ được hiển thị. Tuy nhiên, trường tệp sẽ bị xóa và người dùng sẽ phải chọn lại hình ảnh, điều này không thuận tiện cho lắm. Để khắc phục, chúng tôi cần thêm một trường khác vào biểu mẫu.
Tệp tồn tại qua các yêu cầu
Việc duy trì các tệp trên các biểu mẫu hiển thị lại thực 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 bộ điều khiển:
views / shared / _form.html.erb
<%= f.label :image %> <%= f.file_field :image %><br> <%= f.hidden_field :image_cache %>
posts_controller.rb
params.require(:post).permit(:title, :body, :image, :image_cache)
Bây giờ là image_cache
sẽ được điền tự động và hình ảnh sẽ không bị mất. Có thể hữu ích khi hiển thị hình thu nhỏ để người dùng hiểu rằng hình ảnh đã được xử lý thành công:
views / shared / _form.html.erb
<% if post.image? %> <%= image_tag post.image.thumb.url %> <% end %>
Xóa hình ảnh
Một tính năng rất phổ biến khác là khả năng loại bỏ các tệp đính kèm khi chỉnh sửa bản ghi. Với Carrierwave, việc triển khai tính năng này không phải là vấn đề. Thêm hộp kiểm mới vào biểu mẫu:
views / shared / _form.html.erb
<% if post.image? %> <%= image_tag post.image.thumb.url %> <div> <%= label_tag :remove_image do %> Remove image <%= f.check_box :remove_image %> <% end %> </div> <% end %>
Và cho phép remove_image
thuộc tính:
posts_controller.rb
params.require(:post).permit(:title, :body, :image, :remove_image, :image_cache)
Đó là nó! Để xóa hình ảnh theo cách thủ công, hãy sử dụng remove_image!
phương pháp:
@post.remove_image!
Tải lên từ một vị trí từ xa
Carrierwave cũng cung cấp một tính năng rất thú vị:khả năng tải tệp lên từ các vị trí từ xa bằng URL của chúng. Hãy giới thiệ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
<%= f.text_field :remote_image_url %> <small>Enter URL to an image</small>
posts_controller.rb
params.require(:post).permit(:title, :body, :image, :remove_image, :image_cache, :remote_image_url)
Làm thế nào là thú vị? Bạn không cần thực hiện bất kỳ thay đổi nào và có thể kiểm tra tính năng này ngay lập tức!
Làm việc với nhiều video tải lên
Giả sử chúng ta muốn bài đăng của mình có sẵn nhiều tệp đí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 kịch bản như vậy. Để triển khai tính năng này, bạn cần thêm trường được tuần tự hóa (đối với SQLite) hoặc trường JSON (đối với Postgres hoặc MySQL). Tôi thích tùy chọn thứ hai hơn, vì vậy hãy chuyển sang bộ điều hợp cơ sở dữ liệu mới ngay bây giờ. Xóa đá quý sqlite3 khỏi Gemfile và thêm pg thay vào đó:
Gemfile
gem 'pg'
Cài đặt nó:
bundle install
Sửa đổi cấu hình cơ sở dữ liệu như sau:
config / database.yml
default: &default adapter: postgresql pool: 5 timeout: 5000 development: <<: *default database: upload_carrier_dev username: 'YOUR_USER' password: 'YOUR_PASSWORD' host: localhost
Tạo cơ sở dữ liệu Postgres tương ứng, sau đó tạo và áp dụng quá trình di chuyển:
rails g migration add_attachments_to_posts attachments:json rails db:migrate
Nếu bạn muốn gắn bó với SQLite, hãy làm theo hướng dẫn được liệt kê trong tài liệu của Carrierwave.
Bây giờ gắn kết các trình tải lên (lưu ý dạng số nhiều!):
model / post.rb
mount_uploaders :attachments, ImageUploader
Tôi đang sử dụng cùng một trình tải lên cho các tệp đính kèm, nhưng tất nhiên bạn có thể tạo một trình tải lên mới với cấu hình khác.
Thêm trường nhiều tệp vào biểu mẫu của bạn:
views / shared / _form.html.erb
<div> <%= f.label :attachments %> <%= f.file_field :attachments, multiple: true %> </div>
Miễn là attachments
trường sẽ chứa một mảng, nó phải được cho phép theo cách sau:
posts_controller.rb
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 lại các tệp đính kèm của bài đăng và hiển thị chúng như bình thường:
views / shared / show.html.erb
<% if @post.attachments? %> <ul> <% @post.attachments.each do |attachment| %> <li><%= link_to(image_tag(attachment.thumb.url, alt: 'Image'), attachment.url, target: '_blank') %></li> <% end %> </ul> <% end %>
Lưu ý rằng mỗi tệp đính kèm sẽ có một hình thu nhỏ như được định cấu hình trong ImageUploader
của chúng tôi . Tốt!
Sử dụng bộ nhớ đám mây
Việc gắn bó với việc lưu trữ tệp không phải lúc nào cũng thuận tiện và / hoặc có thể, chẳng hạn như trên Heroku, không thể lưu trữ các tệp tùy chỉnh. Do đó, bạn có thể hỏi làm thế nào để kết hợp Carrierwave với lưu trữ đám mây Amazon S3? Chà, đó cũng là một nhiệm vụ khá dễ dàng. Carrierwave phụ thuộc vào đá quý Mist-aws để triển khai tính năng này:
Gemfile
gem "fog-aws"
Cài đặt nó:
bundle install
Hãy tạo trình khởi tạo cho Carrierwave và định cấu hình lưu trữ đám mây trên toàn cầu:
config / initializers / carrierwave.rb
CarrierWave.configure do |config| config.fog_provider = 'fog/aws' config.fog_credentials = { provider: 'AWS', aws_access_key_id: ENV['S3_KEY'], aws_secret_access_key: ENV['S3_SECRET'], region: ENV['S3_REGION'], } config.fog_directory = ENV['S3_BUCKET'] end
Có một số tùy chọn khác có sẵn, bạn có thể tìm thấy trong tài liệu.
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 cung cấp công khai, vì nếu không, bất kỳ ai cũng có thể tải bất kỳ thứ gì lên bộ chứa của bạn!
Tiếp theo, thay thế storage :file
dòng với:
người tải lên / image_uploader.rb
storage :fog
Ngoài S3, Carrierwave hỗ trợ tải lên Google Storage và Rackspace. Các dịch vụ này cũng dễ dàng thiết lập.
Kết luận
Đây là nó cho ngày hôm nay! Chúng tôi đã đề cập đến 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 mình. Nó có sẵn một số tùy chọn bổ sung, vì vậy hãy duyệt qua tài liệu.
Nếu bạn gặp khó khăn, đừng ngần ngại đăng câu hỏi của bạn. Ngoài ra, có thể hữu ích khi xem qua wiki của Carrierwave, nơi lưu trữ các bài viết hữu ích về "cách thực hiện" trả lời nhiều câu hỏi phổ biến.
Vì vậy, tôi cảm ơn bạn đã ở lại với tôi và chúc bạn viết mã vui vẻ!