railsのpath名の決まり方
結論から言え!
railsでroute.rbにresourcesを使ってrouteを生やすときにcollectもしくはmemberを使う場合、
path名にcreate new show update destroy
のどれかを使うとpathの名前が狂う
困ったこと
かなーりレアケースなのですがこの間、resourcesで定義してあるroutingに新たにリソース作成のrouteを追加したい、と言う状況になりました
使用するメソッドは普通の場合と同じでcreateなので create
と言うrouteをcollectionを使って新たに作成しました
config/routes.rb Rails.application.routes.draw do resources :users end
もともとこうなっていたものを
config/routes.rb Rails.application.routes.draw do resources :users do collection do post 'create', action: 'create' end end end
これで /users/create
にpostすると新たにuserが作成できるようになるはず
と思って rake routes
してroutingを確認するとpath名がおかしなことになっていました
、、ん?
一番上がこうなってます
Prefix Verb URI Pattern Controller#Action users POST /users/create(.:format) users#create
新たに作成した /users/create
と言うpathにusersって言うprefixがついてる、、
つまりこれは redirect_to users_path
とか書くと /users/create
をpostしにいきます、非常にまずい
原因
routingのprefixがどこで呼ばれてるのか調べてみました
rails内の name_for_action
と言うmethodでprefixは作られています
def name_for_action(as, action) prefix = prefix_name_for_action(as, action) name_prefix = @scope[:as] if parent_resource return nil unless as || action collection_name = parent_resource.collection_name member_name = parent_resource.member_name end action_name = @scope.action_name(name_prefix, prefix, collection_name, member_name) candidate = action_name.select(&:present?).join("_") unless candidate.empty? # If a name was not explicitly given, we check if it is valid # and return nil in case it isn't. Otherwise, we pass the invalid name # forward so the underlying router engine treats it and raises an exception. if as.nil? candidate unless !candidate.match?(/\A[_a-z]/i) || has_named_route?(candidate) else candidate end end end
resourcesでprefixが作られる流れとしては
CANONICAL_ACTIONS = %w(index create new show update destroy)
の要素が一つづつactionとしてname_for_actionメソッドに渡されるprefix_name_for_actionにasとactionが渡され、
- as があればprefixに代入される
- as がない場合actionがCANONICAL_ACTIONSに含まれていなければprefixに代入される
collection_nameとmember_nameにresourcesに渡したmodel名の複数形と単数形がそれぞれ代入される
action_nameメソッドが呼ばれ、scope_levelによってどの変数を組み合わせてprefixを作るかが決定する action_nameメソッドは以下の通り
action_nameメソッドで組み合わせが決まったので、それを
-
で繋いでprefixとする。すでに同じ名前のものがあれば先に作られたものを優先する
def action_name(name_prefix, prefix, collection_name, member_name) case scope_level when :nested [name_prefix, prefix] when :collection [prefix, name_prefix, collection_name] when :new [prefix, :new, name_prefix, member_name] when :member [prefix, name_prefix, member_name] when :root [name_prefix, collection_name, prefix] else [name_prefix, member_name, prefix] end end
です。
で、なぜ 'users/create' に users-path
が紐づいてしまったかと言うと
CANONICAL_ACTIONSよりも先にcollectionで指定された 'create' アクションがname_for_actionに渡される
'create'はCANONICAL_ACTIONSに含まれるのでprefixはnullになる
collection_nameはusers、member_nameはuser
action_nameメソッド内では
when :collection
に分岐し、[prefix, name_prefix, collection_name]
でprefixを作成prefix = nil、name_prefix = nil, collection_name = usersなのでここでprefix: users, uri : /users/create, action: createの謎のroutingが爆誕する
と言う訳でした
解決策
要はaction名が'create'なのがいけないので'/create'にしてみました
/はprefix_name_for_actionの一番最後で外されるので新たに作成したrouteは
create-users-path と言う名前になります
これはダメ、users_pathがmethod: post, url: 'users/create', action: 'create'になる
resources :users do collection do post 'create', action: 'create' end end
これはOK、users_pathがmethod: get, url: 'users', action: 'index'になる resources :users do collection do post '/create', action: 'create' end end
rails/mapper.rb at 97b08334589cf15e86b5c89e13b62ac39e910d34 · rails/rails · GitHub
resourcesで作られるroute
CANONICAL_ACTIONS = %w(index create new show update destroy)
が順番にactionとして
name_name_for_actions(as, action) メソッドに渡される
めでたしめでたし