0.01
The project is in a healthy, maintained state
Dry-matcher free implementation of trailblazer endpoint, with ability to redefine matchers and handlers behavior for separate controllers or actions.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
 Dependencies

Development

>= 2.1.0
~> 3.0
~> 13.0
~> 1.25.1
 Project Readme

SimpleEndpoint

<differencialx> Gem Version

Dry-matcher free implementation of trailblazer endpoint.

Installation

Add this to your Gemfile:

gem 'simple_endpoint', '~> 1.0.2'

Getting Started

Include simple endpoint to your base controller

class ApplicationController < ActionController::Base
  include SimpleEndpoint::Controller
end

Define default_cases method to specify trailblazer operation result handling

class ApplicationController < ActionController::Base
  include SimpleEndpoint::Controller

  private

  def default_cases
    {
      success: -> (result) { result.success? },
      invalid: -> (result) { result.failure? }
    }
  end
end

Define default_handler method to specify how to handle each case

class ApplicationController < ActionController::Base
  include SimpleEndpoint::Controller

  private

  def default_handler
    {
      success: -> (result, **opts) { render json: result['model'], **opts, status: 200 },
      invalid: -> (result, **) { render json: result['contract.default'].errors, serializer: ErrorSerializer, status: :unprocessable_entity }
    }
  end
end

OperationIsNotHandled error will be raised if #default_cases doesn't contain case for specific operation result.

UnhadledResultError will be raised if #default_handler doesn't contain for some cases.

NotImplementedError will be raised if default_cases or default_handler methods aren't defined.

#endpoint method

Now you are able to use endpoint method at other controllers

#endpoint method has next signature:

Key Required Default value Description
:operation yes - Traiblazer operation class
:different_cases no {} Cases that should be redefined for exact #endpoint call
:different_handler no {} Case of handler that should be handled in different way
:options no {} Additional hash which will be merged to #ednpoint_options method result before operation execution
:before_response no {} Allow to process code before specific case handler
:renderer_options no {} Allow to pass serializer options from controller and Will available inside handler as second parameter.

Simple endpoint call

class PostsController < ApplicationController
  def create
    endpoint operation: Post::Create
  end
end

Redefining cases for specific controller

If you need to redefine operation result handling for specific controller you can do next

class PostsController < ApplicationController
  def create
    endpoint operation: Post::Create
  end

  private

  def default_cases
    {
      success: -> (result) { result.success? && is_it_raining? },
      invalid: -> (result) { result.failure? && is_vasya_in_the_house? }
      ... # other cases
    }
  end

  def is_it_raining?
    WeatherForecast.for_today.raining?
  end

  def is_vasya_in_the_house?
    User.find_by(login: 'vasya').signed_in?
  end
end

Note that it'll override ApplicationController#default_cases

Redefining cases for specific controller action

Code below will redefine only success operation handling logic of #default_cases method, it doesn't matter where #default_cases was defined, at ApplicationController or PostsController

class PostsController < ApplicationController
  def create
    endpoint operation:       Post::Create,
             different_cases: different_cases
  end

  private

  def different_cases
    {
      success: -> (result) { result.success? && is_vasya_in_the_house? }
    }
  end

  def is_vasya_in_the_house?
    User.find_by(login: 'vasya').signed_in?
  end
end

Redefining handler for specific controller

If you need to redefine handler logic, simply redefine #default_handler method

class PostsController < ApplicationController
  def create
    endpoint operation: Post::Create
  end

  private

  def default_handler
    {
      success: -> (result, **) { head :ok }
    }
  end
end

Redefining handler for specific controller action

class PostsController < ApplicationController
  def create
    endpoint operation: Post::Create,
             different_handler: different_handler
  end

  private

  def different_handler
    {
      success: -> (result, **) { render json: { message: 'Nice!' }, status: :created }
    }
  end
end

Defining default params for trailblazer operation

Default #endpoint_options method implementation

  def endpoint_options
    { params: params }
  end

Redefining endpoint_options

class PostsController < ApplicationController

  private

  def endpoint_options
    { params: permitted_params }
  end

  def permitted_params
    params.permit(:some, :attributes)
  end
end

Passing additional params to operation

options will be merged with #endpoint_options method result and trailblazer operation will be executed with such params: Post::Create.(params: params, current_user: current_user)

class PostsController < ApplicationController
  def create
    endpoint operation: Post::Create,
             options: { current_user: current_user }
  end
end

Before handler actions

You can do some actions before #default_handler execution

class PostsController < ApplicationController
  def create
    endpoint operation: Post::Create,
             before_response: before_render_actions
    end
  end

  private

  def before_response_actions
    {
      success: -> (result, **) { response.headers['Some-header'] = result[:some_data] }
    }
  end
end

Code above will put data from operation result into response haeders before render

Pass additional options from controller

class PostsController < ApplicationController
  def create
    endpoint operation: Post::Create,
             renderer_options: { serializer: SerializerClass }
    end
  end

  private

  def default_handler
    {
      # renderer_options will be available as **opts
      success: -> (result, **opts) { render json: result['model'], **opts, status: 200 },
      invalid: -> (result, **) { render json: result['contract.default'].errors, serializer: ErrorSerializer, status: :unprocessable_entity }
    }
  end
end