Skip to content

Instantly share code, notes, and snippets.

@forsbergplustwo
Created September 15, 2021 11:44
Show Gist options
  • Save forsbergplustwo/8f2fdb8b1d2b3dea39631d881010c3f2 to your computer and use it in GitHub Desktop.
Save forsbergplustwo/8f2fdb8b1d2b3dea39631d881010c3f2 to your computer and use it in GitHub Desktop.
# # USAGE
#
# # Include the concern in the class
# class Settings::CreateSetting
# include SimpleServiceObject
#
# # optional: include validation
# validates :shop, presence: true
#
# # mandatory: initialize the command with keyword arguments
# def initialize(shop)
# @shop = shop
# end
#
# # mandatory: define a #call method.
# # Return value is available through #payload
# # Errors are available through #errors (uses ActiveModel for compatibility with view helpers, I18n etc.)
# def call
# create_setting
# end
#
# private
#
# attr_reader :shop
#
# def create_setting
# languages = []
# languages << shop.default_locale ||= "en"
#
# Setting.create(shop: shop, languages: languages)
# end
# end
#
# #In your locale file you can setup custom errors
#
# #config/locales/en.yml
# en:
# activemodel:
# errors:
# models:
# settings
# create_setting:
# failure: "Could not create setting!"
#
#
# # In your Controller:
#
# class SettingsController < ApplicationController
# def create
# # initialize and execute the service
# # NOTE: `.call` is a shortcut for `.new(args).call`
# service = Settings::CreateSetting.call(current_shop)
#
# # service.payload will contain the return value (even if invalid)
#
# @setting = service.payload
#
# # check service outcome
# if service.success?
# redirect_to @setting
# else
# flash.now[:alert] = t(service.errors.full_messages.to_sentence)
# render :new
# # in the view it will have access to @setting and can
# # show the validation errors normally @setting.errors...
# # and generate forms: <%= form_with model: @setting do |form| %>
# end
# end
# end
#
# # TEST WITH RSPEC
#
# require "rails_helper"
#
# RSpec.describe Settings::CreateSetting do
# describe ".call" do
# it "creates setting with default language" do
# shop = create(:shop)
# expect(shop.setting).to be nil
#
# service = Settings::CreateSetting.call(shop: shop)
#
# expect(service.errors).to be_empty
# expect(service.success?).to be true
# expect(service.payload).to be_instance_of Setting
#
# expect(shop.setting).not_to be nil
# expect(shop.setting.languages).to eq(["en"])
# end
# end
# end
#
# # Inspired by https://github.com/nebulab/simple_command
#
module SimpleServiceObject
extend ActiveSupport::Concern
include ActiveModel::Validations
included do
attr_reader :payload
end
class_methods do
def call(*args, **kwargs)
new(*args, **kwargs)._call
end
end
def _call
fail NotImplementedError unless defined?(call)
@called = true
if valid?
@payload = call
# If an invalid ActiveRecord/ActiveModel record is returned by :call, add a default
# :failure error, which also sets :success? to false. Allows us to return invalid
# records for easier use in views and forms.
errors.add(:base, :failure) if @payload&.errors&.any?
end
self
end
def success?
called? && !failure?
end
alias successful? success?
def failure?
called? && errors.any?
end
# Provided by ActiveModel::Validations, fail loudly if removed.
def errors
fail NotImplementedError unless defined?(super)
super
end
private
def called?
@called ||= false
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment