Upload ảnh áp dụng polymorphic và lưu trữ ảnh trên Cloudinary
Giới thiệu Trong các ứng dụng web hiện nay, hình ảnh là một phần không thể thiếu được. Mối ứng dụng web có nhiều đối tượng cần lưu trữ ảnh, một cách thông thường ta sẽ thêm trường image vào bảng cơ sở dữ liệu của model tương ứng hoặc tạo riêng từng bảng trung gian để liên kết model Image với ...
Giới thiệu
Trong các ứng dụng web hiện nay, hình ảnh là một phần không thể thiếu được. Mối ứng dụng web có nhiều đối tượng cần lưu trữ ảnh, một cách thông thường ta sẽ thêm trường image vào bảng cơ sở dữ liệu của model tương ứng hoặc tạo riêng từng bảng trung gian để liên kết model Image với model đó.
Tuy nhiên, cách làm này làm cho cơ sở dữ liệu thêm rườm rà và gây ra lặp code. Một cách đơn giản để giải quyết vấn đề này, ta áp dụng polymorphic để tạo bảng image dùng chung cho nhiều model khác nhau với chỉ một bảng duy nhất. Mình sẽ giới thiệu với các bạn phương pháp này bằng một ứng dụng demo dưới đây.
Mình sử dụng ứng dụng demo của bài viết này của mình để áp dụng kĩ thuật này.
Upload ảnh áp dụng polymorphic
Đầu tiên, chúng ta tạo uploader bằng lệnh sau:
rails g uploader Image
Sau khi chạy lệnh trên, nó sẽ tạo ra file app/uploaders/image_uploader.rb
class ImageUploader < CarrierWave::Uploader::Base storage :file end
Để áp dụng polymorphic, sử dụng một bảng chung để lưu đường dẫn của ảnh, ta tạo model Image với các trường sau:
class CreateImages < ActiveRecord::Migration[5.2] def change create_table :images do |t| t.text :image_url t.integer :imageable_id t.string :imageable_type t.timestamps end end end
Trong file app/model/image.rb ta viết nội dung như sau:
class Image < ApplicationRecord belongs_to :imageable, polymorphic: true /* Mở chế độ polymorphic */ mount_uploader :image_url, ImageUploader /* Lấy đường dẫn ảnh lưu vào bảng trong cột image_url */ end
Trong các model cần có trường ảnh, ta áp dụng nested attribute: thêm các dòng sau vào model đó.
class Post < ApplicationRecord ... has_one :image, as: :imageable, dependent: :destroy accepts_nested_attributes_for :image, reject_if: proc {|attributes| attributes['image_url'].blank?} ... end
Trong controller, ta thêm image_attributes vào params để lưu ảnh cùng lúc với lúc tạo một bài Post.
class PostsController < ApplicationController ... def post_params params.require(:post).permit :title, :content, image_attributes: [:id, :image_url, :imageable_id, :imageable_type, :_destroy] end end
Trong form tạo post ta thêm trường ảnh vào như sau:
<%= form_for @post do |f| %> <div class="form-group"> <div class="fix-bot-pic upload-pic pic-profile"> <%= f.fields_for :image_attributes do |image_f| %> <%= image_f.file_field :image_url, class: "input-file previewIMG" %> <%= image_f.hidden_field :imageable_id, value: @post.id %> <%= image_f.hidden_field :imageable_type, value: "image" %> <% end %> <label tabindex="0" class="input-file-trigger"> <%= t ".avatar" %> <i class="fa fa-camera"></i> </label> </div> <%= image_tag get_image(@post), class: "image_cm preview-img" %> </div> <% end %>
Lưu trữ ảnh trên Cloud với gem "cloudinary" và gem "carrierwave"
Tại sao nên sử dụng phương pháp này?
Khi chúng ta tạo một ứng dụng Web bằng Rails và deploy lên Heroku, có một vấn đề mà chúng ta hay gặp phải đó là làm thế nào để tải ảnh lên Heroku mà khi reload trang không bị mất ảnh. Một giải pháp đó là dùng gem "cloudinary".
Cloudinary cung cấp dịch vụ quản lý ảnh dựa trên cloud một cách toàn diện. Vì vậy bạn có thể upload, lưu trữ, sửa ảnh và đưa nó lên ứng dụng Web của mình một cách dễ dàng.
Cloudinary còn được tích hợp rất tốt với nhiều ngôn ngữ và framework khác nhau, trong đó có Ruby on Rails.
Cloudinary có nhiều gói dịch vụ khác nhau, trong đó gói free cho phép upload khoảng 75000 ảnh.
Cài đặt
Chúng ta thêm 2 gem vào trong Gemfile, lưu ý cần cài carrierwave trước cloudinary
gem "carrierwave" gem "cloudinary"
Chạy bundle để cài đặt hai gem này
Tạo tài khoản và cấu hình Cloudianary
Đầu tiên bạn cần đăng ký tài khoản và đăng nhập tại đây.
Sau khi đăng nhập vào dashboard, bạn hãy download file cloudinary.yml tại đây
Sau đó, bạn copy file này vào trong thư mục config.
File cloudinary.yml có dạng như sau:
development: cloud_name: your_name api_key: your_api_key api_secret: your_api_secret enhance_image_tag: true static_file_support: false production: cloud_name: your_name api_key: your_api_key api_secret: your_api_secret enhance_image_tag: true static_file_support: true test: cloud_name: your_name api_key: your_api_key api_secret: your_api_secret enhance_image_tag: true static_file_support: false
Cách sử dụng
Thiết đặt môi trường
Trong file Uploader bạn cần include Cloudinary::CarrierWave.
class ImageUploader < CarrierWave::Uploader::Base include Cloudinary::CarrierWave .... end
Cài môi trường upload trực tiếp
Trong application.js, ta thêm dòng sau:
//= require cloudinary
Upload ảnh
Trong form upload, ta thêm một trường hidden là :picture_cache để khi mà page reloading hay validation errors thì sẽ không mất image đã upload.
<%= f.fields_for :image_attributes do |image_f| %> <%= image_f.hidden_field :image_cache %> ... <% end %>
Hiển thị ảnh đã upload
Mỗi image update sẽ có một public ID, bạn hoàn toàn có thể sử dụng public ID của image để hiện thị ảnh đó. Ví dụ một ảnh có image id là "user8" ta có thể hiện thị ảnh như sau:
cl_image_tag("user8.png", :alt => "Default Image")
Hoặc có thể dùng địa chỉ tuyệt đói của bức ảnh đó để hiện thị như sau
image_tag( "http://res.cloudinary.com/demo/image/upload/w_100,h_150,c_fill/sample.jpg", :awidth => 100, :height => 150)
Hoặc ta có thể hiện thị ảnh bình thường như sau
<%= image_tag @post.image.url, alt: @post.name %>
Lưu ý: Bạn chạy lệnh sau để upload các ảnh trên các trang tĩnh lên cloud để tránh tính trạng mất ảnh khi reload
rake cloudinary:sync_static
Các bạn có thể tham khảo mã nguồn tại đây.
Cảm ơn mọi người đã dành thời gian đọc bài viết, mong nhận được các ý kiến góp ý từ các bạn để bài viết được đầy đủ hơn.
Tham khảo
Ruby On Rails Guide
Cloudinary
Rails & CarrierWave Guide