link_toのソースコード読解

ネットでオープンソースを読解することで良い勉強になるということを見かけたので、Railsでも代表的なメソッドであるlink_toのソースコードを読解してみようとおもいます。そして最後のほうに、具体例をあげてコードを追ってみようとおもいます。
事前に申し上げておきますが、今回の記事はかなり重厚になることが予想されます。また、privateメソッドが多いため読解するメソッドがコロコロ変わりますのであしからず。

それでは実際のソースコードを以下にペーストします。
```
def link_to(name = nil, options = nil, html_options = nil, &block)
  html_options, options, name = options, name, block if block_given?
  options ||= {}

  html_options = convert_options_to_data_attributes(options, html_options)

  url = url_target(name, options)
  html_options["href"] ||= url

  content_tag("a", name || url, html_options, &block)
end
```

では実際にコード読解をしていきましょう

```
def link_to(name = nil, options = nil, html_options = nil, &block)
```
引数にはname,options,html_options, &blockが指定されています。第一～三引数までは初期値としてnilが入っています。そのため、html_optionsに値が入ってこなくてもhtml_optionsはnilとして扱われるんですね。

第四引数はブロックです。以前の記事でも書きましたが、blockはdo~endで記述されるコードの塊です。
```<%= link_to ~~~ do %>```このような書き方ができるのは第四引数でブロックを指定しているからなんです。ブロックは、値ではなく一連の処理の塊です。

```
html_options, options, name = options, name, block if block_given?
```
[block_given?](https://docs.ruby-lang.org/ja/latest/method/Kernel/m/block_given=3f.html)というメソッドはrubyのメソッドです。blockがもし存在すれば、値を入れ直すんですね。
ifより前の処理は、多重代入です。[こちら](https://qiita.com/wangqijiangjun/items/20f52bb948843ce9a184)の記事を参考にしてみてください。

```
options ||= {}
```
これは自己代入というものです。左辺がnilもしくはfalseだったら指定した値を入れてくださいね、という処理です。
今回でいえば、optionsがnilもしくはfalseだったら空のハッシュを入れてくださいねというものです。
詳しく知りたい方は、[こちら](https://qiita.com/wangqijiangjun/items/09312bf25779ee3cda90)の記事を参考にしてください

```
html_options = convert_options_to_data_attributes(options, html_options)
```
なにやら、見慣れないメソッドであるconvert_options_to_data_attributesが出てきました。
このメソッドは、モジュール内のprivateメソッドです。

この処理は以下のような内容です。
```
def convert_options_to_data_attributes(options, html_options)
  if html_options
    html_options = html_options.stringify_keys
    html_options["data-remote"] = "true" if link_to_remote_options?(options) || link_to_remote_options?(html_options)

    method = html_options.delete("method")

    add_method_to_attributes!(html_options, method) if method

    html_options
  else
    link_to_remote_options?(options) ? { "data-remote" => "true" } : {}
  end
end
```
3行目
```
html_options = html_options.stringify_keys
```
html_optionsという変数に、引数のhtml_optionsにstringify_keysメソッドを適用しています。このメソッドはハッシュのキーをシンボルから文字列に変えるメソッドです。

4行目
```
html_options["data-remote"] = "true" if link_to_remote_options?(options) || link_to_remote_options?(html_options)
```
このコードを理解するには、
```
def link_to_remote_options?(options)
  if options.is_a?(Hash)
    options.delete("remote") || options.delete(:remote)
  end
end
```
引数であるoptionsがHashであるかどうかを調べ、Hashであればremoteというkeyを削除して、そのkeyに紐づいた値を返すのがlink_to_remote_options?(options)です。

4行目のコードに戻ります。
```
html_options["data-remote"] = "true" if link_to_remote_options?(options) || link_to_remote_options?(html_options)
```
引数であるoptionsがHashであるかどうかを調べ、Hashであればremoteというkeyを削除して、そのkeyに紐づいた値を返している、つまりtrueであるなら、```{data-remote: "true"}```になるということです。

5行目
```
method = html_options.delete("method")
```
:::note info
おそらく
```
method = options.delete("method")
```
の間違いであるとおもいます。以降はこちらのコードをもとに解説していきます
:::

methodという変数に、optionsに"method"というkeyがあれば、keyである"method"を削除してそれに関連するvalueを返します

6行目
```
add_method_to_attributes!(html_options, method) if method
```
methodという変数(optionsに"method"というkeyがあれば、keyである"method"を削除してそれに関連するvalueを返す)であるなら、add_method_to_attributes!が適用される。add_method_to_attributes!は以下のようなメソッドです。
```
def add_method_to_attributes!(html_options, method)
  if method_not_get_method?(method) && !html_options["rel"]&.match?(/nofollow/)
    if html_options["rel"].blank?
      html_options["rel"] = "nofollow"
    else
      html_options["rel"] = "#{html_options["rel"]} nofollow"
    end
  end
  html_options["data-method"] = method
end
```
このメソッドを理解するには、method_not_get_method?を理解しないといけません。そのメソッドが以下の通りです。
```
STRINGIFIED_COMMON_METHODS = {
  get:    "get",
  delete: "delete",
  patch:  "patch",
  post:   "post",
  put:    "put",
}.freeze

def method_not_get_method?(method)
  return false unless method
  (STRINGIFIED_COMMON_METHODS[method] || method.to_s.downcase) != "get"
end
```
このメソッドを読解していきます。
methodという変数(optionsに"method"というkeyがあれば、keyである"method"を削除してそれに関連するvalueを返す)が偽の値だったらfalseを返す。

```STRINGIFIED_COMMON_METHODS[method]```は、keyである```method```に対応したvalueを返します。
そしてmethodという変数を文字列化しそれを小文字化する。これらがgetを含んでいない値を返す。それがmethod_not_get_method?メソッドです。
add_method_to_attributes!メソッドに戻ります。

```
def add_method_to_attributes!(html_options, method)
  if method_not_get_method?(method) && !html_options["rel"]&.match?(/nofollow/)
    if html_options["rel"].blank?
      html_options["rel"] = "nofollow"
    else
      html_options["rel"] = "#{html_options["rel"]} nofollow"
    end
  end
  html_options["data-method"] = method
end
```
2行目から見ていきます。
```
if method_not_get_method?(method) && !html_options["rel"]&.match?(/nofollow/)
```
先ほど見たように```method_not_get_method?(method)```は、getを含んでいない値を返すメソッドです。
```!html_options["rel"]&.match?(/nofollow/)```ではhtml_optionsの中のrelというkeyに対応するvalueがあり、nofollowという文字列を含んでいればtrueを返し、なければnilを返します。ぼっち演算子が使われていますからね。
そして文頭で「!」があるのでhtml_optionsにrelとnofollowという文字列がなければtrueを返し、あればnilもしくはfalseを返します。
つまり、このコードでは「methodにgetがなくて、かつhtml_optionsにrelに対応するvalueとnofollowという文字列がなければ」という条件文です。

```
    if html_options["rel"].blank?
      html_options["rel"] = "nofollow"
    else
      html_options["rel"] = "#{html_options["rel"]} nofollow"
    end
  end
  html_options["data-method"] = method
end
```
html_optionsのrelというkeyに対応したvalueが空だったら、keyに対応したvalueにnofollowを格納する
html_optionsのrelというkeyに対応したvalueが空じゃなかったら、keyに対応したvalueとnofollowという文字列を付け加えます。

methodがgetで、かつhtml_optionsにrelに対応するvalueとnofollowという文字列があれば、
html_optionsのdata-methodにgetメソッドが適用されます。
nofollowを指定することで、指定されたリンクをgoogle検索エンジンがクローリングできないよう明示的に示すためのものがnofollow属性です。

## convert_options_to_data_attributesのコードへ戻ってきました
```
def convert_options_to_data_attributes(options, html_options)
  if html_options
    html_options = html_options.stringify_keys
    html_options["data-remote"] = "true" if link_to_remote_options?(options) || link_to_remote_options?(html_options)

    method = html_options.delete("method")

    add_method_to_attributes!(html_options, method) if method

    html_options
  else
    link_to_remote_options?(options) ? { "data-remote" => "true" } : {}
  end
end
```
このコードでは6行目のコードでは、これまで述べてきたようにhtml_optionsが処理されて、その処理を返します。

最後に理解しなければいけないプライベートメソッドであるurl_targetメソッドがあります。
そのコードがこちらです。
```
def url_target(name, options)
  if name.respond_to?(:model_name) && options.empty?
    url_for(name)
  else
    url_for(options)
  end
end
```
引数であるnameが指定したメソッドを呼び出せて、optionsが空であるなら、nameをもとにしたURLが作成される。
引数であるnameが指定したメソッドを呼び出せないのなら、optionsをもとにしたURLが作成される。

## link_toの読解へ
```
def link_to(name = nil, options = nil, html_options = nil, &block)
  html_options, options, name = options, name, block if block_given?
  options ||= {}

  html_options = convert_options_to_data_attributes(options, html_options)

  url = url_target(name, options)
  html_options["href"] ||= url

  content_tag("a", name || url, html_options, &block)
end
```
5行目urlという変数にurl_targetで作成されたURLが格納されます。
そのurlという変数は、html_options["href"]のvalueが空であったら、そのURLが代入されます。

content_tagメソッドを適用して、HTML形式のリンクが作成されています。

次の章でまとめます。
## link_toのソースコードのまとめ
再掲

```
def link_to(name = nil, options = nil, html_options = nil, &block)
  html_options, options, name = options, name, block if block_given?
  options ||= {}

  html_options = convert_options_to_data_attributes(options, html_options)

  url = url_target(name, options)
  html_options["href"] ||= url

  content_tag("a", name || url, html_options, &block)
end
```
2行目でブロックがあれば引数に値を代入し直す。
3行目では、optionsの値がnilがfalseであればハッシュを変数に入れる
4行目では、html_optionsが条件によって処理される
5行目では、引数nameと引数optionsのどちらかに合致したURLが生成される
最終行では、content_tagメソッドを使って、タグを動的に生成してくれています。

以上です。
長かったですし、あっちこっち飛んでしまったので理解しにくかったかもしれません。
でも、あらゆる知識をしれたり、link_toの内部の挙動が知れて楽しかったのでこれからも続けていきたいと思います。
何か間違いがございましたら、ご教示いただけますと幸いです。