route.rb的resources命令浅析
跟踪一个简单的resources命令:
Rails.application.routes.draw do
binding.trace_tree(htmp: 'routes/resources') do
resources :students do
end
end
得调用栈如下
本质上它也只是调用了collection、member、get、post等基本的路由定义命令
def resources(*resources, &block)
options = resources.extract_options!.dup
if apply_common_behavior_for(:resources, resources, options, &block)
return self
end
with_scope_level(:resources) do
options = apply_action_options options
resource_scope(Resource.new(resources.pop, api_only?, @scope[:shallow], options)) do
yield if block_given?
concerns(options[:concerns]) if options[:concerns]
collection do
get :index if parent_resource.actions.include?(:index)
post :create if parent_resource.actions.include?(:create)
end
new do
get :new
end if parent_resource.actions.include?(:new)
set_member_mappings_for_resource
end
end
self
end
def set_member_mappings_for_resource
member do
get :edit if parent_resource.actions.include?(:edit)
get :show if parent_resource.actions.include?(:show)
if parent_resource.actions.include?(:update)
patch :update
put :update
end
delete :destroy if parent_resource.actions.include?(:destroy)
end
end
但有点奇怪的是,它这样配合collection、member定义的index、create、show、update、destroy,却没有出现在url的匹配模式上,如/students/index,为什么呢?
一路跟踪到add_route,可见url模式是由path_for_action产生的
def path_for_action(action, path)
return "#{@scope[:path]}/#{path}" if path
if canonical_action?(action)
@scope[:path].to_s
else
"#{@scope[:path]}/#{action_path(action)}"
end
end
def canonical_action?(action)
resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
end
根据其判断,若当前scope在RESOURCE_METHOD_SCOPES = [:collection, :member, :new]之中,且action为CANONICAL_ACTIONS = %w(index create new show update destroy)之一的时候,则匹配的路径省略该动词
但同时又发现,这里也包含了new,而现实中新建资源的页面url是带new的,唯有new并没有省略,怎么回事呢?
其实collection、member、new所创建的scope的路径一开始就不一样了
def collection
unless resource_scope?
raise ArgumentError, "can't use collection outside resource(s) scope"
end
with_scope_level(:collection) do
path_scope(parent_resource.collection_scope) do
yield
end
end
end
def member
unless resource_scope?
raise ArgumentError, "can't use member outside resource(s) scope"
end
with_scope_level(:member) do
if shallow?
shallow_scope {
path_scope(parent_resource.member_scope) { yield }
}
else
path_scope(parent_resource.member_scope) { yield }
end
end
end
def new
unless resource_scope?
raise ArgumentError, "can't use new outside resource(s) scope"
end
with_scope_level(:new) do
path_scope(parent_resource.new_scope(action_path(:new))) do
yield
end
end
end
collection是用原资源的路径,member则是拼上:id,而new则是拼上new
alias :collection_scope :path
def member_scope
"#{path}/:#{param}"
end
alias :shallow_scope :member_scope
def new_scope(new_path)
"#{path}/#{new_path}"
end
所以path_for_action因canonical_action?忽略action而直接使用当前scope的path时,其实已带上new在末尾