Trailblazerを業務で使ってみた

B49aa473d5cd7f08cdce3d56ef837f29?s=47 kbaba1001
September 20, 2015

 Trailblazerを業務で使ってみた

Trailblazer::Tokyo #1 (http://connpass.com/event/20137/) にて発表

B49aa473d5cd7f08cdce3d56ef837f29?s=128

kbaba1001

September 20, 2015
Tweet

Transcript

  1. 6.

    APIリクエストの妥当性 Grapeの例 desc 'Return a status.' params do requires :id,

    type: Integer, desc: 'Status id.' end route_param :id do get do Status.find(params[:id]) end end
  2. 8.

    フォームオブジェクト module ShopService class Search include ActiveModel::Model attr_accessor :latitude, :longitude

    validates :latitude, presence: true, numericality: true validates :longitude, presence: true, numericality: true def initialize(params = {}) @latitude = params[:latitude] @longitude = params[:longitude] end def collections # search は scope Shop.search(latitude: latitude, longitude: longitude) end end end
  3. 9.

    コントローラ class ShopsController < ApplicationController def search service = ShopService::Search.new(params)

    if service.valid? @shops = service.collections else raise( InvalidParamatersError, service.errors.full_messages.join(', ') ) end end end
  4. 10.

    テスト describe 'ShopService::Search' do specify do shop = FactoryGirl.create(:shop, latitude:

    35.0, longitude: 138.0 ) service = ShopService::Search.new( latitude: 35.0, longitude: 138.0 ) expect(service).to be_valid expect(service.collections).to include(shop) end end
  5. 12.

    入れ子 params = { northeast: {latitude: 35.0, longitude: 138.0}, southwest:

    {latitude: 34.0, longitude: 137.0} } これを ActiveModel で扱う…?
  6. 13.

    ActiveModelで入れ子 module ShopService class Search include ActiveModel::Model attr_accessor :northeast_latitude, :northeast_longitude

    attr_accessor :southwest_latitude, :southwest_longitude with_options(presence: true, numericality: true) do |v| v.validates :northeast_latitude, :northeast_longitude v.validates :southwest_latitude, :southwest_longitude end def initialize(params = {}) @northeast_latitude = params[:northeast][:latitude] @northeast_longitude = params[:northeast][:longitude] @southwest_latitude = params[:southwest][:latitude] @southwest_longitude = params[:southwest][:longitude] end end end
  7. 15.

    Reform module ShopService class Search < Reform::Form property :northeast do

    property :latitude property :longitude validates :latitude, presence: true, numericality: true validates :longitude, presence: true, numericality: true end property :southwest do property :latitude property :longitude validates :latitude, presence: true, numericality: true validates :longitude, presence: true, numericality: true end def collections # search は scope Shop.search(northeast: northeast, southwest: southwest) end end end
  8. 16.

    Controller class ShopsController < ApplicationController def search service = ShopService::Search.new(Shop.new)

    if service.validate(params) @shops = service.collections else raise( InvalidParamatersError, service.errors.full_messages.join(', ') ) end end end
  9. 21.

    ShopService::Create module ShopService class Create < Reform::Form property :latitude property

    :longitude property :prefecture_code, virtual: true validates :latitude, presence: true, numericality: true validates :longitude, presence: true, numericality: true validates :prefecture_code, presence: true end end
  10. 22.

    Controller class ShopsController < ApplicationController def create service = ShopService::Create.new(Shop.new)

    if service.validate(params) service.save do |hash| prefecture = Prefecture.find_by(code: service.prefecture_code) service.model.create( hash.except(:prefecture_code).merge(prefecture: prefecture) ) end head :no_content else raise( InvalidParamatersError, service.errors.full_messages.join(', ') ) end end end
  11. 24.

    save を上書き module ShopService class Create < Reform::Form # property

    は省略 def save super do |params| prefecture = Prefecture.find_by(code: prefecture_code) model.create( params.except(:prefecture_code).merge(prefecture: prefecture) ) yield params if block_given? end end end end
  12. 25.

    Controller class ShopsController < ApplicationController def create service = ShopService::Create.new(Shop.new)

    if service.validate(params) service.save head :no_content else raise( InvalidParamatersError, service.errors.full_messages.join(', ') ) end end end
  13. 27.

    Service Spec describe 'ShopService::Create' do specify do shop = Shop.new

    service = ShopService::Create.new(shop) validation_result = service.validate( latitude: 35.0, longitude: 138.0, prefecture_code: 'AE01', ) expect(validation_result).to be(true) service.save shop.reload! expect(shop.latitude).to eq(35.0) expect(shop.longitude).to eq(138.0) expect(shop.prefecture.code).to eq('AE01') end end
  14. 28.

    Request Spec describe 'shop api' do describe 'POST /api/v1/shop' do

    specify do post '/api/v1/shop', { latitude: 35.0, longitude: 138.0, prefecture_code: 'AE01' } expect(response.code).to be(204) expect(Shop.count).to be(1) end specify '異常系', rambulance: true do post '/api/v1/shop' expect(response.code).to be(400) expect(response.body).to include('緯度を入力してください') expect(response.body).to include('経度を入力してください') end end end
  15. 30.

    before describe 'ShopService::Search' do specify do shop = FactoryGirl.create(:shop, latitude:

    35.0, longitude: 138.0 ) service = ShopService::Search.new( latitude: 35.0, longitude: 138.0 ) expect(service).to be_valid expect(service.collections).to include(shop) end end
  16. 31.

    after describe 'ShopService::Search' do specify do shop = ShopService::Create.new(Shop.new).validate( latitude:

    35.0, longitude: 138.0, prefecture_code: 'AE01', ).save service = ShopService::Search.new( latitude: 35.0, longitude: 138.0 ) expect(service).to be_valid expect(service.collections).to include(shop) end end
  17. 41.

    Refor -> Trailblazer class Shop < ActiveRecord::Base class Create <

    Trailblazer::Operation contract do property :latitude property :longitude property :prefecture_code, virtual: true validates :latitude, presence: true, numericality: true validates :longitude, presence: true, numericality: true validates :prefecture_code, presence: true end def process(params) @model = Shop.new validate(params, @model) do |f| prefecture = Prefecture.find_by(code: f.prefecture_code) @model.create( params.except(:prefecture_code).merge(prefecture: prefecture) ).save end end end end
  18. 42.

    Controller class ShopsController < ApplicationController include Trailblazer::Operation::Controller def create run

    Shop::Create do |op| head :no_content and return end raise( InvalidParamatersError, @model.errors.full_messages.join(', ') ) end end
  19. 43.

    Factory # before shop = ShopService::Create.new(Shop.new).validate( latitude: 35.0, longitude: 138.0,

    prefecture_code: 'AE01' ).save # after shop = Shop::Create[ latitude: 35.0, longitude: 138.0, prefecture_code: 'AE01' ].model
  20. 45.

    感想 Rails にも MVC 以外の層を作っ てもいい 小さな gem を組み合わせる導 入の柔軟さ

    テストしやすさを重視 apotonick先生は何個gem作っ てるの…? Powered by Rabbit 2.1.6 and COZMIXNG