method

build

Importance_0
v6.1.7.7 - Show latest stable - 0 notes - Class: ActionDispatch::Routing::Mapper::Mapping
build(scope, set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options) public

No documentation

This method has no description. You can help the Ruby on Rails community by adding new notes.

Hide source
# File actionpack/lib/action_dispatch/routing/mapper.rb, line 77
        def self.build(scope, set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
          scope_params = {
            blocks: scope[:blocks] || [],
            constraints: scope[:constraints] || {},
            defaults: (scope[:defaults] || {}).dup,
            module: scope[:module],
            options: scope[:options] || {}
          }

          new set: set, ast: ast, controller: controller, default_action: default_action,
              to: to, formatted: formatted, via: via, options_constraints: options_constraints,
              anchor: anchor, scope_params: scope_params, options: scope_params[:options].merge(options)
        end

        def self.check_via(via)
          if via.empty?
            msg = "You should not use the `match` method in your router without specifying an HTTP method.\n"                "If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n"                "If you want to expose your action to GET, use `get` in the router:\n"                "  Instead of: match \"controller#action\"\n"                "  Do: get \"controller#action\""
            raise ArgumentError, msg
          end
          via
        end

        def self.normalize_path(path, format)
          path = Mapper.normalize_path(path)

          if format == true
            "#{path}.:format"
          elsif optional_format?(path, format)
            "#{path}(.:format)"
          else
            path
          end
        end

        def self.optional_format?(path, format)
          format != false && !path.match?(OPTIONAL_FORMAT_REGEX)
        end

        def initialize(set,, ast,, controller,, default_action,, to,, formatted,, via,, options_constraints,, anchor,, scope_params,, options))
          @defaults           = scope_params[:defaults]
          @set                = set
          @to                 = intern(to)
          @default_controller = intern(controller)
          @default_action     = intern(default_action)
          @ast                = ast
          @anchor             = anchor
          @via                = via
          @internal           = options.delete(:internal)
          @scope_options      = scope_params[:options]

          path_params = []
          wildcard_options = {}
          ast.each do |node|
            if node.symbol?
              path_params << node.to_sym
            elsif formatted != false && node.star?
              # Add a constraint for wildcard route to make it non-greedy and match the
              # optional format part of the route by default.
              wildcard_options[node.name.to_sym] ||= /.+?/
            elsif node.cat?
              alter_regex_for_custom_routes(node)
            end
          end

          options = wildcard_options.merge!(options)

          options = normalize_options!(options, path_params, scope_params[:module])

          split_options = constraints(options, path_params)

          constraints = scope_params[:constraints].merge Hash[split_options[:constraints] || []]

          if options_constraints.is_a?(Hash)
            @defaults = Hash[options_constraints.find_all { |key, default|
              URL_OPTIONS.include?(key) && (String === default || Integer === default)
            }].merge @defaults
            @blocks = scope_params[:blocks]
            constraints.merge! options_constraints
          else
            @blocks = blocks(options_constraints)
          end

          requirements, conditions = split_constraints path_params, constraints
          verify_regexp_requirements requirements.map(&:last).grep(Regexp)

          formats = normalize_format(formatted)

          @requirements = formats[:requirements].merge Hash[requirements]
          @conditions = Hash[conditions]
          @defaults = formats[:defaults].merge(@defaults).merge(normalize_defaults(options))

          if path_params.include?(:action) && !@requirements.key?(:action)
            @defaults[:action] ||= "index"
          end

          @required_defaults = (split_options[:required_defaults] || []).map(&:first)
        end

        def make_route(name, precedence)
          Journey::Route.new(name: name, app: application, path: path, constraints: conditions,
                             required_defaults: required_defaults, defaults: defaults,
                             request_method_match: request_method, precedence: precedence,
                             scope_options: scope_options, internal: @internal)
        end

        def application
          app(@blocks)
        end

        JOINED_SEPARATORS = SEPARATORS.join # :nodoc:

        def path
          Journey::Path::Pattern.new(@ast, requirements, JOINED_SEPARATORS, @anchor)
        end

        def conditions
          build_conditions @conditions, @set.request_class
        end

        def build_conditions(current_conditions, request_class)
          conditions = current_conditions.dup

          conditions.keep_if do |k, _|
            request_class.public_method_defined?(k)
          end
        end
        private :build_conditions

        def request_method
          @via.map { |x| Journey::Route.verb_matcher(x) }
        end
        private :request_method

        private
          # Find all the symbol nodes that are adjacent to literal nodes and alter
          # the regexp so that Journey will partition them into custom routes.
          def alter_regex_for_custom_routes(node)
            if node.left.literal? && node.right.symbol?
              symbol = node.right
            elsif node.left.literal? && node.right.cat? && node.right.left.symbol?
              symbol = node.right.left
            elsif node.left.symbol? && node.right.literal?
              symbol = node.left
            elsif node.left.symbol? && node.right.cat? && node.right.left.literal?
              symbol = node.left
            end

            if symbol
              symbol.regexp = /(?:#{Regexp.union(symbol.regexp, '-')})+/
            end
          end

          def intern(object)
            object.is_a?(String) ? -object : object
          end

          def normalize_options!(options, path_params, modyoule)
            if path_params.include?(:controller)
              raise ArgumentError, ":controller segment is not allowed within a namespace block" if modyoule

              # Add a default constraint for :controller path segments that matches namespaced
              # controllers with default routes like :controller/:action/:id(.:format), e.g:
              # GET /admin/products/show/1
              # => { controller: 'admin/products', action: 'show', id: '1' }
              options[:controller] ||= /.+?/
            end

            if to.respond_to?(:action) || to.respond_to?(:call)
              options
            else
              to_endpoint = split_to to
              controller  = to_endpoint[0] || default_controller
              action      = to_endpoint[1] || default_action

              controller = add_controller_module(controller, modyoule)

              options.merge! check_controller_and_action(path_params, controller, action)
            end
          end

          def split_constraints(path_params, constraints)
            constraints.partition do |key, requirement|
              path_params.include?(key) || key == :controller
            end
          end

          def normalize_format(formatted)
            case formatted
            when true
              { requirements: { format: /.+/ },
                defaults:     {} }
            when Regexp
              { requirements: { format: formatted },
                defaults:     { format: nil } }
            when String
              { requirements: { format: Regexp.compile(formatted) },
                defaults:     { format: formatted } }
            else
              { requirements: {}, defaults: {} }
            end
          end

          def verify_regexp_requirements(requirements)
            requirements.each do |requirement|
              if ANCHOR_CHARACTERS_REGEX.match?(requirement.source)
                raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
              end

              if requirement.multiline?
                raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
              end
            end
          end

          def normalize_defaults(options)
            Hash[options.reject { |_, default| Regexp === default }]
          end

          def app(blocks)
            if to.respond_to?(:action)
              Routing::RouteSet::StaticDispatcher.new to
            elsif to.respond_to?(:call)
              Constraints.new(to, blocks, Constraints::CALL)
            elsif blocks.any?
              Constraints.new(dispatcher(defaults.key?(:controller)), blocks, Constraints::SERVE)
            else
              dispatcher(defaults.key?(:controller))
            end
          end

          def check_controller_and_action(path_params, controller, action)
            hash = check_part(:controller, controller, path_params, {}) do |part|
              translate_controller(part) {
                message = +"'#{part}' is not a supported controller name. This can lead to potential routing problems."
                message << " See https://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"

                raise ArgumentError, message
              }
            end

            check_part(:action, action, path_params, hash) { |part|
              part.is_a?(Regexp) ? part : part.to_s
            }
          end

          def check_part(name, part, path_params, hash)
            if part
              hash[name] = yield(part)
            else
              unless path_params.include?(name)
                message = "Missing :#{name} key on routes definition, please check your routes."
                raise ArgumentError, message
              end
            end
            hash
          end

          def split_to(to)
            if /#/.match?(to)
              to.split("#").map!(&:-@)
            else
              []
            end
          end

          def add_controller_module(controller, modyoule)
            if modyoule && !controller.is_a?(Regexp)
              if controller&.start_with?("/")
                -controller[1..-1]
              else
                -[modyoule, controller].compact.join("/")
              end
            else
              controller
            end
          end

          def translate_controller(controller)
            return controller if Regexp === controller
            return controller.to_s if /\A[a-z_0-9][a-z_0-9\/]*\z/.match?(controller)

            yield
          end

          def blocks(callable_constraint)
            unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?)
              raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?"
            end
            [callable_constraint]
          end

          def constraints(options, path_params)
            options.group_by do |key, option|
              if Regexp === option
                :constraints
              else
                if path_params.include?(key)
                  :path_params
                else
                  :required_defaults
                end
              end
            end
          end

          def dispatcher(raise_on_name_error)
            Routing::RouteSet::Dispatcher.new raise_on_name_error
          end
      end
Register or log in to add new notes.