パラメーター

パラメータはRack envから取得され、引数として渡され#callます。それらはRubyハッシュに似ていますが、拡張された機能セットを提供します。

 

ソース

Paramsの出身:

  • ルータ変数(例:。/books/:id
  • クエリ文字列(例えば。/books?title=Hanami
  • 要求の本文(例えば、POST要求/books

 

アクセス

paramの値にアクセスするには、サブスクライバ演算子 を使用でき#[]ます。

# apps/web/controllers/dashboard/index.rb
module Web::Controllers::Dashboard
  class Index
    include Web::Action

    def call(params)
      self.body = "Query string: #{ params[:q] }"
    end
  end
end

私たちが訪れるなら/dashboard?q=foo、私たちは見るべきQuery string: fooです。

 

シンボルへのアクセス

パラメタとネストされたパラメータは、シンボルを介してのみ参照できます。

params[:q]
params[:book][:title]

さて、:bookリクエストにパラメータがない場合はどうなりますか?のでparams[:book]ありnil、我々はアクセスできません:title。この場合、Rubyはaを生成しNoMethodErrorます。

私たちには、私たちの問題に対する安全な解決策があります:#get。シンボルのリストを受け取ります。各シンボルは、ネストされた構造内のレベルを表します。

params.get(:book, :title)             # => "Hanami"
params.get(:unknown, :nested, :param) # => nil instead of NoMethodError

 

ホワイトリスト登録

ホワイトリスト作成の仕組みを示すために、新しいアクションを作成しましょう:

bundle exec hanami generate action web signup#create

私たちはユーザーに自己登録を提供したいと考えています。ペイロードを受け取り、それをusersテーブルに格納するアクションにポストするHTMLフォームを作成します。このテーブルには、adminユーザーに管理権限があるかどうかを示すブール値の列があります。

悪意のあるユーザーは、この特別なパラメータをアプリケーションに送信することでこのシナリオを悪用し、管理者自身を管理者にすることができます。

アプリケーション内で許可されている許可されたパラメータをフィルタリングすることで、この問題を簡単に修正できます。常にそれは覚えておいてください信頼できない入力を表しparamsは

我々は.params(ネストされた)パラメータの構造をマップするのに使います。

# apps/web/controllers/signup/create.rb
module Web::Controllers::Signup
  class Create
    include Web::Action

    params do
      required(:email).filled
      required(:password).filled

      required(:address).schema do
        required(:country).filled
      end
    end

    def call(params)
      puts params[:email]             # => "alice@example.org"
      puts params[:password]          # => "secret"
      puts params[:address][:country] # => "Italy"

      puts params[:admin]             # => nil
    end
  end
end

場合であってもadmin、要求の本体内に送られ、それはからはアクセスできませんparams

 

検証と強制

 

ユースケース

この例では(「申し込み」と呼ばれます)、password必要なパラメータを作成します。

2番目の機能、「招待状」を紹介するとします。既存のユーザーは誰かに参加を依頼できます。招待されたユーザーは後でパスワードを決定するので、Userその値を持たずにそのレコードを保持する必要があります。

検証をpassword行うとUser、条件付きでこの2つのユースケースを処理する必要があります。しかし、長期的に見ると、このアプローチはメンテナンスの観点から痛いものです。

# Example of poor style for validations
class User
  attribute :password, presence: { if: :password_required? }

  private
  def password_required?
     !invited_user? && !admin_password_reset?
  end
end

検証は、特定のユースケースに対して必要とするデータの正確性のためのルールのセットとして見ることができます。私たちのために、ワークフロー永続化されるルートに応じて、User aはパスワードの有無にかかわらず永続化することができます。User

 

境界

2番目の重要な点は、システムで無効な入力が伝播しないように検証を使用することです。MVCアーキテクチャでは、モデルレイヤーは入力から最も離れています。データベースにレコードを作成する直前にデータをチェックするのは費用がかかります。

私たちは場合は前提条件として正しいデータを考慮し、当社のワークフローを開始する前に、我々はできるだけ早く容認できない入力を停止する必要があります。

次の方法を考えてみましょう。データが無効な場合、私たちは続行したくありません。

def expensive_computation(argument)
  return if argument.nil?
  # ...
end

 

使用法

Rubyの型を強制し、paramが必要かどうかを検証し、値の範囲内にあるかどうかなどを調べることができます。

# apps/web/controllers/signup/create.rb
module Web::Controllers::Signup
  class Create
    include Web::Action
    MEGABYTE = 1024 ** 2

    params do
      required(:name).filled(:str?)
      required(:email).filled(:str?, format?: /@/).confirmation
      required(:password).filled(:str?).confirmation
      required(:terms_of_service).filled(:bool?)
      required(:age).filled(:int?, included_in?: 18..99)
      optional(:avatar).filled(size?: 1..(MEGABYTE * 3)
    end

    def call(params)
      if params.valid?
        # ...
      else
        # ...
      end
    end
  end
end

パラメータの検証は、Hanami :: Validationsに委譲されます。オプションの完全なリストと、検証の間にコードを共有する方法については、関連ドキュメントを参照してください。

 

コンクリートクラス

params DSLはとても素早く直感的ですが、視覚的にノイズが多く、ユニットテストが難しいという欠点があります。代わりに、クラスを抽出し、それを引数として渡すこともできます.params

# apps/web/controllers/signup/my_params.rb
module Web::Controllers::Signup
  class MyParams < Web::Action::Params
    MEGABYTE = 1024 ** 2

    params do
      required(:name).filled(:str?)
      required(:email).filled(:str?, format?: /@/).confirmation
      required(:password).filled(:str?).confirmation
      required(:terms_of_service).filled(:bool?)
      required(:age).filled(:int?, included_in?: 18..99)
      optional(:avatar).filled(size?: 1..(MEGABYTE * 3)
    end
  end
end
# apps/web/controllers/signup/create.rb
require_relative './my_params'

module Web::Controllers::Signup
  class Create
    include Web::Action
    params MyParams

    def call(params)
      if params.valid?
        # ...
      else
        # ...
      end
    end
  end
end

 

ボディパーサー

Rackは、フォーム提出から来ていない限り、リクエスト本体を無視します。JSONエンドポイントがある場合、ペイロードはで利用できませんparams

module Web::Controllers::Books
  class Create
    include Web::Action
    accept :json

    def call(params)
      puts params.to_h # => {}
    end
  end
end
curl http://localhost:2300/books      \
  -H "Content-Type: application/json" \
  -H "Accept: application/json"       \
  -d '{"book":{"title":"Hanami"}}'    \
  -X POST

書籍のペイロードを利用paramsできるようにするには、この機能を有効にする必要があります。

# apps/web/application.rb
module Web
  class Application < Hanami::Application
    configure do
      # ...
      body_parsers :json
    end
  end
end

今すぐparams.get(:book, :title)戻る"Hanami"