36
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

simple_form の f.input が呼ばれた時何が起こっているかコードリーディングして調べてみた

Last updated at Posted at 2018-12-09

はじめに

現在、社内の有志で気になる gem をランチを食べながらコードリーディングする、ということを週1回のペースで続けています。

その中で、Rails で form を簡単に実装できる gem である simple_form において、f.input でどんな処理がされているかコードリーディングした記録をまとめようと思います。

前提

想定読者

Rails で simple_form を使ったことがある

simple_form のバージョン

現時点で最新の simple_form v4.1.0 を対象に読みます。

メソッドの記号表記について

コードを読み進めるにあたり、メソッドの記号表記は、ruby 2.5.0 ドキュメントのヘルプ を参考にします。

記号 意味
.# モジュール関数 Kernel.#require
# インスタンスメソッド String#size
. クラスメソッド Dir.chdir

読むテーマについて

今回は、以下サンプルコードにおいて、simple_form_for メソッド内の f.input でどんな処理をしているのかを追っていきます。

= simple_form_for @user do |f|
  = f.input :username
  = f.input :password
  = f.button :submit

おさらい:simple_form の The Wrappers API について

実際にコードリーディングを始める前に、今回のコードリーディングに関わる前提知識として simple_form の The Wrappers API についておさらいしておきます。

simple_form の The Wrappers API では、フォームのコンポーネントをどのように render するかを設定、カスタマイズすることができます。

例えば simple_form で以下のような簡単なフォームを作った時を考えます。

= simple_form_for @user do |f|
  = f.input :username
  = f.input :password
  = f.button :submit

上記において、f.inputf.button といった要素をフォームのコンポーネントと呼びます。

こちらが render されると、若干省いているところもありますが、ざっくり以下のような html が生成されます。

<form novalidate="novalidate" class="simple_form new_user" id="new_user" action="/users/sign_in" accept-charset="UTF-8" method="post">
  <input name="utf8" type="hidden" value="✓">
  <input type="hidden" name="authenticity_token" value="xxx">
  <div class="input email optional user_email">
    <label class="email optional" for="user_email">メールアドレス</label>
    <input class="string email optional" type="email" value="" name="user[email]" id="user_email">
  </div>
  <div class="input password optional user_password">
    <label class="password optional" for="user_password">パスワード</label>
    <input class="password optional" type="password" name="user[password]" id="user_password">
  </div>
  <input type="submit" name="commit" class="btn">
</form>

それぞれのコンポーネントにおいて、単純な input タグだけではなく、div タグで囲われていたり、label タグが追加されていたりと、簡単な記述で様々な補完を加えた上で生成してくれているのがわかると思います。

このそれぞれのコンポーネントにおいて、どのような形で html タグを生成するかを指定するのが、simple_form の The Wrappers API です。

The Wrappers APIは、config.wrappers を使うことで設定できます。

simple_form をインストールする時に、 rails generate simple_form:install と実行すると思いますが、その際 config/initializers/simple_form.rb という初期設定ファイルが生成されますよね。

以下に生成されるファイルをコメントを省いて記載します。

SimpleForm.setup do |config|
  config.wrappers :default, class: :input,
    hint_class: :field_with_hint, error_class: :field_with_errors do |b|

    b.use :html5
    b.use :placeholder
    b.optional :maxlength
    b.optional :pattern
    b.optional :min_max
    b.optional :readonly

    b.use :label_input
    b.use :hint,  wrap_with: { tag: :span, class: :hint }
    b.use :error, wrap_with: { tag: :span, class: :error }
  end

  config.default_wrapper = :default
  # 以下略
end

こちらに記載されている config.wrappers ブロック内の設定をいじることで、生成される html をカスタマイズすることができます。

詳細な利用方法は README に書いてありますので、ご参照ください。

STEP①: simple_form_for メソッドの定義場所を確認する

それではコードを読み進めていきます。

まずサンプルコードで定義した simple_form_for メソッドをどこで定義しているかから確認していきます。

STEP②: SimpleForm::FormBuilder の概要を確認する

ここまでで、SimpleForm::ActionViewExtensions::FormHelper.#simple_form_for メソッドで、ActionView::Helpers::FormHelper#form_forbuilder として SimpleForm::FormBuilderクラスのオブジェクトが使われていることがわかりました。

次は、SimpleForm::FormBuilder の概要をさらっていきます。

STEP③: SimpleForm::FormBuilder#input の処理を確認する

ここまでで、f.input の処理は、SimpleForm::FormBuilder#input メソッドが実行されていることがつかめました。

次は、SimpleForm::FormBuilder#input の処理内容を読んでいきます。

STEP④: SimpleForm::FormBuilder#wrapper メソッドの戻り値を確認する

ここまでで、SimpleForm::FormBuilder#input メソッドでは、SimpleForm::Inputs::TextInput などのオブジェクトを引数に、wrapper.render を実行することがわかりましたが、wrapper のオブジェクトの実体がまだはっきりしていません。

そのため次は、SimpleForm::FormBuilder#wrapper メソッドの戻り値をたどって、wrapper の実体を確認します。

STEP⑤: SimpleForm::Wrappers::Builder の処理概要を確認する

ここまでで、wrapper の実体が SimpleForm::Wrappers::Root クラスのオブジェクトであることがわかりました。

しかし、SimpleForm::Wrappers::Root のインスタンス変数に含めているSimpleForm::Wrappers::Builder の処理は何をしているのでしょうか?少し確認してみます。

STEP⑥: wrapper.render input の処理内容を確認する

ここまでで、wrapper の実体が SimpleForm::Wrappers::Root クラスのオブジェクトであり、SimpleForm::Wrappers::Root クラスのインスタンス変数 @components には、SimpleForm::Wrappers::SingleSimpleForm::Wrappers::Leafオブジェクトの配列が格納されていることがわかりました。

今まで読んできた内容を踏まえ、一旦SimpleForm::FormBuilder#input メソッドに戻り、wrapper.render input の処理を追っていきます。

STEP⑦: component.render の処理内容を確認する

ここまでで、wrapper.render input が実行されると、整形された html が返ってくるというところまでわかりました。(ようやくhtml変換のところまで来ましたね。。)

あとモヤッとしているところが component.render の処理内容だったので、こちらを確認していきます。

まとめ

ここまで非常に長くなりましたが、simple_form_for メソッド内の f.input メソッドが実行されるまでの処理をまとめました。

  • SimpleForm::ActionViewExtensions::FormHelper.#simple_form_for メソッドで、ActionView::Helpers::FormHelper#form_forbuilder として SimpleForm::FormBuilderクラスのオブジェクトが使われている
  • f.input の処理は、SimpleForm::FormBuilder#input メソッドが実行されている
  • SimpleForm::FormBuilder#input メソッドでは、SimpleForm::Inputs::TextInput クラスなどのオブジェクトを引数に、wrapper.render を実行する
  • wrapper オブジェクトの実体は SimpleForm::Wrappers::Root クラスのオブジェクト
  • SimpleForm::Wrappers::Root クラスのインスタンス変数 @components には、SimpleForm::Wrappers::SingleSimpleForm::Wrappers::Leafオブジェクトの配列が格納されている
  • wrapper.render input が実行されると、整形された html が返ってくる
  • wrapper.render 内の処理 component.render では、 config.wrappers で定義されたオプションを一つ一つ処理し、html として生成している

終わりに

今回は f.input の処理だけをコードで追ってみましたが、それだけでも simple_form のコードを全体的に読むことになり、良い経験になりました。

一気に読み進めたので拙い部分もあると思いますが、もし間違いなどあればコメントいただけるとありがたいです。

36
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
36
24

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?