Multiple Images Uploading With CarrierWave and Mysql Array
Trong quá trình thực hiện dự án, tôi có nhận được một yêu cầu cần thực hiện việc upload nhiều ảnh cho một bài viết, đồng thời khách hàng cũng đã giới hạn số ảnh tối đa có thể cho một bài viết chỉ là 5. Sau quá trình Google thần chưởng tôi biết được gem CarrierWave có cho phép việc upload nhiều ...
Trong quá trình thực hiện dự án, tôi có nhận được một yêu cầu cần thực hiện việc upload nhiều ảnh cho một bài viết, đồng thời khách hàng cũng đã giới hạn số ảnh tối đa có thể cho một bài viết chỉ là 5.
Sau quá trình Google thần chưởng tôi biết được gem CarrierWave có cho phép việc upload nhiều ảnh kết hợp với Mysql Array
Sau đây tôi sẽ minh họa tính năng trên thông qua một ứng dụng gallery đơn giản. Các tính năng trong ứng dụng này là:
- Tạo một gallery để upload ảnh
- Upload nhiều ảnh vào một gallery sẵn có
- Xóa một ảnh bất kỳ trong gallery
Các bước thực hiện chi tiết như sau:
Đầu tiên phải chạy lệnh rails new sample-gallery-app-with-carrierwave để generate ứng dụng rails. Tiếp theo vào trong Gemfile xóa gem sqlite3 và bổ sung các gem sau:
gem "mysql2" gem "slim-rails" gem "carrierwave", :github => "carrierwaveuploader/carrierwave"
Tiếp theo ta tiến hành config trong file config/database.yml để kết nối đến mysql server. Cuối cùng là chạy lệnh bundle để sử dụng tất cả các gem đã khai báo.
Tạo file theo đường dẫn config/initializers/carrier_wave.rb có nội dung như dưới đây để config CarrierWave :
CarrierWave.configure do |config| config.storage :file end
Sau đó bạn cần chạy lệnh rails generate uploader Image để tạo một file mới theo đường dẫn app/uploaders/image_uploader.rb
Bây giờ, chúng ta đã có ImageUploader, sau đây chúng ta cần viết các action CRUD cơ bản cho gallery.
Chạy lệnh rails g scaffold gallery title:string để tạo ra views, controllers, và model gallery. Tiếp theo, ta bổ sung dòng mount_uploaders :images, ImageUploader vào model gallery.
class Gallery < ActiveRecord::Base # Chú ý là hàm mount_uploaders khác với hàm mount_uploader mọi người thường dùng mount_uploaders :images, ImageUploader # mount the uploaders end
Tiếp theo, ta cần tạo cột images vào bảng gallery bằng cách gọi lệnh sau:
rails g migration AddImagesToGallery
Trong file migration viết như sau:
class AddImagesToGallery < ActiveRecord::Migration def change add_column :galleries, :images, :string, array: true end end
Cuối cùng chạy lệnh rake db:migrate là ta đã có một cột images kiểu array.
Sửa file app/views/galleries/_form.html.slim để upload được nhiều ảnh trong form
.field = f.file_field :images, multiple: true
Toàn bộ file app/views/galleries/_form.html.slim sau khi sửa là:
= form_for @gallery do |f| - if @gallery.errors.any? #error_explanation h2 = "#{pluralize(@gallery.errors.count, "error")} prohibited this gallery from being saved:" ul - @gallery.errors.full_messages.each do |message| li = message .field = f.label :title = f.text_field :title .field = f.file_field :images, multiple: true .actions = f.submit
Sau đó, ta thay đổi là gallery_params trong GalleriesController để cho phép params kiểu array như sau:
def gallery_params params.require(:gallery).permit(:title, {images: []}) end
Tiếp đến, ta thêm hiển thị ảnh vào trang show và index. Thêm những đoạn code sau vào file app/views/galleries/index.html.slim:
td - gallery.images.each do |image| = image_tag(image.url)
Tương tự, ta cũng thêm những đoạn code sau vào file app/views/galleries/show.html.slim:
div - @gallery.images.each do |image| = image_tag(image.url)
Đến đây ta đã hoàn thành xong việc tạo mới một gallery, chúng ta có thể test theo địa chỉ trên local là http://localhost:3000/galleries/new. Sau khi tạo xong sẽ được dẫn về trang hiển thị danh sách gallery http://localhost:3000/galleries.
Đầu tiên, ta phải định nghĩa trong file config/routes.rb thành nội dung như sau:
Rails.application.routes.draw do resources :galleries do resources :images, only: :create end end
Tiếp theo ta tạo ImagesController bằng cách chạy lệnh rails generate controller Images
Sau đó, ta viết code trong action create để thêm ảnh vào một gallery đã có sẵn như sau:
class ImagesController < ApplicationController before_action :set_gallery def create add_more_images(images_params[:images])) flash[:error] = "Failed uploading images" unless @gallery.save redirect_to :back end private def set_gallery @gallery = Gallery.find(params[:gallery_id]) end def add_more_images(new_images) images = @gallery.images # copy images cũ images += new_images # Thêm image mới vào mảng images @gallery.assign_attributes(images: images) # gán lại vào thuộc tính images end def images_params params.require(:gallery).permit({images: []}) end end
Bước tiếp theo ta cần có view để thêm ảnh vào một gallery bằng cách bổ sung đoạn code sau vào trong views show của gallery(Nằm trong file app/views/galleries/show.html.slim)
h1 Add more images = form_for @gallery, url: gallery_images_path(@gallery), method: :post do |f| # use customized url endpoint .field = f.file_field :images, multiple: true .actions = f.submit "Add More Images"
Bây giờ khi vào trang show chi tiết của bất kỳ gallery nào cũng sẽ có thêm một form để thêm ảnh vào gallery hiện tại
Tính năng cuối cùng của ứng dụng này là xóa một image bất kỳ.
Đầu tiên, ta khai báo thêm action destroy trong file config/routes.rb:
Rails.application.routes.draw do resources :galleries do resources :images, only: [:create, :destroy] end end
Sau đó, ta viết code cho chức năng xóa image trong action destroy trong ImagesController như sau:
class ImagesController < ApplicationController before_action :set_gallery def create add_more_images(images_params[:images]) flash[:error] = "Failed uploading images" unless @gallery.save redirect_to :back end def destroy remove_image_at_index(params[:id].to_i) flash[:error] = "Failed deleting image" unless @gallery.save redirect_to :back end private def set_gallery @gallery = Gallery.find(params[:gallery_id]) end def add_more_images(new_images) images = @gallery.images images += new_images @gallery.assign_attributes images: images end def remove_image_at_index(index) remain_images = @gallery.images # copy images remain_images.delete_at(index) # delete image theo index @gallery.assign_attributes images: remain_images end def images_params params.require(:gallery).permit({images: []}) end end
Cuối cùng, ta thêm một link delete cho mỗi ảnh được hiển thị trong trang show chi tiết của một gallery như sau:
div - @gallery.images.each_with_index do |image, index| #grab the index div = image_tag(image.url) = link_to "Delete", gallery_image_path(@gallery, index), method: :delete, data: { confirm: "Are you sure you want to delete this image?" }
Bây giờ, khi ta vào trang show của một gallery đã có đầy đủ các tính năng thêm ảnh và xóa một ảnh bất kỳ.
Trong bài viết trên tôi chỉ sử dụng CarrierWave để upload ảnh và lưu trữ file trên local. Nếu muốn upload ảnh lên Amazon S3 thì chúng ta cần sửa lại file config của CarrierWave như sau:
CarrierWave.configure do |config| config.storage :fog config.fog_credentials = { provider: "AWS", aws_access_key_id: ENV["AWS_ACCESS_KEY_ID"], aws_secret_access_key: ENV["AWS_SECRET_ACCESS_KEY"], region: ENV["AWS_REGION"], path_style: true } config.fog_directory = ENV['S3_BUCKET_NAME'] config.fog_public = Settings.carrierwave.fog_public config.fog_attributes = { "Cache-Control" => "max-age=#{eval(Settings.carrierwave.fog_cache_control).to_i}" } config.asset_host = ENV["CLOUDFRONT_DOMAIN_NAME"] end
Tiếp theo cần sử dụng thêm gem "fog-aws" trong Gemfile để không bị lỗi uninitialized constant CarrierWave::Storage::Fog
Trong bài viết trên, tôi đã hoàn thành việc hướng dẫn sử dụng `CarrierWave và mysql array để upload nhiều ảnh và lưu trữ vào cùng một cột trong MYSQL.
Như vậy, thay vì phải tạo một bảng images riêng để lưu trữ thông tin ảnh thì nay khi biết chính xác số ảnh tối đa có thể upload ta chỉ cần một cột để lưu trữ danh sách ảnh đó.