method

build

Importance_0
v7.1.3.4 - 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 79
        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)
          @anchor             = anchor
          @via                = via
          @internal           = options.delete(:internal)
          @scope_options      = scope_params[:options]
          ast                 = Journey::Ast.new(ast, formatted)

          options = ast.wildcard_options.merge!(options)

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

          split_options = constraints(options, ast.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 ast.path_params, constraints
          verify_regexp_requirements requirements, ast.wildcard_options

          formats = normalize_format(formatted)

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

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

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

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

        JOINED_SEPARATORS = SEPARATORS.join # :nodoc:

        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, source_location: route_source_location)
        end

        def application
          app(@blocks)
        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
          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
              if to.nil?
                controller = default_controller
                action = default_action
              elsif to.is_a?(String) && to.include?("#")
                to_endpoint = to.split("#").map!(&:-@)
                controller  = to_endpoint[0]
                action      = to_endpoint[1]
              else
                raise ArgumentError, ":to must respond to `action` or `call`, or it must be a String that includes '#'"
              end

              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, wildcard_options)
            requirements.each do |requirement, regex|
              next unless regex.is_a? Regexp

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

              if regex.multiline?
                next if wildcard_options.key?(requirement)

                raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{regex.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 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

          def route_source_location
            if Mapper.route_source_locations
              action_dispatch_dir = File.expand_path("..", __dir__)
              caller_location = caller_locations.find { |location| !location.path.include?(action_dispatch_dir) }
              cleaned_path = Mapper.backtrace_cleaner.clean([caller_location.path]).first
              "#{cleaned_path}:#{caller_location.lineno}" if cleaned_path
            end
          end
      end
Register or log in to add new notes.