Sejak memasang "dark" theme, saya cenderung menjadi malas menulis. Untuk sementara, dark theme saya disable dulu yaa. Terima kasih (^_^) (bandithijo, 2024/09/15) ●
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.
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.
1
2
3
4
5
6
7
8
9
10
11
class AlterExperiencesDurationToInteger < ActiveRecord::Migration[5.2]
def up
remove_column :experiences, :duration, :time
add_column :experiences, :duration, :integer
end
def down
remove_column :experiences, :duration, :integer
add_column :experiences, :duration, :time
end
end
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.
1
2
3
4
5
6
7
create_table "experiences", force: :cascade do |t|
# ...
# ...
t.integer "duration"
# ...
# ...
end
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”.
1
2
3
4
5
6
7
8
9
def formatted_duration(total_minute)
hours = total_minute / 60
minutes = total_minute % 60
if minutes == 0
pluralize("#{ hours }", "hour")
else
pluralize("#{ hours }", "hour") + " " + pluralize("#{ minutes }", "minute")
end
end
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
2
3
4
5
6
<div class="row no-gutters">
<div class="col-sm-6">
<span class="icon-time1 mr-1"></span>
<span>Duration: <%= formatted_duration(experience.duration) %></span>
</div>
</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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<div class="form-group row no-gutters form-custom mb-3">
<label class="col-md-3 col-form-label p-sm-0 d-flex align-items-center">Duration</label>
<div class="col-md-9">
<div class="row no-gutters">
<% if controller_name == "experiences" && action_name == "edit" %>
<% hours = experience.duration / 60 %>
<% minutes = experience.duration % 60 %>
<%= hidden_field_tag 'duration-hours', hours %>
<%= hidden_field_tag 'duration-minutes', minutes %>
<%= f.time_select :duration,
{ start_hour: 1, end_hour: 12, minute_step: 10, time_separator: ' ',
prompt: { hour: "Choose hour", minute: "Choose minute"}, ignore_date: true},
class: "form-control col-md-2 px-2 mr-md-2 mb-md-0 mb-2" %>
<% else %>
<%= f.time_select :duration,
{ start_hour: 1, end_hour: 12, minute_step: 10, time_separator: ' ',
prompt: { hour: "Choose hour", minute: "Choose minute"}, ignore_date: true},
class: "form-control col-md-2 px-2 mr-md-2 mb-md-0 mb-2" %>
<% end %>
</div>
</div>
</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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class ExperiencesController < ApplicationController
def new
@experience = Experience.new
end
def create
@experience = Experience.new(experience_params)
@hours = (params[:experience]['duration(4i)']).to_i
@minutes = (params[:experience]['duration(5i)']).to_i
@experience.duration = @hours * 60 + @minutes
if @experience.save
redirect_to experience_path(@experience)
else
render :new
end
end
def edit
@experience = Experience.find(params[:id])
end
def update
@experience = Experience.find(params[:id])
@hours = (params[:experience]['duration(4i)']).to_i
@minutes = (params[:experience]['duration(5i)']).to_i
@experience.duration = @hours * 60 + @minutes
@duration = @experience.duration
if @experience.update(experience_params)
@experience.update(duration: @duration)
redirect_to experience_path(@experience)
else
render :edit
end
end
private
def experience_params
params.require(:experience).permit( ..., ...., :duration, ..., ...)
end
end
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
Lisensi
Atribusi-NonKomersial-BerbagiSerupa 4.0 Internasional (CC BY-NC-SA 4.0)
Penulis
My journey kicks off from reading textbooks as a former Medical Student to digging bugs as a Software Engineer – a delightful rollercoaster of career twists. Embracing failure with the grace of a Cat avoiding water, I've seamlessly transitioned from Stethoscope to Keyboard. Armed with ability for learning and adapting faster than a Heart Beat, I'm on a mission to turn Code into a Product.
- Rizqi Nur Assyaufi