Route = Struct.new(:name, :path, :has_params?) do def <=> (other) self.name <=> other.name end end def placeholder_params_hash(params) Hash[params.zip(params.map { |param| "${params.#{param.to_s.camelize(:lower)}}" })] end class InternalRouteGenerator def self.path_for_route(route, params) path = route.format(placeholder_params_hash(params)) path = path.gsub(/%7B(.+?)%7D/, '{\1}') # undo escaping of JavaScript placeholdes path.gsub(/\.\$\{params.format\}$/, '') # we favor ?format= end def self.all routes = Rails.application.routes.routes.named_routes routes.map do |name, route| params = route.parts Route.new("#{name}_path", self.path_for_route(route, params), !params.empty?) end end end class ApiRouteGenerator def self.path_for_route(route, params, api_version) values = placeholder_params_hash(params).merge(version: api_version) segments = route.path_segments_with_values(values).reject { |s| route.optional_segment?(s) } '/' + segments.join('/') end def self.all routes = Array(Grape::API.decorated_routes) routes.flat_map do |route| Array(route.route_options[:version]).map do |api_version| params = route.route_options[:params].keys name = route.path_helper_name({ version: api_version, format: route.extension }) Route.new(name, self.path_for_route(route, params, api_version), !params.empty?) end end end end namespace :frontend do task :routes do def write_routes(file, routes) routes.each do |route| name = route.name.camelize(:lower) path = "${baseUrl}#{route.path}" if route.has_params? file.puts "export const #{name} = params => `#{path}`;" else file.puts "export const #{name} = () => `#{path}`;" end end end all_routes = (InternalRouteGenerator.all + ApiRouteGenerator.all).uniq { |route| route.name }.sort output_path = File.join(Rails.root, 'app', 'assets', 'javascripts', 'routes.js') File.open(output_path, 'w') do |file| file.puts '// This file was generated by bin/rake frontend:routes' file.puts "const baseUrl = gon.relative_url_root || '';" write_routes(file, all_routes) end end end