Meng-upload dengan Rails dan Carrierwave
() translation by (you can also view the original English article)
Ini adalah artikel lain dalam seri "Mengunggah dengan Rails". Hari ini kita akan bertemu Carrierwave salah satu solusi untuk file uploading paling populer untuk Rails. Saya menyukai Carrierwave karena mudah untuk digunakan, memiliki banyak fitur yang diluar dugaan, dan menyediakan lusinan artikel "how to" yang ditulis oleh anggota komunitas, jadi anda tidak akan bingung dalam menggunakan Carrierwave.
Dalam artikel ini, Anda akan belajar cara:
- Mengintegrasikan Carrierwave ke aplikasi Rails anda
- Menambahkan validasi
- Mempertahankan file dari seluruh request
- Menghapus file
- Membuat thumbnail
- Mengunggah file dari lokasi jarak jauh
- Memperkenalkan cara uploading multiple file
- Menambahkan dukungan untuk penyimpanan cloud
Source code dalam artikel ini tersedia di GitHub. Selamat membaca!
Peletakan Pondasi
Seperti biasanya, mari mulai dengan membuat aplikasi Rails yang baru:
1 |
rails new UploadingWithCarrierwave -T
|
Untuk demo kali ini saya akan menggunakan Rails 5.0.2. Harap dicatat bahwa Carrierwave 1 hanya mendukung Rails 4+ dan Ruby 2. Jika Anda masih menggunakan Rails 3, maka hubungkan dengan Carrierwave versi 0.11.
Untuk melihat bagaimana Carrierwave bekerja, kita akan membuat aplikasi blogging yang sangat sederhana dengan model Post
tunggal. Aplikasi ini akan memiliki atribut utama sebagai berikut:
-
title
(string
) -
body
(text
) -
image
(string
) — bagian ini akan berisi gambar (nama file gambar, tepatnya) yang akan dilampirkan pada post.
Generate dan jalankan perintah migrate:
1 |
rails g model Post title:string body:text image:string |
2 |
rails db:migrate |
Mengatur beberapa routes:
config/routes.rb
1 |
resources :posts |
2 |
root to: 'posts#index'
|
Lalu, buatlah sebuah basic controller.
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
|
Sekarang, mari membuat index index view:
views/posts/index.html.erb
1 |
<h1>Posts</h1> |
2 |
|
3 |
<%= link_to 'Add post', new_post_path %> |
4 |
|
5 |
<%= render @posts %> |
Parsial yang sesuai:
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>
|
Di sini saya menggunakan metode Rails truncate
hanya untuk menampilkan 150 simbol pertama dari postingan. Sebelum kita membuat tampilan lain dan sebuah bentuk parsial, mari kita mengintegrasikan Carrierwave ke dalam aplikasi.
Integrasi Carrierwave
Masukkan gem baru ke dalam Gemfile:
Gemfile
1 |
gem 'carrierwave', '~> 1.0' |
Jalankan:
1 |
bundle install |
Carrierwave menyimpan konfigurasinya di dalam uploaders yang terdapat dalam model Anda. Untuk membuat uploader, gunakan perintah berikut:
1 |
rails generate uploader Image |
Sekarang, di dalam app/uploaders, Anda akan menemukan file baru bernama image_uploader.rb. Perhatikan bahwa ini memiliki beberapa perintah dan contoh yang berguna, jadi Anda dapat menggunakannya untuk memulai app. Dalam demo ini kita akan menggunakan ActiveRecord, tetapi juga mendukung penggunaan Mongoid, Sequel, dan DataMapper.
Selanjutnya, kita perlu menyertakan atau mount uploader ini ke dalam model:
models/post.rb
1 |
mount_uploader :image, ImageUploader |
Uploader sudah memiliki pengaturan default, tetapi setidaknya kita harus memilih di mana file yang diunggah akan disimpan. Untuk saat ini, mari gunakan penyimpanan file:
uploaders/image_uploader.rb
1 |
storage :file |
Secara default, file akan ditempatkan di dalam direktoripublic/uploads, ini adalah cara terbaik untuk mengeluarkannya dari version system control:
.gitignore
1 |
public/uploads |
Anda juga dapat memodifikasi store_dir
method di dalam uploader Anda agar dapat memilih beberapa lokasi lain.
Pada titik ini, kita dapat membuat tampilan baru dan sebuah formulir parsial untuk mulai mengunggah file:
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 %> |
Perhatikan bahwa PostsController
tidak perlu dimodifikasi karena kita sudah mengizinkan atribut image
Terakhir, mari membuat edit view:
views/posts/edit.html.erb
1 |
<h1>Edit post</h1> |
2 |
|
3 |
<%= render 'form', post: @post %> |
Nah! Anda dapat mem-boot server dan mencoba membuat sebuah postingan dengan gambar. Masalahnya adalah gambar ini tidak terlihat di mana pun, jadi mari kita lanjutkan ke bagian selanjutnya dan tambahkan show page!
Menampilkan Gambar
Sekarang, satu-satunya view yang belum kita buat adalahs show. Tambahkan source code ini sekarang:
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> |
Seperti yang Anda lihat, menampilkan attachment sangat mudah: semua yang perlu Anda lakukan adalah mengetikkan @post.image.url
untuk mengambil URL image. Untuk mendapatkan path ke file, gunakan metode current_path
. Perhatikan bahwa Carrierwave juga menyediakan image?
method bagi kita untuk memeriksa apakah attachment benar-benar ada ( image
itu sendiri tidak akan pernah kembali dalam keadaan nil
, bahkan jika file tersebut tidak ada).
Sekarang, setelah melakukan navigasi ke post, Anda akan melihat gambar, tetapi mungkin gambar tampak terlalu besar: karena kita tidak membatasi ukuran dimensinya. Tentu saja, kita dapat mengecilkan gambar dengan beberapa aturan CSS, tetapi jauh lebih baik untuk membuat thumbnail setelah file diunggah. Untuk membuatnya, bagaimanapun, membutuhkan beberapa langkah tambahan.
Membuat Thumbnail
Untuk memangkas dan memperbesar gambar, kita membutuhkan tool yang terpisah. Carrierwave memiliki dukungan yang mengejutkan untuk RMagick dan MiniMagick gem yang, pada gilirannya, digunakan untuk memanipulasi gambar dengan bantuan ImageMagick. ImageMagick adalah aplikasi open-source yang memungkinkan Anda untuk mengedit gambar yang ada dan menghasilkan yang baru, jadi sebelum melanjutkan Anda perlu download tool ini dan meng-install-nya. Selanjutnya, Anda bebas memilih salah satu dari dua gem itu. Saya selalu menggunakan MiniMagick, karena lebih mudah untuk menginstalnya dan memiliki dukungan yang lebih baik:
Gemfile
1 |
gem 'mini_magick' |
Jalankan:
1 |
bundle install
|
Lalu, masukkan MiniMagick kedalam uploader:
uploaders/image_uploader.rb
1 |
include CarrierWave::MiniMagick |
Sekarang kita hanya perlu memperkenalkan version baru ke uploader yang sudah kita miliki sebelumnya. Konsep versions (atau styles) digunakan di banyak file yang mengunggah library; maksudnya adalah file tambahan berdasarkan lampiran asli akan dibuat, misalnya, dengan dimensi atau format yang berbeda. Memperkenalkan version baru yang disebut thumb
:
uploaders/image_uploader.rb
1 |
version :thumb do |
2 |
process resize_to_fill: [350, 350] |
3 |
end |
Anda mungkin memiliki banyak version yang anda suka dan version dapat dibuat di atas version yang lain:
uploaders/image_uploader.rb
1 |
version :small_thumb, from_version: :thumb do |
2 |
process resize_to_fill: [20, 20] |
3 |
end |
Jika anda sudah mengunggah beberapa gambar, mereka tidak akan memiliki thumbnail yang tersedia. Ini bukan masalah, karena anda dapat membuatnya kembali menggunakan konsol Rails:
1 |
rails c |
2 |
Post.find_each {|post| post.image.recreate_versions!(:thumb) if post.image?} |
Terakhir, tampilkan thumbnail anda dengan tautan ke gambar asli:
views/posts/show.html.erb
1 |
<%= link_to(image_tag(@post.image.thumb.url, alt: 'Image'), @post.image.url, target: '_blank') if @post.image? %> |
Boot servernya dan amati hasilnya!
Menambahkan validasi
Saat ini pengunggahan kita sudah berfungsi, tetapi kita tidak memvalidasi apa saja yang user masukan sama sekali, yang tentu saja buruk. Jika kita ingin bekerja hanya dengan gambar, mari kita whitelist .png, .jpg dan .gif extensions:
uploaders/image_uploader.rb
1 |
def extension_whitelist |
2 |
%w(jpg jpeg gif png) |
3 |
end |
Anda juga dapat menambahkan pemeriksaan jenis konten dengan menuliskan metode content_type_whitelist
:
uploaders/image_uploader.rb
1 |
def content_type_whitelist |
2 |
/image\// |
3 |
end |
Selain itu, sangat mungkin untuk melakukan blacklist terhadap beberapa jenis file, misalnya file yang dapat dieksekusi, dengan menggunakan metode content_type_blacklist
.
Selain memvalidasi jenis dan ekstensi file, mari kita membuat file yang diunggah harus kurang dari 1 megabyte. Untuk melakukannya, kita akan membutuhkan tambahan gem yang mendukung validasi untuk ActiveModel:
Gemfile
1 |
gem 'file_validators' |
Lalu, lakukan instalasi:
1 |
bundle install
|
Sekarang perkenalkan validasi yang diinginkan (perhatikan bahwa saya juga menambahkan pemeriksaan untuk atribut title
dan body
):
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 } |
Hal berikutnya yang harus dilakukan adalah menambahkan I18n translation untuk error messages dari 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}" |
Saat ini, kita tidak menampilkan kesalahan validasi di mana saja, jadi mari buat shared partial:
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 %> |
Gunakan partial ini ke dalam form:
views/posts/_form.html.erb
1 |
<%= render 'shared/errors', object: post %> |
Sekarang coba unggah beberapa file tidak valid dan amati hasilnya. Ini seharus berhasil, tetapi jika anda memilih file yang valid dan tidak mengisi judul atau badan, maka cek akan tetap gagal dan pesan error akan ditampilkan. Namun, file akan dihapus dan user harus memilih gambar lagi, hal ini sangat tidak nyaman. Untuk memperbaikinya, kita perlu menambahkan field lain ke form.
Mempertahankan File di Seluruh Request
Mempertahankan seluruh file di formulir redisplay is actually quite easy. All you need to do is add a new hidden field and permit it inside the 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) |
Sekarang image_cache
akan terisi secara otomatis dan gambar tidak akan hilang. Mungkin akan berguna untuk menampilkan thumbnail juga sehingga pengguna memahami gambar telah berhasil diproses sebelumnya:
views/shared/_form.html.erb
1 |
<% if post.image? %> |
2 |
<%= image_tag post.image.thumb.url %> |
3 |
<% end %> |
Menghapus Gambar
Fitur lain yang sangat umum adalah kemampuan untuk remove attached files saat mengedit records. Dengan Carrierwave, menerapkan fitur ini tidak menjadi masalah. Tambahkan checkbox baru ke 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 %> |
Dan izinkan atribut remove_image
:
posts_controller.rb
1 |
params.require(:post).permit(:title, :body, :image, :remove_image, :image_cache) |
Nah itu dia! Untuk menghapus sebuah gambar secara manual, gunakan remove_image!
method:
1 |
@post.remove_image! |
Meng-upload dari Lokasi Jarak Jauh
Carrierwave juga menyediakan fitur yang sangat keren: kemampuan untuk mengunggah file dari lokasi yang jauh hanya dengan URL mereka. Mari tambahkan kemampuan ini sekarang dengan menuliskan bidang baru dan mengizinkan atribut terkait:
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) |
Betapa kerennya itu? Anda tidak perlu melakukan perubahan sama sekali, dan Anda dapat segera menguji fitur ini!
Melakukan Beberapa Upload Sekaligus
Misalkan kita ingin postingan kita memiliki beberapa lampiran yang tersedia. Dengan pengaturan saat ini tidak mungkin, tapi untungnya, Carrierwave supports skenario seperti itu juga. Untuk mengimplementasikan fitur ini, Anda perlu menambahkan serialized field (untuk SQLite) atau JSON (untuk Postgres atau MySQL). Saya lebih memilih opsi yang terakhir, jadi mari kita beralih ke adaptor database baru sekarang. Hapus sqlite3 gem dari Gemfile dan tambahkan pg sebagai gantinya:
Gemfile
1 |
gem 'pg' |
Lakukan instalasi:
1 |
bundle install
|
Modifikasi konfigurasi database seperti dibawah ini:
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 |
Membuat database Postgres sesuai, lalu generate dan terapkan migration:
1 |
rails g migration add_attachments_to_posts attachments:json |
2 |
rails db:migrate |
Jika Anda lebih memilih untuk tetap menggunakan SQLite, ikuti petunjuk yang tercantum dalam dokumentasi Carrierwave.
Sekarang lakukakn mount uploader (perhatikan plural form!):
model/post.rb
1 |
mount_uploaders :attachments, ImageUploader |
Saya menggunakan uploader yang sama untuk attachment, tetapi tentu saja anda dapat membuat yang baru dengan konfigurasi yang berbeda.
Tambahkan multiple file field ke form anda:
views/shared/_form.html.erb
1 |
<div>
|
2 |
<%= f.label :attachments %> |
3 |
<%= f.file_field :attachments, multiple: true %> |
4 |
</div>
|
Selama attachments
field akan berisi sebuah array, itu harus diizinkan dengan cara berikut:
posts_controller.rb
1 |
params.require(:post).permit(:title, :body, :image, :remove_image, :image_cache, :remote_image_url, attachments: []) |
Terakhir, Anda dapat iterate ulang attachment postingan anda dan menampilkannya seperti biasa:
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 %> |
Perhatikan bahwa setiap attachment akan memiliki thumbnail seperti yang kita konfigurasikan di ImageUploader
. Bagus!
Menggunakan Cloud Storage
Menggunakan file storage tidak selalu mudah dan / atau mungkin, misalnya, pada Heroku tidak mungkin menyimpan file kustom. Oleh karena itu Anda mungkin bertanya bagaimana cara memasangkan Carrierwave dengan cloud storage Amazon S3? Nah, itu tugas yang cukup mudah. Carrierwave bergantung pada gem fog-aws untuk menerapkan fitur ini:
Gemfile
1 |
gem "fog-aws" |
Lakukan instalasi:
1 |
bundle install
|
Mari kita buat penginisialisasi untuk Carrierwave dan konfigurasi cloud storage secara global:
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 |
Ada beberapa opsi lain yang tersedia, yang dapat ditemukan di dokumentasi.
Saya menggunakan dotenv-rails gem untuk mengatur variabel lingkungan dengan cara yang aman, tetapi Anda dapat memilih opsi lain. Namun, pastikan bahwa S3 key pair Anda tidak tersedia untuk umum, karena jika tidak, siapa pun dapat mengunggah apa pun ke storage Anda!
Selanjutnya, ganti storage baris storage :file
dengan:
uploaders/image_uploader.rb
1 |
storage :fog |
Selain S3, Carrierwave mendukung uploads ke Google Storage dan Rackspace. Layanan ini juga mudah diatur.
Kesimpulan
Cukup untuk hari ini! Kami telah mencakup semua fitur utama Carrierwave, dan sekarang Anda dapat mulai menggunakannya dalam proyek Anda. Carrierwave memiliki beberapa opsi tambahan yang tersedia, jadi silahkan pelajari dokumentasi.
Jika Anda bingung, jangan ragu untuk memposting pertanyaan Anda. Juga, anda mungkin dapat mengakses Carrierwave's wiki, yang mengposting artikel "how to" yang berguna untuk menjawab banyak pertanyaan umum.
Jadi saya berterima kasih untuk tetap bersama saya, dan happy coding!