class ApplicationForm
include ActiveModel::API
include ActiveModel::Attributes
def save
return false unless valid?
with_transaction { submit! }
end
private
def with_transaction(&block)
ApplicationRecord.transaction(&block)
end
def submit!
raise NotImplementedError
end
end
class FeedbackForm < ApplicationForm
attribute :name
attribute :email
attribute :message
validates :name, :email, :message, presence: true
def submit!
FeedbackMailer.new(name:, email:, message:).deliver_later
end
end
class FeedbackController < ApplicationController
def create
form = FeedbackForm.new(
params.require(:feedback).permit(:name, :email, :message)
)
if form.save
redirect ...
else
render ...
end
end
end
Note: There are instances when callbacks are preferred instead of adding logic to #submit! which is executed in the context of a DB transaction.
class ApplicationForm
...
define_callbacks :commit, only: :after
define_callbacks :save, only: :after
class << self
def after_save(method)
set_callback(:save, :after, method)
end
end
end