# frozen_string_literal: truerequire"action_controller/metal/exceptions"moduleActionDispatch# :stopdoc:moduleJourney# The Formatter class is used for formatting URLs. For example, parameters# passed to +url_for+ in Rails will eventually call Formatter#generate.classFormatterattr_reader:routesdefinitialize(routes)@routes=routes@cache=nilendclassRouteWithParamsattr_reader:paramsdefinitialize(route,parameterized_parts,params)@route=route@parameterized_parts=parameterized_parts@params=paramsenddefpath(_)@route.format(@parameterized_parts)endendclassMissingRouteattr_reader:routes,:name,:constraints,:missing_keys,:unmatched_keysdefinitialize(constraints,missing_keys,unmatched_keys,routes,name)@constraints=constraints@missing_keys=missing_keys@unmatched_keys=unmatched_keys@routes=routes@name=nameenddefpath(method_name)raiseActionController::UrlGenerationError.new(message,routes,name,method_name)enddefparamspath("unknown")enddefmessagemessage=+"No route matches #{Hash[constraints.sort_by{|k,v|k.to_s}].inspect}"message<<", missing required keys: #{missing_keys.sort.inspect}"ifmissing_keys&&!missing_keys.empty?message<<", possible unmatched constraints: #{unmatched_keys.sort.inspect}"ifunmatched_keys&&!unmatched_keys.empty?messageendenddefgenerate(name,options,path_parameters)original_options=options.duppath_params=options.delete(:path_params)||{}options=path_params.merge(options)constraints=path_parameters.merge(options)missing_keys=nilmatch_route(name,constraints)do|route|parameterized_parts=extract_parameterized_parts(route,options,path_parameters)# Skip this route unless a name has been provided or it is a# standard Rails route since we can't determine whether an options# hash passed to url_for matches a Rack application or a redirect.nextunlessname||route.dispatcher?missing_keys=missing_keys(route,parameterized_parts)nextifmissing_keys&&!missing_keys.empty?params=options.delete_ifdo|key,_|# top-level params' normal behavior of generating query_params# should be preserved even if the same key is also a bind_paramparameterized_parts.key?(key)||route.defaults.key?(key)||(path_params.key?(key)&&!original_options.key?(key))enddefaults=route.defaultsrequired_parts=route.required_partsroute.parts.reverse_eachdo|key|breakifdefaults[key].nil?&¶meterized_parts[key].present?nextifparameterized_parts[key].to_s!=defaults[key].to_sbreakifrequired_parts.include?(key)parameterized_parts.delete(key)endreturnRouteWithParams.new(route,parameterized_parts,params)endunmatched_keys=(missing_keys||[])&constraints.keysmissing_keys=(missing_keys||[])-unmatched_keysMissingRoute.new(constraints,missing_keys,unmatched_keys,routes,name)enddefclear@cache=nilendprivatedefextract_parameterized_parts(route,options,recall)parameterized_parts=recall.merge(options)keys_to_keep=route.parts.reverse_each.drop_while{|part|!(options.key?(part)||route.scope_options.key?(part))||(options[part].nil?&&recall[part].nil?)}|route.required_partsparameterized_parts.delete_ifdo|bad_key,_|!keys_to_keep.include?(bad_key)endparameterized_parts.eachdo|k,v|ifk==:controllerparameterized_parts[k]=velseparameterized_parts[k]=v.to_paramendendparameterized_parts.compact!parameterized_partsenddefnamed_routesroutes.named_routesenddefmatch_route(name,options)ifnamed_routes.key?(name)yieldnamed_routes[name]elseroutes=non_recursive(cache,options)supplied_keys=options.each_with_object({})do|(k,v),h|h[k.to_s]=trueifvendhash=routes.group_by{|_,r|r.score(supplied_keys)}hash.keys.sort.reverse_eachdo|score|breakifscore<0hash[score].sort_by{|i,_|i}.eachdo|_,route|yieldrouteendendendenddefnon_recursive(cache,options)routes=[]queue=[cache]whilequeue.any?c=queue.shiftroutes.concat(c[:___routes])ifc.key?(:___routes)options.eachdo|pair|queue<<c[pair]ifc.key?(pair)endendroutesend# Returns an array populated with missing keys if any are present.defmissing_keys(route,parts)missing_keys=niltests=route.path.requirements_for_missing_keys_checkroute.required_parts.each{|key|casetests[key]whennilunlessparts[key]missing_keys||=[]missing_keys<<keyendelseunlesstests[key].match?(parts[key])missing_keys||=[]missing_keys<<keyendend}missing_keysenddefpossibles(cache,options,depth=0)cache.fetch(:___routes){[]}+options.find_all{|pair|cache.key?(pair)}.flat_map{|pair|possibles(cache[pair],options,depth+1)}enddefbuild_cacheroot={___routes: []}routes.routes.each_with_indexdo|route,i|leaf=route.required_defaults.inject(root)do|h,tuple|h[tuple]||={}end(leaf[:___routes]||=[])<<[i,route]endrootenddefcache@cache||=build_cacheendendend# :startdoc:end