BanditHijo.dev

Rails time_select dengan Integer

Created at: 2019-12-04
Author by: BanditHijo

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

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.

db/migrate/20191122060352_alter_experiences_duration_to_integer.rb
1class AlterExperiencesDurationToInteger < ActiveRecord::Migration[5.2]
2 def up
3 remove_column :experiences, :duration, :time
4 add_column :experiences, :duration, :integer
5 end
6
7 def down
8 remove_column :experiences, :duration, :integer
9 add_column :experiences, :duration, :time
10 end
11end

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.

db/schema.rb
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”.

app/helpers/experiences_helper.rb
1def formatted_duration(total_minute)
2 hours = total_minute / 60
3 minutes = total_minute % 60
4 if minutes == 0
5 pluralize("#{ hours }", "hour")
6 else
7 pluralize("#{ hours }", "hour") + " " + pluralize("#{ minutes }", "minute")
8 end
9end

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.

app/views/experiences/index.html.erb
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.

app/views/experiences/new.html.erb,app/views/experiences/edit.html.erb
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:

  1. Jika data baru, maka masuk ke dalam blok else, yang akan digunakan oleh experiences#new.
  2. Jika data edit, maka masuk de dalam blok if, yang artinya data duration yang berupa menit, akan dipecah menjadi dua variabel, hours dan minutes. Kemudian dimasukkan ke dalam hidden_field_tag masing-masing, dan akan dikrimkan ke experiences#edit.

Nah, berikut ini isi dari experiences_controller.rb yang akan menerima data dari kedua inputan pada view template di atas.

app/controllers/experiences_controller.rb
1class ExperiencesController < ApplicationController
2
3 def new
4 @experience = Experience.new
5 end
6
7 def create
8 @experience = Experience.new(experience_params)
9
10 @hours = (params[:experience]['duration(4i)']).to_i
11 @minutes = (params[:experience]['duration(5i)']).to_i
12 @experience.duration = @hours * 60 + @minutes
13
14 if @experience.save
15 redirect_to experience_path(@experience)
16 else
17 render :new
18 end
19 end
20
21 def edit
22 @experience = Experience.find(params[:id])
23 end
24
25 def update
26 @experience = Experience.find(params[:id])
27
28 @hours = (params[:experience]['duration(4i)']).to_i
29 @minutes = (params[:experience]['duration(5i)']).to_i
30 @experience.duration = @hours * 60 + @minutes
31 @duration = @experience.duration
32
33 if @experience.update(experience_params)
34 @experience.update(duration: @duration)
35 redirect_to experience_path(@experience)
36 else
37 render :edit
38 end
39 end
40
41 private
42
43 def experience_params
44 params.require(:experience).permit( ..., ...., :duration, ..., ...)
45 end
46end

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

  1. api.rubyonrails.org/v5.2.1/classes/ActionView/Helpers/DateHelper.html
    Diakses tanggal: 2019/12/04

  2. guides.rubyonrails.org/form_helpers.html#using-date-and-time-form-helpers
    Diakses tanggal: 2019/12/04