require "action_dispatch/routing/mapper" require_relative "utils" module ActionDispatch::Routing class Mapper # Internal interface to get the controller class from the name and current scope. def _get_controller_class(name, pluralize: true, fallback_reverse_pluralization: true) # Get class name. name = name.to_s.camelize # Camelize to leave plural names plural. name = name.pluralize if pluralize if name == name.pluralize name_reverse = name.singularize else name_reverse = name.pluralize end name += "Controller" name_reverse += "Controller" # Get scope for the class. if @scope[:module] mod = @scope[:module].to_s.camelize.constantize else mod = Object end # Convert class name to class. begin controller = mod.const_get(name) rescue NameError if fallback_reverse_pluralization controller = mod.const_get(name_reverse) else raise end end return controller end # Interal interface for routing extra actions. def _route_extra_actions(actions, &block) parsed_actions = RESTFramework::Utils.parse_extra_actions(actions) parsed_actions.each do |action, config| [config[:methods]].flatten.each do |m| public_send(m, config[:path], action: action, **(config[:kwargs] || {})) end yield if block_given? end end # Internal core implementation of the `rest_resource(s)` router, both singular and plural. # @param default_singular [Boolean] the default plurality of the resource if the plurality is # not otherwise defined by the controller # @param name [Symbol] the resource name, from which path and controller are deduced by default def _rest_resources(default_singular, name, **kwargs, &block) controller = kwargs.delete(:controller) || name if controller.is_a?(Class) controller_class = controller else controller_class = self._get_controller_class(controller, pluralize: !default_singular) end # Set controller if it's not explicitly set. kwargs[:controller] = name unless kwargs[:controller] # Passing `unscoped: true` will prevent a nested resource from being scoped. unscoped = kwargs.delete(:unscoped) # Determine plural/singular resource. force_singular = kwargs.delete(:force_singular) force_plural = kwargs.delete(:force_plural) if force_singular singular = true elsif force_plural singular = false elsif !controller_class.singleton_controller.nil? singular = controller_class.singleton_controller else singular = default_singular end resource_method = singular ? :resource : :resources # Call either `resource` or `resources`, passing appropriate modifiers. skip = RESTFramework::Utils.get_skipped_builtin_actions(controller_class) public_send(resource_method, name, except: skip, **kwargs) do if controller_class.respond_to?(:extra_member_actions) member do self._route_extra_actions(controller_class.extra_member_actions) end end collection do # Route extra controller-defined actions. self._route_extra_actions(controller_class.extra_actions) # Route extra RRF-defined actions. RESTFramework::RRF_BUILTIN_ACTIONS.each do |action, methods| next unless controller_class.method_defined?(action) [methods].flatten.each do |m| public_send(m, "", action: action) if self.respond_to?(m) end end # Route bulk actions, if configured. RESTFramework::RRF_BUILTIN_BULK_ACTIONS.each do |action, methods| next unless controller_class.method_defined?(action) [methods].flatten.each do |m| public_send(m, "", action: action) if self.respond_to?(m) end end end if unscoped yield if block_given? else scope(module: name, as: name) do yield if block_given? end end end end # Public interface for creating singular RESTful resource routes. def rest_resource(*names, **kwargs, &block) names.each do |n| self._rest_resources(true, n, **kwargs, &block) end end # Public interface for creating plural RESTful resource routes. def rest_resources(*names, **kwargs, &block) names.each do |n| self._rest_resources(false, n, **kwargs, &block) end end # Route a controller without the default resourceful paths. def rest_route(name=nil, **kwargs, &block) controller = kwargs.delete(:controller) || name route_root_to = kwargs.delete(:route_root_to) if controller.is_a?(Class) controller_class = controller else controller_class = self._get_controller_class(controller, pluralize: false) end # Set controller if it's not explicitly set. kwargs[:controller] = name unless kwargs[:controller] # Passing `unscoped: true` will prevent a nested resource from being scoped. unscoped = kwargs.delete(:unscoped) # Route actions using the resourceful router, but skip all builtin actions. public_send(:resource, name, only: [], **kwargs) do # Route a root for this resource. if route_root_to get("", action: route_root_to, as: "") end collection do # Route extra controller-defined actions. self._route_extra_actions(controller_class.extra_actions) # Route extra RRF-defined actions. RESTFramework::RRF_BUILTIN_ACTIONS.each do |action, methods| next unless controller_class.method_defined?(action) [methods].flatten.each do |m| public_send(m, "", action: action) if self.respond_to?(m) end end end if unscoped yield if block_given? else scope(module: name, as: name) do yield if block_given? end end end end # Route a controller's `#root` to '/' in the current scope/namespace, along with other actions. def rest_root(name=nil, **kwargs, &block) # By default, use RootController#root. root_action = kwargs.delete(:action) || :root controller = kwargs.delete(:controller) || name || :root # Remove path if name is nil (routing to the root of current namespace). unless name kwargs[:path] = "" end return rest_route(controller, route_root_to: root_action, **kwargs) do yield if block_given? end end end end