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
endDefine 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
endor
class ApplicationController < ActionController::Base
include SimpleEndpoint::Controller
private
def default_cases
{
success: -> (result) { result.success? },
invalid: -> (result) { result.failure? }
}
end
endDefine 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
endor 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
endOperationIsNotHandled 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
endRedefining 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
endIf 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
endor 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
endRedefining 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
endor
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
endRedefining 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
endIf 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
endor redefine default_handler method
class PostsController < ApplicationController
def create
endpoint operation: Post::Create
end
private
def default_handler
{
success: -> (result, **) { head :ok }
}
end
endRedefining 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
endor
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
endDefining default params for trailblazer operation
Default #endpoint_options method implementation
def endpoint_options
{ params: params }
endRedefining endpoint_options
It will extend existing options
class PostsController < ApplicationController
endpoint_options { { params: permitted_params } }
def permitted_params
params.permit(:some, :attributes)
end
endIf 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
endOr redefine endpoint_options method
class PostsController < ApplicationController
private
def endpoint_options
{ params: permitted_params }
end
def permitted_params
params.permit(:some, :attributes)
end
endPassing 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
endBefore 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
endor
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
endCode 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
endMigration 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