Setelah sekian lama pensi, akhirnya ada kesempatan untuk nulis lagi.

Pada kesempatan ini saya mau bahas form object. Jika anda sudah mengenal atau pernah menggunakan form object di projek anda, mungkin tulisan ini bisa untuk refrerensi.

Saya yakin dari pembaca sudah banyak menggenal form object. Form object adalah sebuah object yang mempresentasikan sebuah form. Jadi dengan form object ini, sebuah form yang kita buat adalah sebuah object yang utuh.

Form object ini baiknya digunakan ketika anda memiliki sebuah form yang melibatkan lebih dari satu model, atau bahkan form anda tidak memiliki table atau model apapun. Namun jika form anda hanya terdiri dari satu model, maka anda tidak perlu menggunakan form object ini karena akan over engineering.

Seperti biasa, agar konsepnya lebih masuk kita akan membuat sebuah studi kasus. Studi kasusnya simple saja.Kita akan membuat sebuah form untuk menginput organisasi baru.

Kira-kira formnya akan seperti in:

Nama Organisasi: <String>
Nama Anda: <String>
Email Anda: <String>

<Button Submit>

Dan untuk rancangan tablenya akan seperti ini:

table_name: organizations
id: integer
name: string
user_id: integer
created_at: datetime
updated_at: datetime

---

table_name: users
id: integer
name: string
email: string
created_at: string
updated_at: string

Jadi, form yang akan kita buat akan memuat dua buah model yang berbeda.

Sekarang mari kita buat form tersebut dengan menulis kode testnya terlebih dahulu:

require 'rails_helper'

RSpec.describe 'Create new organization', type: :system do
  context 'with valid params' do
    it 'returns success message' do
      visit new_organization_path
      fill_in :organization_form_name, with: 'Ruby conf'
      fill_in :organization_form_user_name, with: 'Philip Lambok'
      fill_in :organization_form_user_email, with: 'philiplambok@gmail.com'
      click_on 'Submit'
      expect(page).to have_content 'Organization has been created'
    end
  end

  context 'with invalid params' do
    it 'returns error message' do
      visit new_organization_path
      click_on 'Submit'
      expect(page).to have_content "Name can't be blank"
      expect(page).to have_content "User name can't be blank"
      expect(page).to have_content "User email can't be blank"
    end
  end
end

Setelah specnya telah dibuat, maka sekarang mari kita buat kode testnya menjadi passed.

Tambahkan routes.rb

Rails.application.routes.draw do
  resources :organizations
end

Tambahkan controller organizations_controller.rb

class OrganizationsController < ApplicationController
  def new
    @form = OrganizationForm.new
  end

  def create
    form = OrganizationForm.new(form_params)
    if form.save
      flash[:success] = 'Organization has been created'
    else
      flash[:error] = form.errors.full_messages.join(', ')
    end
    redirect_to new_organization_path
  end

  private

  def form_params
    params.require(:organization_form).permit(
      :name, :user_name, :user_email
    )
  end
end

Lalu sekarang tambahkan form object: organization_form.rb-nya

class OrganizationForm
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :name, :string
  attribute :user_name, :string
  attribute :user_email, :string

  validates :name, presence: true
  validates :user_name, presence: true
  validates :user_email, presence: true

  def save
    return false if invalid?

    user = User.new(name: user_name, email: user_email)
    user.save
    organization = user.organizations.build(name: name)
    organization.save

    true
  end
end

Jangan lupa juga untuk model-modelnya:

class Organization < ApplicationRecord
  belongs_to :user
end

class User < ApplicationRecord
  has_many :organizations
end

Dan terakhir untuk views-nya

<% flash.each do |key, value| %>
  <p><%= value %></p>
<% end %>

<%= form_with model: @form, url: organizations_path, local: true do |form| %>
  <div>
    <%= form.label :name %>
    <%= form.text_field :name %>
  </div>
  <div>
    <%= form.label :user_name %>
    <%= form.text_field :user_name %>
  </div>
  <div>
    <%= form.label :user_email %>
    <%= form.text_field :user_email %>
  </div>
  <%= form.submit "Submit" %>
<% end %>

Lalu jalankan testnya, maka hasilnya akan passed.

Pada form object ini saya menggunakan dua module yang ada di active-model, yaitu ActiveModel::Model agar form kita bisa layaknya model memiliki build-in validations, errors, invalid, valid, dll.

Selain itu juga saya menggunakan ActiveModel::Attributes agar saya bisa menggunakan callback attribute di form object yang saya punya.

Dibandingkan dengan menggunakan attr_accessor, menggunakan attribute kita bisa men-define tipe data dari attribute kita, dan juga kita bisa membuat default value disaat kita perlu.


Saya kira cukup untuk tulisan tentang pengenalan form object ini, jika anda tertarik dengan kode sumbernya dapat melihat kodenya disini.

Selamat hacking~