SimpleEndpoint
Dry-matcher free implementation of trailblazer endpoint.
Installation
Add this to your Gemfile:
gem 'simple_endpoint', '~> 2.0.0'
Getting Started
Include simple endpoint to your base controller
class ApplicationController < ActionController::Base
include SimpleEndpoint::Controller
end
Define cases
to specify trailblazer operation result handling.
class ApplicationController < ActionController::Base
include SimpleEndpoint::Controller
cases do
match(:success) { |result| result.success? }
match(:invalid) { |result| result.failure? }
end
end
or
class ApplicationController < ActionController::Base
include SimpleEndpoint::Controller
private
def default_cases
{
success: -> (result) { result.success? },
invalid: -> (result) { result.failure? }
}
end
end
Define handler
to specify how to handle each case
class ApplicationController < ActionController::Base
include SimpleEndpoint::Controller
handler do
on(:success) { |result, **opts| render json: result['model'], **opts, status: 200 }
on(:invalid) { |result, **| render json: result['contract.default'].errors, serializer: ErrorSerializer, status: :unprocessable_entity }
end
end
or default_handler
method
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 cases
/#default_cases
doesn't contain case for specific operation result.
UnhandledResultError
will be raised if handler
/#default_hadnler
doesn't contain for some cases.
NotImplementedError
will be raised if cases
/#default_cases
or handler
/#default_hadnler
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
It will redefine or add only these cases
class PostsController < ApplicationController
cases do
match(:success) { |result| result.success? && is_it_raining? }
match(:invalid) { |result| result.failure? && is_vasya_in_the_house? }
end
def create
endpoint operation: Post::Create
end
private
def is_it_raining?
WeatherForecast.for_today.raining?
end
def is_vasya_in_the_house?
User.find_by(login: 'vasya').signed_in?
end
end
If you want to remove parent cases use inherit
option
It will remove cases and add new ones
class PostsController < ApplicationController
cases(inherit: false) do
match(:success) { |result| result.success? && is_it_raining? }
match(:invalid) { |result| result.failure? && is_vasya_in_the_house? }
end
def create
endpoint operation: Post::Create
end
private
def is_it_raining?
WeatherForecast.for_today.raining?
end
def is_vasya_in_the_house?
User.find_by(login: 'vasya').signed_in?
end
end
or manually create default_cases
method. Note that it'll override ApplicationController#default_cases
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
Redefining cases for specific controller action
Code below will redefine only success
operation handling logic of cases
/#default_cases
, it doesn't matter where cases
/#default_cases
was defined, at ApplicationController
or PostsController
class PostsController < ApplicationController
def create
cases { on(:success) { |result| result.success? && is_vasya_in_the_house? } }
endpoint operation: Post::Create
end
private
def is_vasya_in_the_house?
User.find_by(login: 'vasya').signed_in?
end
end
or
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 handler
. It'll redefine only success
handler
class PostsController < ApplicationController
handler do
on(:success) { |result, **| head :ok }
end
def create
endpoint operation: Post::Create
end
end
If you want remove parent handler settings you can use inherit
option. It'll remove all other settings.
class PostsController < ApplicationController
handler(inherit: false) do
on(:success) { |result, **| head :ok }
end
def create
endpoint operation: Post::Create
end
end
or 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
handler { on(:success) { |result, **| render json: { message: 'Nice!' }, status: :created } }
endpoint operation: Post::Create,
different_handler: different_handler
end
end
or
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
It will extend existing options
class PostsController < ApplicationController
endpoint_options { { params: permitted_params } }
def permitted_params
params.permit(:some, :attributes)
end
end
If you want to remove previously defined options you can use inherit
option
class PostsController < ApplicationController
endpoint_options(inherit: false) { { params: permitted_params } }
def permitted_params
params.permit(:some, :attributes)
end
end
Or redefine endpoint_options
method
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
before_response do
on(:success) do |result, **|
response.headers['Some-header'] = result[:some_data]
end
end
endpoint operation: Post::Create
end
end
end
or
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
Migration from v1 to v2
⚠️⚠️⚠️
Be cautious while using v2.x.x. We executing blocks and lambdas explicitly on the instance of the class where SimpleEndpoint::Controller
module were included. That's related to handler
/#default_handler
, cases
/#default_cases
and before_response
.
If you are creating lambdas or blocks for handler and cases in different class with internal methods usage it won't work unlike the v1
⚠️⚠️⚠️
class Handler
def self.call
{
success: ->(result, **) { result.success? && some_method? }
}
end
private
def some_method?
true
end
end
class ApplicationController
def default_handler
# works with v1 but will cause an error in v2
Handler.call
end
end