Railsコードリーデイング④ route.rbのgetメソッドを深掘りしてみた　【備忘録】

# はじめに
MVCにおいてどのように内部処理が行われページ遷移に至るのかフレームワークを使用していると何も気にせずとも開発できてしまいます。
しかしこの基本的な動作の仕組みを理解することで、エンジニアとしての基本的な思考を理解できるのではと考えました。
今回は最も基本的なgetメソッドを見ていきます。

https://qiita.com/engineer_ikuzou/items/ef70de5de070b1094ab6
こちらの記事の続きです。

# matchメソッド

```rails/actionpack/lib/action_dispatch/routing/mapper.rb
module Resources
        # Matches a URL pattern to one or more routes.
        # For more information, see match[rdoc-ref:Base#match].
        #
        #   match 'path' => 'controller#action', via: :patch
        #   match 'path', to: 'controller#action', via: :post
        #   match 'path', 'otherpath', on: :member, via: :get
        def match(path, *rest, &block)
          if rest.empty? && Hash === path
            options  = path
            path, to = options.find { |name, _value| name.is_a?(String) }

            raise ArgumentError, "Route path not specified" if path.nil?

            case to
            when Symbol
              options[:action] = to
            when String
              if /#/.match?(to)
                options[:to] = to
              else
                options[:controller] = to
              end
            else
              options[:to] = to
            end

            options.delete(path)
            paths = [path]
          else
            options = rest.pop || {}
            paths = [path] + rest
          end

          if options.key?(:defaults)
            defaults(options.delete(:defaults)) { map_match(paths, options, &block) }
          else
            map_match(paths, options, &block)
          end
        end
```

## pathのnilチェック

```rails/actionpack/lib/action_dispatch/routing/mapper.rb
raise ArgumentError, "Route path not specified" if path.nil?
```
###　学び① 例外処理 raise
throwと同じ役割のようです。
`raise SomeException, 'message'`
例外クラスとメッセージを引数として渡すことができます。

## 変数toの型に応じてoptionsハッシュに代入

```rails/actionpack/lib/action_dispatch/routing/mapper.rb
            case to
            when Symbol
              options[:action] = to
            when String
              if /#/.match?(to)
                options[:to] = to
              else
                options[:controller] = to
              end
            else
              options[:to] = to
            end

            options.delete(path)
            paths = [path]
```
変数`to`は、`"users#index"`つまりStringなので、`when String`に入ります。
`コントローラ#アクション`で書かれるため、#がない場合は、controllerが書かれているというように処理します。

そして、
`{"users"=>"users#index", :via=>:get, :to=>"users#index"}`
から`path`(users)を削除。
`{:via=>:get, :to=>"users#index"}`

最後にpathsにpathを配列で代入。

### 学び② case whenは型の比較にも適用できる。

### 学び③ matchメソッド
`/#/.match?(to)`で、`to`に`#`が含まれているかどうか判定しています。
https://docs.ruby-lang.org/ja/latest/method/String/i/match.html
https://docs.ruby-lang.org/ja/latest/method/Regexp/i/match=3f.html


## デフォルト値のチェック
```rails/actionpack/lib/action_dispatch/routing/mapper.rb
          if options.key?(:defaults)
            defaults(options.delete(:defaults)) { map_match(paths, options, &block) }
          else
            map_match(paths, options, &block)
          end
```
route.rbで
`get 'photos/:id', to: 'photos#show', defaults: { format: 'jpg' }`
のようにデフォルト値を設定している場合は、trueの方に入ります。
今回はデフォルト値の設定なしなので、elseです。

### 学び④ key?メソッド
`options.key?(:defaults)`optionsが:defaultsというkeyを持つかどうかの判定です。
https://docs.ruby-lang.org/ja/latest/method/Hash/i/has_key=3f.html

## 処理本体
```rails/actionpack/lib/action_dispatch/routing/mapper.rb
          def map_match(paths, options)
            if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
              raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
            end

            if @scope[:to]
              options[:to] ||= @scope[:to]
            end

            if @scope[:controller] && @scope[:action]
              options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
            end

            controller = options.delete(:controller) || @scope[:controller]
            option_path = options.delete :path
            to = options.delete :to
            via = Mapping.check_via Array(options.delete(:via) {
              @scope[:via]
            })
            formatted = options.delete(:format) { @scope[:format] }
            anchor = options.delete(:anchor) { true }
            options_constraints = options.delete(:constraints) || {}

            path_types = paths.group_by(&:class)
            (path_types[String] || []).each do |_path|
              route_options = options.dup
              if _path && option_path
                raise ArgumentError, "Ambiguous route definition. Both :path and the route path were specified as strings."
              end
              to = get_to_from_path(_path, to, route_options[:action])
              decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints)
            end

            (path_types[Symbol] || []).each do |action|
              route_options = options.dup
              decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints)
            end

            self
          end
```
ついに前処理が終わり、ルーティングの本体の処理が見えました。
一旦、最終的に残ったものが何か見てみます。

```terminal
[11] pry(#<ActionDispatch::Routing::Mapper>)> self
=> #<ActionDispatch::Routing::Mapper:0x00007faf9452b930
 @concerns={},
 @draw_paths=[#<Pathname:/Users/username/codereading/CodeReading/config/routes>],
 @scope=
  #<ActionDispatch::Routing::Mapper::Scope:0x00007faf9452b8e0
   @hash={:path_names=>{:new=>"new", :edit=>"edit"}},
   @parent=#<ActionDispatch::Routing::Mapper::Scope:0x00007faf9f10bd90 @hash=nil, @parent=nil, @scope_level=nil>,
   @scope_level=nil>,
 @set=#<ActionDispatch::Routing::RouteSet:0x00007faf9e98ada0>>
```

`ActionDispatch::Routing::Mapper`にインスタンス変数として、ルーティング情報を記録しているということでしょうか。
こちらのモジュールを継承することによってルーティング情報を取得できるものと思われます。

# 最後に
本日はここまでです。処理の詳細は引き続き読んでみようと思いますが、ルーティングの情報はインスタンス変数に保存されるということでした。考えたら当たり前な気もしますが、謎に包まれていたものが少し見えてきたため、嬉しいです。