Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Input object ではじめる入力値検証/input-value-validation-using-input-object

Input object ではじめる入力値検証/input-value-validation-using-input-object

Masatoshi Moritsuka

November 15, 2023
Tweet

More Decks by Masatoshi Moritsuka

Other Decks in Programming

Transcript

  1. 森塚 真年(@sanfrecce_osaka)

    2023/11/15 GENBA #1 〜RubyとRails開発の現場〜

    #GENBA
    Input object ではじめる

    入力値検証

    View full-size slide

  2. 森塚 真年
    U GitHub: @sanfrecce-osakc
    U Twitter(X): @sanfrecce_osakc
    U Qiita: @sanfrecce_osakc
    U from: 大阪府枚方2
    U 趣味: コミュニティ・勉強@
    U 好きな Ruby の機能: パターンマッ3
    U 最近の興味: orthoses-rail8
    U 所属: 株式会社エンペイ
    自己紹介

    View full-size slide

  3. 概要
    1 サービb
    1 集金業務支援サービス enpa6
    1 口座振替業務支援サービス koufuri)
    1 モバイル決済アプリ enpayウォレッe
    1 技
    1 バックエンド: Ruby / Rails / Go / HasurA
    1 フロントエンド: Flutter / React / Vue
    株式会社エンペイ
    https://www.enpay.co.jp/

    View full-size slide

  4. はじめに

    View full-size slide

  5. 最近の予定
    10月
    0 1週目: 会社のイベントで東#
    0 3週目: 大江戸Ruby会議で東#
    0 4週目: Kaigi on Railsで東京
    11月
    0 11/15: このイベントで東#
    0 11/20: 会社のイベントで東京
    12月/1月
    0 12/15 RubyConf Taiwan 202—
    0 1/13 福岡Rubyフェスタ202i
    0 1/20 BuriKaigi

    View full-size slide

  6. 毎週のように

    遠征している謎

    View full-size slide

  7. こんなこと

    ありませんか?

    View full-size slide

  8. 呼び出し側の実装ミス
    e.g. https://example.com/undefined

    View full-size slide

  9. あちこちで実装される

    クエリパラメータの

    変換ロジック
    e.g. ids.split(‘,’).map(&:to_i)

    View full-size slide

  10. Fat Controller

    & Model
    e.g. rubocop:disable Metrics/ClassLength

    View full-size slide

  11. https://speakerdeck.com/daikimiura/upgrow?slide=23
    参考資料
    – Upgrow: Railsアプリの保守性を高めるためのShopify
    のアプローチ / Upgroƒ
    – Input object を使ってリクエストパラメータを検証す
    y
    – ぼくがかんがえたさいきょうの Input object
    Input object パターン

    View full-size slide

  12. Text
    enpay での場合
    方針
    g 属性ごとに Input object 作R
    g API として想定外のもののみバリデーションの対&
    g クエリを発行するような処理は行わない
    ディレクトリ構成
    g model†
    g input†
    g boolean_input.r•
    g ids_input.r•
    g keywords_input.r•
    g type†
    g integer_array_type.r•
    g string_array_type.rb

    View full-size slide

  13. enpay での利用例

    View full-size slide

  14. Controller(action)
    class <
    def
    => :
    end
    before_action
    index_inputs [{ ids }]


    HogeController ApplicationController

    :validate_params


    index

    # ...

    View full-size slide

  15. Controller(private methods)
    private


    def
    unless &
    end


    def
    end


    def
    ||= :: new :
    end
    head , index_inputs.all?( )

    [ids_input]

    . (ids params[ ])

    validate_params

    index_inputs

    ids_input

    :bad_request :valid?
    @ids_input :ids
    Inputs IdsInput

    View full-size slide

  16. Input object
    module
    class
    include
    :: new :
    def **
    super **
    end


    def
    unless &
    end


    def
    :
    end

    end

    end
    attribute , . , default { }


    validate
    ( args)

    ( args.compact_blank)

    errors.add( , ) ids.all?( )

    ( )

    { ids }

    Inputs

    IdsInput

    Types IntegerArrayType ->
    Input


    :ids
    :should_be_positive_all


    :base :positive?
    ''
    ' '
    initialize
    should_be_positive_all

    deconstruct_keys
    有効なIDを指定してください
    _

    View full-size slide

  17. Input object(Concern)
    module
    extend ::
    do

    include ::
    include ::
    end

    end


    included
    Input

    ActiveSupport
    ActiveModel
    ActiveModel
    Concern

    Model

    Attributes

    View full-size slide

  18. カスタムタイプ
    module
    class < :: ::
    &
    end

    end

    end
    cast_value(value)

    value.split( ).map( )

    Types

    IntegerArrayType ActiveModel Type Value

    def
    [[:blank:] :to_i
    /, ]*/

    View full-size slide

  19. しばらく運用してみて
    h 更新系APIでも利用され始めg
    h 導入当初の対象は 参照系API のみだっg
    h private で大量に生える xxx_input メソッ
    h 複数のアクションで利用され始めg
    h アクションごとの分岐が増えてきた
    その後
    action_name

    private


    def
    case
    in then
    in then
    end


    def
    end


    def
    end


    def
    end


    validate_params

    xxx_input

    yyy_input

    zzz_input

    ' '
    ' '
    index
    create
    # ...

    # ...

    # ...

    View full-size slide

  20. 方針変更(予定)
    方針
    ˜ 属性ごとに Input object 作ƒ
    ˜ => Controller の action ごとに Input object 作T
    ˜ => 共通のインターフェースを提8
    ˜ API として想定外のもののみバリデーションの対f
    ˜ クエリを発行するような処理は行わな
    ˜ generator の提供(未実装)
    ディレクトリ構成
    ˜ model{
    ˜ input{
    ˜ controller{
    ˜ xxx_controllev
    ˜ index_input.r„
    ˜ create_input.r„
    ˜ xxx_input.r„
    ˜ type{
    ˜ xxx_type.rb

    View full-size slide

  21. 変更後の実装

    View full-size slide

  22. 共通インターフェース
    module
    def
    #{ }
    end


    def
    #{ } #{ }
    end

    end
    send( .to_sym)

    .constantize

    Inputs

    input

    input_class

    " "
    " "
    action_name _input
    ::Inputs::Controllers:: .class :: action_name.classify Input
    self

    View full-size slide

  23. 基底Controller
    class <
    include
    end
    ApplicationController ::ActionController::API

    Inputs

    View full-size slide

  24. Controller(action)
    class <
    def
    => :
    end


    def
    => :
    end
    before_action
    input { ids }


    input { hoges }

    HogeController ApplicationController

    :validate_params


    index

    create

    # ...

    View full-size slide

  25. Controller(private methods)
    private


    def
    unless
    end


    def
    ||= new
    end


    def
    end
    head , input.valid?

    input_class. (create_params)

    validate_params

    create_input

    create_params

    :bad_request
    @create_input
    # ...


    # Strong Parameter の処理

    View full-size slide

  26. Input object
    module
    module
    module
    class
    include
    :: :: new

    :: :: new :: :: : :
    : in
    :
    def **
    super **
    end


    def
    : : :
    end

    end

    end

    end

    end
    attribute ,
    attribute , .
    attribute , . ( , array ), default { [] }


    validates , inclusion { : }

    validates , presence
    ( args)

    ( args.compact_blank)

    ( )

    { name , status , hoges }

    Inputs

    Controllers

    HogeController

    CreateInput

    Types SymbolType
    Types InputType Inputs ->
    Input


    :name :string

    :status
    :fugas FugaInput true
    :status
    :fugas true


    %[ ]
    todo doing done
    initialize
    deconstruct_keys _

    View full-size slide

  27. ご清聴

    ありがとうございました

    View full-size slide