区别

path是相对路径,url是绝对路径

*_path are for views because ahrefs are implicitly linked to the current URL. So it'd be a waste of bytes to repeat it over and over.

In the controller, though, *_url is needed for redirect_to because the HTTP specification mandates that the Location: header in 3xx redirects is a complete URL.

model为复数的会对应到action=>index, 为单数时需要传递id参数并对应到action=>show

例如,如对于user而言:

users_url # => http://localhost:3000/users
users_path # => /users
user_url(1) # => http://localhost:3000/users/1
user_path(1) # => /users/1

url 的名字        REST方法    对应的url的表达式              对应的controller#action
----------------------------------------------------------------------------------
travels_path      GET         /travels(.:format)            travels#index
new_travel_path   GET         /travels/new(.:format)        travels#new
edit_travel_path  GET         /travels/:id/edit(.:format)   travels#edit
travel_path       GET         /travels/:id(.:format)        travels#show
travel_path       PATCH/PUT   /travels/:id(.:format)        travels#update
travel_path       POST        /travels/:id(.:format)        travels#create
travel_path       DELETE      /travels/:id(.:format)        travels#destroy


在route_set中被初始化,然后在view或controller中才能被调用。(是method)

何时生成

在WelcomeController的index方法中检查:welcome_index_path的source_location,得:

[3] pry(#)> method(:welcome_index_path).source_location
=> ["/home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.0.1/lib/action_dispatch/routing/route_set.rb", 283]


查看如下:

def define_url_helper(mod, route, name, opts, route_key, url_strategy)
  helper = UrlHelper.create(route, opts, route_key, url_strategy)
  mod.module_eval do
    define_method(name) do |*args|
      last = args.last
      options = case last
                when Hash
                  args.pop
                when ActionController::Parameters
                  if last.permitted?
                    args.pop.to_h
                  else
                    raise ArgumentError, ActionDispatch::Routing::INSECURE_URL_PARAMETERS_MESSAGE
                  end
                end
      helper.call self, args, options
    end
  end
end


在define_method前加入调试语句

binding.pry if /welcome_index_path/ =~ name


重新rails server,结果就立即进入了pry。查看调用栈

[7] pry(#)> _bs_
=> [#<binding:70310498294340 #<module:0x007fe4dfcbf3b0="">.block in define_url_helper /home/z/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/actionpack-5.0.1/lib/action_dispatch/routing/route_set.rb:283>,
 #<binding:70310498342680 actiondispatch::routing::routeset::namedroutecollection#define_url_helper="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.1="" lib="" action_dispatch="" routing="" route_set.rb:282="">,
 #<binding:70310498390220 actiondispatch::routing::routeset::namedroutecollection#add="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.1="" lib="" action_dispatch="" routing="" route_set.rb:114="">,
 #<binding:70310498454920 actiondispatch::routing::routeset#add_route="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.1="" lib="" action_dispatch="" routing="" route_set.rb:516="">,
 #<binding:70310498501300 actiondispatch::routing::mapper#add_route="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.1="" lib="" action_dispatch="" routing="" mapper.rb:1657="">,
 #<binding:70310498550160 actiondispatch::routing::mapper#decomposed_match="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.1="" lib="" action_dispatch="" routing="" mapper.rb:1628="">,
 #<binding:70310498607320 actiondispatch::routing::mapper#block="" in="" map_match="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.1="" lib="" action_dispatch="" routing="" mapper.rb:1927="">,
 #<binding:70310498704400 actiondispatch::routing::mapper#map_match="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.1="" lib="" action_dispatch="" routing="" mapper.rb:1910="">,
 #<binding:70310498753600 actiondispatch::routing::mapper#match="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.1="" lib="" action_dispatch="" routing="" mapper.rb:1599="">,
 #<binding:70310498810300 actiondispatch::routing::mapper#map_method="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.1="" lib="" action_dispatch="" routing="" mapper.rb:719="">,
 #<binding:70310498859480 actiondispatch::routing::mapper#get="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.1="" lib="" action_dispatch="" routing="" mapper.rb:680="">,
 #<binding:70310498907560 actiondispatch::routing::mapper#block="" in="" <top="" (required)=""> /home/z/test_rails/dapo/config/routes.rb:4>,
 #<binding:70310498964660 actiondispatch::routing::routeset#eval_block="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.1="" lib="" action_dispatch="" routing="" route_set.rb:390="">,
 #<binding:70310499014220 actiondispatch::routing::routeset#draw="" home="" z="" .rbenv="" versions="" 2.4.0="" lib="" ruby="" gems="" 2.4.0="" gems="" actionpack-5.0.1="" lib="" action_dispatch="" routing="" route_set.rb:372="">,
 #<binding:70310499094380 object#<top="" (required)=""> /home/z/test_rails/dapo/config/routes.rb:1>,
#...</binding:70310499094380></binding:70310499014220></binding:70310498964660></binding:70310498907560></binding:70310498859480></binding:70310498810300></binding:70310498753600></binding:70310498704400></binding:70310498607320></binding:70310498550160></binding:70310498501300></binding:70310498454920></binding:70310498390220></binding:70310498342680></binding:70310498294340>


可见源自在config/routes.rb

怎样返回url

对*_url进行trace

[2] pry(#)> binding.trace_tree{welcome_index_url}
WelcomeController#block in index (pry):2
└─WelcomeController#block (2 levels) in define_url_helper $GemPath0/gems/actionpack-5.0.1/lib/action_dispatch/routing/route_set.rb:286
  └─ActionDispatch::Routing::RouteSet::NamedRouteCollection::UrlHelper::OptimizedUrlHelper#call $GemPath0/gems/actionpack-5.0.1/lib/action_dispatch/routing/route_set.rb:171
    ├─ActionDispatch::Routing::RouteSet::NamedRouteCollection::UrlHelper::OptimizedUrlHelper#optimize_routes_generation? $GemPath0/gems/actionpack-5.0.1/lib/action_dispatch/routing/route_set.rb:191
    │ └─#..
    ├─WelcomeController#url_options $GemPath0/gems/actionpack-5.0.1/lib/action_controller/metal/url_for.rb:28
    │ └─#...
    ├─ActionDispatch::Routing::RouteSet::NamedRouteCollection::UrlHelper::OptimizedUrlHelper#optimized_helper $GemPath0/gems/actionpack-5.0.1/lib/action_dispatch/routing/route_set.rb:183
    │ └─#...
    └─ActionDispatch::Routing::RouteSet.block in  $GemPath0/gems/actionpack-5.0.1/lib/action_dispatch/routing/route_set.rb:306
      └─ActionDispatch::Http::URL.url_for $GemPath0/gems/actionpack-5.0.1/lib/action_dispatch/http/url.rb:48
        └─#...


可见使用的是OptimizedUrlHelper(未研究),最后用route_set.rb:306的block,里面调了个url_for来返回url

而*_path也类似,用的是route_set.rb:305的block,里面调了个path_for

[5] pry(#)> binding.trace_tree{welcome_index_path}
WelcomeController#block in index (pry):5
├─WelcomeController#block (2 levels) in define_url_helper $GemPath0/gems/actionpack-5.0.1/lib/action_dispatch/routing/route_set.rb:286
└─WelcomeController#block (2 levels) in define_url_helper $GemPath0/gems/actionpack-5.0.1/lib/action_dispatch/routing/route_set.rb:286
  └─ActionDispatch::Routing::RouteSet::NamedRouteCollection::UrlHelper::OptimizedUrlHelper#call $GemPath0/gems/actionpack-5.0.1/lib/action_dispatch/routing/route_set.rb:171
    ├─ActionDispatch::Routing::RouteSet::NamedRouteCollection::UrlHelper::OptimizedUrlHelper#optimize_routes_generation? $GemPath0/gems/actionpack-5.0.1/lib/action_dispatch/routing/route_set.rb:191
    │ └─#...
    ├─WelcomeController#url_options $GemPath0/gems/actionpack-5.0.1/lib/action_controller/metal/url_for.rb:28
    │ └─#...
    ├─ActionDispatch::Routing::RouteSet::NamedRouteCollection::UrlHelper::OptimizedUrlHelper#optimized_helper $GemPath0/gems/actionpack-5.0.1/lib/action_dispatch/routing/route_set.rb:183
    │ └─#...
    └─ActionDispatch::Routing::RouteSet.block in  $GemPath0/gems/actionpack-5.0.1/lib/action_dispatch/routing/route_set.rb:305
      └─ActionDispatch::Http::URL.path_for $GemPath0/gems/actionpack-5.0.1/lib/action_dispatch/http/url.rb:68


这两个block就是

PATH    = ->(options) { ActionDispatch::Http::URL.path_for(options) }
UNKNOWN = ->(options) { ActionDispatch::Http::URL.url_for(options) }


*_url那个叫做UNKNOWN,看上去有点奇怪,搜一下哪里用到或是否有其他名字指向他

$ actionpack-5.0.1 git:(master) grep UNKNOWN -rn *
lib/action_dispatch/routing/route_set.rb:115:          define_url_helper @url_helpers_module,  route, url_name,  route.defaults, name, UNKNOWN
lib/action_dispatch/routing/route_set.rb:303:      UNKNOWN = ->(options) { ActionDispatch::Http::URL.url_for(options) }
lib/action_dispatch/routing/route_set.rb:685:      def url_for(options, route_name = nil, url_strategy = UNKNOWN)


唯一与config/routes.rb调用栈能联系起来的,就是ActionDispatch::Routing::RouteSet::NamedRouteCollection#add了

def add(name, route)
  key       = name.to_sym
  path_name = :"#{name}_path"
  url_name  = :"#{name}_url"

  if routes.key? key
    @path_helpers_module.send :undef_method, path_name
    @url_helpers_module.send  :undef_method, url_name
  end
  routes[key] = route
  define_url_helper @path_helpers_module, route, path_name, route.defaults, name, PATH
  define_url_helper @url_helpers_module,  route, url_name,  route.defaults, name, UNKNOWN

  @path_helpers << path_name
  @url_helpers << url_name
end


而这里的@path_helpers_module和@url_helpers_module,可以猜到,就是会mixin到controller的匿名module

[9] pry(#)> self.class.ancestors
=> [WelcomeController,
 #,
 ApplicationController,
 #,
 #,
 #,
 ActionController::Base,
 Turbolinks::Redirection,
 Turbolinks::Controller,
 ActiveRecord::Railties::ControllerRuntime,
 ActionDispatch::Routing::RouteSet::MountedHelpers,
 ActionController::ParamsWrapper,
 #...
[13] pry(#)> self.class.ancestors[4].instance_methods(false)
=> [:rails_info_properties_path, :rails_info_routes_path, :rails_info_path, :rails_mailers_path, :welcome_index_path, :welcome_go_to_index_path]
[14] pry(#)> self.class.ancestors[5].instance_methods(false)
=> [:rails_info_properties_url, :rails_info_routes_url, :rails_info_url, :rails_mailers_url, :welcome_index_url, :welcome_go_to_index_url]