Finally implemented Rails Form object

Preface

――It seems that you can write a form for a model that is not tied to ActiveRecord in a monolithic Rails application by using Form Object. ――I knew it for a long time, but I had never actually implemented it by myself. ――This time, I got an opportunity to implement it by myself, so I will keep a record. ――Since it is announced and the implementation is blurred to the extent that there is no problem, there may be typographical errors in the process. Please note.

What you want to make

--I want to add a page for admins that sends push notifications to the app.

Implementation without Form object

--First, I implemented it as follows without using Form object.

config.rb


namespace :admin do
  resources :push_notifications, only: [:new, :create]
end

app/controllers/admin/push_notifications_controller.rb



class Admin::PushNotificationsController < Admin::BaseController
  def new
    @demo_form = Admin::PushNotificationDemoForm.new
  end

  def create
    @demo_form = Admin::PushNotificationDemoForm.new(
      # user_If the id is an empty string or a string that cannot be converted to an int, an error will be thrown, but in form f.number_It is good because the field is specified
      user_id: Integer(demo_form_params['user_id']),
      title: demo_form_params['title'],
      body: demo_form_params['body'],
      url: demo_form_params['url'],
    )

    if @demo_form.save
      flash[:notice] = "user#{@demo_form.user_id}of#{Rails.env}アプリへofプッシュ通知が送信されました。"
      redirect_to new_admin_push_notification_path
    else
      flash.now[:alert] = @demo_form.errors.full_messages.join(" / ")
      return render :new
    end
  end

  private
  def demo_form_params
    params.require(:admin_push_notification_demo_form).permit(:user_id, :title, :body, :url)
  end
end

app/views/admin/new.html.haml



%h1= "Test sending push notifications"

= form_with(url: admin_push_notifications_path, local: true) do |f|
  .div
    = f.label :user_id, 'User ID (Mandatory)'
    = f.number_field :user_id, autofocus: true, required: true
  .div
    = f.label :title, 'Title: (Mandatory)'
    = f.text_area :title, autofocus: true, required: true
  .div
    = f.label :body, 'Body'
    = f.text_area :body, autofocus: true
  .div
    = f.label :url, 'URL'
    = f.text_field :url, autofocus: true
  .div
    = f.submit("Send")

――As you can see, the repeated validations performed in the Controller before the desired process `` are impressive in a bad sense.

Implement this using a Form object

config.rb


namespace :admin do
  resources :push_notifications, only: [:new, :create]
end

app/controllers/admin/push_notifications_controller.rb


class Admin::PushNotificationsController < Admin::BaseController
  def new
    @demo_form = Admin::PushNotificationDemoForm.new
    render 'new'
  end

  def create
    @demo_form = Admin::PushNotificationDemoForm.new(demo_form_params)

    if @demo_form.save
      flash[:notice] = "user#{@demo_form.user_id}A push notification has been sent to."
      redirect_to new_admin_push_notification_path
    else
      flash.now[:alert] = @demo_form.errors.full_messages.join(" / ")
      return render :new
    end
  end

  private
  def demo_form_params
    params.require(:admin_push_notification_demo_form).permit(:user_id, :title, :body, :url)
  end
end

app/forms/push_notification_demo_form.rb


class Admin::PushNotificationDemoForm
  include ActiveModel::Model

  validates :user_id, presence: true
  validates :title, presence: true
  validate :is_valid_user_id_and_has_individual_job

  def save
    return false if invalid?

    VisitNotificationService.new.notify(
      user_job_id: @job.id,
      user_id: @user_id,
      push_notification_content: VisitNotificationService.build_push_notification(
        title: @title,
        body: @body,
        url: @url,
      )
    )
    return true
  end

  private

  def is_valid_user_id_and_has_individual_job
    user = User.find_by(id: @user_id)
    unless user
      errors.add(:user_id, 'Is invalid.')
      return false
    end

    @job = user.individual_job
    unless @job
      errors.add(:base, "Specified User(#{@user_id})There is no Individual Job associated with.")
      return false
    end

    true
  end
end

app/views/admin/new.html.haml



%h1= "Test sending push notifications"

= form_for(@demo_form, url: admin_push_notifications_path, local: true) do |f|
  .div
    = f.label :user_id, 'User ID (Mandatory)'
    = f.number_field :user_id, autofocus: true, required: true
  .div
    = f.label :title, 'Title: (Mandatory)'
    = f.text_area :title, autofocus: true, required: true
  .div
    = f.label :body, 'Body'
    = f.text_area :body, autofocus: true
  .div
    = f.label :url, 'URL'
    = f.text_field :url, autofocus: true
  .div
    = f.submit("Send")


--As you can see, the validation (essentially business logic) is pushed into the form object so it can be implemented very nicely.

--In my implementation this time, the model specified by form_with and the url are different. --It's also convenient to be able to store error messages in errors, which are instance variables of the form object (like active record).

reference

Recommended Posts

Finally implemented Rails Form object
Ruby, Rails finally installed
[Rails] (Supplement) Implemented follow function
[Rails] Japanese localization of error message when using Form object
[Rails] Check the contents of the object
Implemented mail sending function with rails
Eliminate Rails FatModel with value object
[Rails] Display form error messages asynchronously
Implement a contact form in Rails