Rails time_select dengan Integer
Prerequisite
ruby 2.6.3
rails 5.2.3
postgresql 11.5
Prakata
Saya mendapatkan kasus dimana saya harus membuat “duration” field dengan tipe data integer. Yang mana sebelumnya duration field ini saya set dengan tipe data time.
Tujuan dari penggunaan tipe data integer pada duration field agar nantinya dapat dengan mudah diolah, seperti difilter berdasarkan jumlah menit tertentu, dan lain sebagainya.
Target
Target yang ingin dicapai adalah, saya perlu menyimpan data di dalam kolom duration dalam satuan menit.
Contoh, untuk durasi selama 8 jam 30 menit, berarti data akan disimpan sebesar 510 menit.
Tampilannya kira-kira seperti ini, untuk pemilihan jam dan menit.
Gambar 1. time_select untuk jam dan menit
Permasalahan
time_select
adalah salah satu method yang dimiliki oleh ActionView::Helpers::DateHelper
. Beberapa method lain diantaranya seperti, date_select
, datetime_select
, dll.1
Secara normal, time_select
ini akan bekerja dengan baik pada field dengan tipe data time. Namun, karena kebutuhan project, saya perlu memodifikasi agar time_select
dapat menyimpan data ke dalam field dengan tipe data integer.
Solusi
Sebagai catatan, cara yang saya lakukan ini mungkin bukan merupakan cara yang baik. Mengingat masih minimnya pengalaman dan jam terbang saya dalam membangun web aplikasi, terkhusus dengan Ruby on Rails.
Oke, kembali ke pokok perbincangan utama.
Untuk merubah kolom/field duration dari tipe data time menjadi integer, saya melakukan migration seperti ini.
$ rails g migration alter_experiences_duration_to_integer
Kemudian menuliskan manual, masing-masing method up dan down-nya.
1class AlterExperiencesDurationToInteger < ActiveRecord::Migration[5.2]2 def up3 remove_column :experiences, :duration, :time4 add_column :experiences, :duration, :integer5 end67 def down8 remove_column :experiences, :duration, :integer9 add_column :experiences, :duration, :time10 end11end
Perlu diketahui, saya melakukan perintah remove_column
karena web aplikasi yang saya bangun, belum memiliki data sungguhan. Apabila sudah memiliki data sungguhan, sangat dihindari untuk melakukan penghapusan kolom/field. Lebih baik membuat kolom/field yang baru.
Berikut ini adalah contoh dari skema tabel experiences yang saya miliki.
1create_table "experiences", force: :cascade do |t|2 # ...3 # ...4 t.integer "duration"5 # ...6 # ...7end
Karena saya menggunakan seed untuk membuat data-data dummy, saya perlu merubah formatnya dari format time menjadi total menit.
Sebelum,
duration: ['01:30', '03:00', '06:30', '08:00'].sample
Sesudah,
duration: [90, 180, 390, 480].sample
Karena data sudah disimpan dalam bentuk integer dan dalam satuan menit, maka saya perlu bantuan helper method untuk mengkonversi bentuk dari “hanya menit” menjadi “jam dan menit”.
1def formatted_duration(total_minute)2 hours = total_minute / 603 minutes = total_minute % 604 if minutes == 05 pluralize("#{ hours }", "hour")6 else7 pluralize("#{ hours }", "hour") + " " + pluralize("#{ minutes }", "minute")8 end9end
Dengan begini, saya memiliki helper method bernama formatted_duration()
yang dapat saya manfaatkan untuk mengkonversi tampilan data duration yang hanya dalam satuan menit, menjadi bentuk jam dan menit.
Kira-kira seperti ini contoh penggunaannya dalam view template.
1<div class="row no-gutters">2 <div class="col-sm-6">3 <span class="icon-time1 mr-1"></span>4 <span>Duration: <%= formatted_duration(experience.duration) %></span>5 </div>6</div>
Nah, sekarang bagian input field.
Pada bagian ini, harus saya akui, cukup merepotkan. Saya menghabiskan banyak sekali percobaan untuk membuat time_select
dapat berfungsi seperti tujuan saya.
Pertama-tama pada view template dulu.
1<div class="form-group row no-gutters form-custom mb-3">2 <label class="col-md-3 col-form-label p-sm-0 d-flex align-items-center">Duration</label>3 <div class="col-md-9">4 <div class="row no-gutters">5 <% if controller_name == "experiences" && action_name == "edit" %>6 <% hours = experience.duration / 60 %>7 <% minutes = experience.duration % 60 %>8 <%= hidden_field_tag 'duration-hours', hours %>9 <%= hidden_field_tag 'duration-minutes', minutes %>10 <%= f.time_select :duration,11 { start_hour: 1, end_hour: 12, minute_step: 10, time_separator: ' ',12 prompt: { hour: "Choose hour", minute: "Choose minute"}, ignore_date: true},13 class: "form-control col-md-2 px-2 mr-md-2 mb-md-0 mb-2" %>14 <% else %>15 <%= f.time_select :duration,16 { start_hour: 1, end_hour: 12, minute_step: 10, time_separator: ' ',17 prompt: { hour: "Choose hour", minute: "Choose minute"}, ignore_date: true},18 class: "form-control col-md-2 px-2 mr-md-2 mb-md-0 mb-2" %>19 <% end %>20 </div>21 </div>22</div>
Algoritma dari kode di atas adalah:
- Jika data baru, maka masuk ke dalam blok else, yang akan digunakan oleh
experiences#new
. - Jika data edit, maka masuk de dalam blok if, yang artinya data duration yang berupa menit, akan dipecah menjadi dua variabel,
hours
danminutes
. Kemudian dimasukkan ke dalamhidden_field_tag
masing-masing, dan akan dikrimkan keexperiences#edit
.
Nah, berikut ini isi dari experiences_controller.rb
yang akan menerima data dari kedua inputan pada view template di atas.
1class ExperiencesController < ApplicationController23 def new4 @experience = Experience.new5 end67 def create8 @experience = Experience.new(experience_params)910 @hours = (params[:experience]['duration(4i)']).to_i11 @minutes = (params[:experience]['duration(5i)']).to_i12 @experience.duration = @hours * 60 + @minutes1314 if @experience.save15 redirect_to experience_path(@experience)16 else17 render :new18 end19 end2021 def edit22 @experience = Experience.find(params[:id])23 end2425 def update26 @experience = Experience.find(params[:id])2728 @hours = (params[:experience]['duration(4i)']).to_i29 @minutes = (params[:experience]['duration(5i)']).to_i30 @experience.duration = @hours * 60 + @minutes31 @duration = @experience.duration3233 if @experience.update(experience_params)34 @experience.update(duration: @duration)35 redirect_to experience_path(@experience)36 else37 render :edit38 end39 end4041 private4243 def experience_params44 params.require(:experience).permit( ..., ...., :duration, ..., ...)45 end46end
Selesai!
Pesan Penulis
Sekian, mudah-mudahan catatan saya yang masih banyak kurangnya ini dapat bermanfaat buat teman-teman.
Mungkin pada kesempatan yang lain, akan saya buatkan repo khusus agar teman-teman langsung merasakan fiturnya.
Terima kasih (^_^)v
Referensi
-
api.rubyonrails.org/v5.2.1/classes/ActionView/Helpers/DateHelper.html
Diakses tanggal: 2019/12/04 -
guides.rubyonrails.org/form_helpers.html#using-date-and-time-form-helpers
Diakses tanggal: 2019/12/04