Skip to content

Instantly share code, notes, and snippets.

@fernandes
Last active July 10, 2017 13:31
Show Gist options
  • Save fernandes/f379b8a6eae347eb6be1becf0677d469 to your computer and use it in GitHub Desktop.
Save fernandes/f379b8a6eae347eb6be1becf0677d469 to your computer and use it in GitHub Desktop.
require 'trailblazer/endpoint'
require 'trailblazer/endpoint/app_matcher'
class Api::Controller < ApplicationController
protect_from_forgery with: :null_session
protected
def default_handler
->(m) do
m.present { |result| render json: result["representer.render.class"].new(result["model"]).to_json }
m.created { |result| render json: result["representer.render.class"].new(result["model"]).to_json, status: :created }
m.success { |result| render json: result["representer.render.class"].new(result["model"]).to_json }
m.invalid { |result| render json: result["representer.errors.class"].new(result["result.contract.default"].errors.messages).to_json, status: :unprocessable_entity }
m.not_found { |result| render json: result["representer.render.class"].new(result["model"]).to_json }
m.unauthenticated { |result| render json: result["representer.render.class"].new(result["model"]).to_json }
end
end
def endpoint(operation_class, options={}, &block)
options[:args] ||= [params, {}]
options[:args][1]['document'] = request.body.read
App::Endpoint.(operation_class, default_handler, *options[:args], &block)
end
end
class Api::V1::Thing::Create < Trailblazer::Operation
extend Contract::DSL
extend Representer::DSL
contract do
include Representable::JSON
defaults render_nil: true
property :id, writeable: false
property :name
validates :name, presence: true
end
# if wanna use specific representer
# representer do
#
# end
# or inherits representer from contract
representer :render, Representer.infer(self["contract.default.class"], format: Representable::JSON)
representer :errors, ::ErrorsRepresenter
step Model( Thing, :new )
step Contract::Build()
step Contract::Validate( representer: Representer.infer(self["contract.default.class"], format: Representable::JSON))
step Contract::Persist()
end
class Api::V1::Thing::Index < Trailblazer::Operation
extend Representer::DSL
representer :render do
include Representable::JSON::Collection
items class: Thing do
property :id, writeable: false
property :name
end
end
step :model!
def model!(options, params:, **)
options["model"] = Thing.all
end
end
require_dependency Rails.root.join('app/concepts/api/v1/thing/operation/create')
class Api::V1::Thing::Show < Trailblazer::Operation
extend Representer::DSL
step Model( Thing, :find_by )
# inherits representer from Create operation
# on generator the idea is create own representer here, without any dep on Create op
representer :render, Api::V1::Thing::Create['representer.render.class']
end
class Api::V1::ThingsController < Api::Controller
def index
endpoint Api::V1::Thing::Index
end
def create
endpoint Api::V1::Thing::Create
end
def show
endpoint Api::V1::Thing::Show
end
end
# /lib/trailblazer/endpoint/app_matcher.rb
module App
class Endpoint < Trailblazer::Endpoint
# this is totally WIP as we need to find best practices.
# also, i want this to be easily extendable.
Matcher = Dry::Matcher.new(
present: Dry::Matcher::Case.new( # DISCUSS: the "present" flag needs some discussion.
match: ->(result) { result.success? && result["present"] },
resolve: ->(result) { result }),
success: Dry::Matcher::Case.new(
match: ->(result) { result.success? },
resolve: ->(result) { result }),
created: Dry::Matcher::Case.new(
match: ->(result) { result.success? && result["model.action"] == :new }, # the "model.action" doesn't mean you need Model.
resolve: ->(result) { result }),
not_found: Dry::Matcher::Case.new(
match: ->(result) { result.failure? && result["result.model"] && result["result.model"].failure? },
resolve: ->(result) { result }),
# TODO: we could add unauthorized here.
unauthenticated: Dry::Matcher::Case.new(
match: ->(result) { result.failure? }, # FIXME: we might need a &. here ;)
resolve: ->(result) { result }),
invalid: Dry::Matcher::Case.new(
match: ->(result) { result.failure? && result["result.contract.default"] && result["result.contract.default"].failure? },
resolve: ->(result) { result })
)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment