/hoge/1/fuga/2
にGETでアクセスしたときに下記のロジックを実行させたいときにどうするかちょっと悩んだので考えたこととかメモしておく。
router.get '/hoge/:hoge_id/fuga/:fuga_id' do request.params['id'] end
今までは完全一致でパスの検証をしていたので、idみたいな動的に変更されるURLだとid単位にroutingを用意しないと行けない感じでした😭
動的項目を表すプレフィックスをどうするか
今回はsinatraとrailsに倣って:
を動的項目を表すプレフィックスにしました。
get '/hello/:name' do # "GET /hello/foo" と "GET /hello/bar" にマッチ # params['name'] は 'foo' か 'bar' "Hello #{params['name']}!" end
http://sinatrarb.com/intro-ja.html
get '/patients/:id', to: 'patients#show'
URLの中の動的項目をどう判定するか
今回は/
でsplitして動的項目だけリクエストのときにきたURLに書き換えて一致しているか判断するような方針をとりました。
例えば/hoge/1/fuga/2
と/hoge/:hoge_id/fuga/:fuga_id
をチェックするさいにhoge, 1, fuga, 2
とhoge, :hoge_id, fuga, :fuga_id
で動的項目の値を書き換えて一致していることを確認するようにしました。(これでいけるはずと思ってますけど、なんか漏れとかありそう・・・)
def dynamic_indx @dynamic_indx ||= path.split('/').map.with_index do |val, i| i if val.include?(DYNAMIC_PREFIX) end.compact end attr_reader :path, :process, :method def path_match?(path) request_path, route_path = [path, self.path].map(&->(array) { array.split('/') }) request_path.each.with_index do |val, i| route_path[i] = val if dynamic_indx.include?(i) end route_path == request_path end
※今は出来てないけど、この辺ちょっと動的項目の一意性を担保(hoge/:hoge_id/fuga/:fuga_id
はNG)するのと、2回連続で動的項目を設定不可(hoge/:hoge_id/:fuga_id
)とかはチェックするようにしたほうが良いかもですね・・・!
ちなみにsinatraだとMustermann
という正規表現とプレフィックスを元に一致チェックと行えるやつを使ってるみたいですね👀
if '/foo/bar' =~ Mustermann.new('/foo/*') puts 'it works!' end case 'something.png' when Mustermann.new('foo/*') then puts "prefixed with foo" when Mustermann.new('*.pdf') then puts "it's a PDF" when Mustermann.new('*.png') then puts "it's an image" end pattern = Mustermann.new('/:prefix/*.*') pattern.params('/a/b.c') # => { "prefix" => "a", splat => ["b", "c"] }
railsはどうやってるのかは見てないですが、後で時間があるときに見てみようかと思います。
動的項目をrequestのparamとして受け取る
これはrouteにurl_argsというインスタンス変数を追加して、{ 動的項目 => 値 }
の要はhashを設定するようにしました。
そしてそれをrequest
のparams
にmergeするようにしました。
module Makanai class Application def execute_route route = router.bind!(url: request.url, method: request.method) # NOTE: merge dynamic url params (ex. /resources/1 -> { 'id' => '1' }) @request.params.merge!(route.url_args) route.process.call(request) module Makanai class Router def build_url_args(path:) request_path, route_path = [path, self.path].map(&->(array) { array.split('/') }) dynamic_indx.each do |i| @url_args.merge!({ route_path[i].gsub(DYNAMIC_PREFIX, '') => request_path[i] }) end end
でもrequestのオブジェクトをrouteのインスタンス変数で上書きするのは、ちょっとイマイチな気もしていたりするけど、railsとかsinatraに合わせておいたほうがいいかなと。。。
おわりに
今回はオレオレWebフレームワークを動的URLに対応させるときに個人的に考えていたことをメモしました。 DSL周りの考え方とか、paramsのような跨って使うようなobjectをどこで修正してどうやって処理に引き継がせるかとか、判断基準とか難しいけど、勉強になりますね💪
オレオレフレームワークの全体像が気になる方はこちらからコードをすべて読むことが出来ます。