結論から言え!
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) メソッドに渡される
めでたしめでたし