12/08/2018, 13:39

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à:

  1. Tạo một gallery để upload ảnh
  2. Upload nhiều ảnh vào một gallery sẵn có
  3. 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 đó.

0