Last active
September 10, 2020 06:17
-
-
Save danieldraper/a0916e7f8a62aeea6a293f79ba1555ce to your computer and use it in GitHub Desktop.
Service object and form object using ActiveModel and dry-rb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "dry/monads/result" | |
class ApplicationForm | |
include Dry::Monads::Result::Mixin | |
include ActiveModel::Model | |
def self.attribute(name, options = {}) | |
self.send(:attr_accessor, name) | |
_attributes << Attribute.new(name, options) | |
end | |
def self._attributes | |
@_attributes ||= [] | |
end | |
def initialize(attributes = {}) | |
super | |
self.class._attributes.each do |attribute| | |
attribute.apply_default_to(self) | |
end | |
end | |
def attributes | |
attrs = {} | |
self.class._attributes.each do |a| | |
attrs[a.name] = self.send(a.name) | |
end | |
attrs | |
end | |
def attributes_for_model | |
attrs = {} | |
self.class._attributes.select { |attr| attr.options[:virtual] == false }.each do |attr| | |
attrs[attr.name] = self.send(attr.name) | |
end | |
attrs | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "dry/monads/result" | |
class ApplicationOperation | |
include Dry::Monads::Result::Mixin | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Attribute | |
attr_accessor :name, :options | |
def initialize(name, options = {}) | |
@name = name | |
@options = options | |
extract_options | |
end | |
def apply_default_to(form) | |
if form.send(@name).nil? | |
form.send("#{@name}=", default_value(form)) if @apply_default | |
end | |
end | |
private | |
def extract_options | |
@apply_default = true | |
@default = options.fetch(:default) { @apply_default = false; nil } | |
@skip_validations = options.fetch(:skip_validations, false) | |
options[:virtual] ||= false | |
end | |
def default_value(context) | |
if @default.respond_to?(:call) | |
context.instance_eval(&@default) | |
else | |
@default | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Manage::CreateMemberForm < ApplicationForm | |
include ActiveModel::Validations::Callbacks | |
attribute :first_name | |
attribute :last_name | |
attribute :dob | |
attribute :reference | |
attribute :status | |
attribute :joined_on | |
attribute :account_id | |
attribute :email | |
attribute :mobile | |
attribute :country_code, virtual: true | |
phony_normalize :mobile, default_country_code: "AU" | |
validates :first_name, presence: true | |
validates :dob, allow_blank: true, date: { before: -> { Date.current } } | |
validates :joined_on, allow_blank: true, date: { on_or_before: -> { Date.current } } | |
validates :email, allow_blank: true, email: true | |
validates :mobile, allow_blank: true, phony_plausible: true | |
validates :account_id, presence: true | |
validates :country_code, presence: true | |
def call | |
ActiveRecord::Base.transaction do | |
begin | |
if valid? | |
Success(persist!) | |
else | |
Failure([:validation, self]) | |
end | |
rescue => exception | |
Failure([:exception, exception]) | |
end | |
end | |
end | |
private | |
def persist! | |
Member.create(attributes_for_model) | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Manage::CreateMemberOperation < ApplicationOperation | |
include Dry::Matcher.for(:call, with: ResultMatcher) | |
attr_reader :form_class | |
def initialize(form_class) | |
@form_class = form_class | |
end | |
def call(form_params) | |
form_class.new(form_params).call do |on| | |
on.success do |result| | |
Success(result) | |
end | |
on.failure do |form| | |
Failure([:validation, form]) | |
end | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Manage::MembersController < Manage::ApplicationController | |
def new | |
@form = create_form.new | |
end | |
def create | |
create_operation.(create_params) do |on| | |
on.success do | |
redirect_to manage_members_path, notice: t(".success") | |
end | |
on.failure :validation do |form| | |
@form = form | |
render :new | |
end | |
end | |
end | |
private | |
def permitted_params(form_key) | |
params.require(form_key).permit( | |
:first_name, | |
:last_name, | |
:dob, | |
:reference, | |
:status, | |
:joined_on, | |
:email, | |
:mobile, | |
) | |
end | |
def create_params | |
permitted_params(:manage_create_member_form).merge( | |
account_id: current_account.id, | |
country_code: current_account.country_code | |
) | |
end | |
def update_params | |
permitted_params(:manage_update_member_form).merge( | |
id: params[:id], | |
country_code: current_account.country_code | |
) | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%= simple_form_for(@form, url: manage_members_path, method: :post, html: { autocomplete: "off" }) do |form| %> | |
<div class="card card__form"> | |
<div class="card-header">Personal Details</div> | |
<div class="card-body"> | |
<div class="col-lg-6 col-xl-4"> | |
<div class="form-row"> | |
<%= form.input :first_name, label: t("manage.members.forms.labels.first_name"), wrapper_html: { class: "col-md-6" } %> | |
<%= form.input :last_name, label: t("manage.members.forms.labels.last_name"), wrapper_html: { class: "col-md-6" } %> | |
</div> | |
<%= form.input :dob, as: :string, label: t("manage.members.forms.labels.dob"), input_html: { class: "js-date" } %> | |
</div> | |
</div> | |
</div> | |
<div class="card card__form"> | |
<div class="card-header">Membership Details</div> | |
<div class="card-body"> | |
<div class="col-lg-6 col-xl-4"> | |
<%= form.input :reference, label: t("manage.members.forms.labels.reference"), help: { title: t(".help.reference.title"), content: t(".help.reference.content") } %> | |
<%= form.input :status, label: t("manage.members.forms.labels.status") %> | |
<%= form.input :joined_on, as: :string, label: t("manage.members.forms.labels.joined_on"), input_html: { class: "js-date" } %> | |
</div> | |
</div> | |
</div> | |
<div class="card card__form"> | |
<div class="card-header">Contact Details</div> | |
<div class="card-body"> | |
<div class="col-lg-6 col-xl-4"> | |
<%= form.input :email, label: t("manage.members.forms.labels.email") %> | |
<%= form.input :mobile, label: t("manage.members.forms.labels.mobile") %> | |
</div> | |
</div> | |
<div class="card-footer"> | |
<%= form.button :submit, t("manage.members.forms.labels.submit"), class: "btn btn-primary" %> | |
<%= link_to t("manage.members.forms.labels.cancel"), manage_members_path, class: "btn btn-secondary" %> | |
</div> | |
</div> | |
<% end %> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
ResultMatcher = Dry::Matcher.new( | |
success: Dry::Matcher::Case.new( | |
match: -> result, *pattern { | |
result = result.to_result | |
result.success? | |
}, | |
resolve: -> result { | |
result = result.to_result | |
result.value! | |
}, | |
), | |
failure: Dry::Matcher::Case.new( | |
match: -> result, *pattern { | |
result = result.to_result | |
result.failure? && (pattern.any? ? pattern.include?(result.failure.first) : true) | |
}, | |
resolve: -> result { | |
result = result.to_result | |
result.failure.last | |
}, | |
) | |
) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Manage::UpdateMemberForm < ApplicationForm | |
include ActiveModel::Validations::Callbacks | |
attribute :id, virtual: true | |
attribute :first_name | |
attribute :last_name | |
attribute :dob | |
attribute :reference | |
attribute :status | |
attribute :joined_on | |
attribute :email | |
attribute :mobile | |
attribute :country_code, virtual: true | |
phony_normalize :mobile, default_country_code: "AU" | |
validates :first_name, presence: true | |
validates :dob, allow_blank: true, date: { before: -> { Date.current } } | |
validates :joined_on, allow_blank: true, date: { on_or_before: -> { Date.current } } | |
validates :email, allow_blank: true, email: true | |
validates :mobile, allow_blank: true, phony_plausible: true | |
validates :country_code, presence: true | |
def initialize(attributes = {}) | |
super | |
# TODO: Find a better abstraction... | |
@first_name ||= member.first_name | |
@last_name ||= member.last_name | |
@dob ||= member.dob | |
@reference ||= member.reference | |
@status ||= member.status | |
@joined_on ||= member.joined_on | |
@email ||= member.email | |
@mobile ||= member.mobile | |
end | |
def call | |
ActiveRecord::Base.transaction do | |
begin | |
if valid? | |
Success(persist!) | |
else | |
Failure([:validation, self]) | |
end | |
rescue => exception | |
Failure([:exception, exception]) | |
end | |
end | |
end | |
private | |
def persist! | |
member.update_attributes(attributes_for_model) | |
end | |
def member | |
@member ||= Member.find(id) | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Manage::UpdateMemberOperation < ApplicationOperation | |
include Dry::Matcher.for(:call, with: ResultMatcher) | |
attr_reader :form_class | |
def initialize(form_class) | |
@form_class = form_class | |
end | |
def call(form_params) | |
form_class.new(form_params).call do |on| | |
on.success do |result| | |
Success(result) | |
end | |
on.failure do |form| | |
Failure([:validation, form]) | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment