Skip to content

Instantly share code, notes, and snippets.

@mrbongiolo
Last active October 26, 2020 00:41
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mrbongiolo/9cbd29f0c599de9ca5c81e83c70a745c to your computer and use it in GitHub Desktop.
Save mrbongiolo/9cbd29f0c599de9ca5c81e83c70a745c to your computer and use it in GitHub Desktop.
Ruby on Rails Operation
# usually I place this file on app/lib/operation.rb
# frozen_string_literal: true
module Operation
class Flow
def self.call(output)
new(output)
end
attr_reader :output
def initialize(output)
@output = output
end
def success?
is_a? Flow::Success
end
def failure?
is_a? Flow::Failure
end
class Success < self
end
class Failure < self
end
end
class Result
def initialize(flow)
@flow = flow
end
def success?
@flow.success?
end
def failure?
@flow.failure?
end
def call
return yield(self) if block_given?
[success?, @flow.output]
end
def success
return unless success?
return yield(@flow.output) if block_given?
@flow.output
end
def failure
return unless failure?
return yield(@flow.output) if block_given?
@flow.output
end
end
def self.included(includer)
includer.extend ClassMethods
end
module ClassMethods
def call(*input)
new.process(*input) # [success?, output]
raise 'Invalid Operation return Type, it must be a Flow' unless flow.is_a? Flow
if block_given?
Result.new(flow).call { |m| yield(m) }
else
Result.new(flow)
end
end
end
end

An operation using the module:

module Api
  module V1
    module Page
      class Update
        include Operation

        attr_reader :resource

        SCHEMA = Dry::Validation.JSON do
          optional(:keywords, [:nil, Yubb::Types::ArrayOfStrings]).each(:str?)
          optional(:description, [:nil, :string]).filled(:str?)
        end

        def process(resource, input)
          @resource = resource

          schema = SCHEMA.call(input)

          return Flow::Failure.call(schema.errors) if schema.failure?

          resource.update(input)
          
          # if you need to send an email, invoke a job or something else, all of this could be done here

          Flow::Success.call decorated_resource
        end

        private

        def decorated_resource
          Decorators::Resource.new(resource)
        end
      end
    end
  end
end

Then on the controller:

module Api
  module V1
    class PagesController < BaseController
      before_action :authenticate!
      before_action :authorize!

      def update
        Api::V1::Page::Update.call(resource, payload) do |on|
          on.success { |output| return json_ok(output) }
          on.failure { |errors| return json_unprocessable_entity(errors) }
        end
      end

      private

      def resource
        @resource ||= ::Page.find_by!(slug: params[:id])
      end
    end
  end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment