class RailsRoutesAnalyzer::ActionAnalysis

Constants

PREAMBLE_WARNING

Attributes

all_action_methods[R]
options[R]
unused_controllers[R]

Public Class Methods

new(route_analysis: RailsRoutesAnalyzer::RouteAnalysis.new, report_routed: false, report_duplicates: false, report_gems: false, report_modules: false, full_path: false, metadata: false) click to toggle source

Options:

report_duplicates - report actions which the parent controller also has
report_gems       - report actions which are implemented by a gem
report_modules    - report actions inherited from modules
report_routed     - report all actions including those with a route
full_path         - skips shortening file paths
metadata          - include discovered metadata about actions
# File lib/rails_routes_analyzer/action_analysis.rb, line 77
def initialize(route_analysis: RailsRoutesAnalyzer::RouteAnalysis.new,
               report_routed:     false,
               report_duplicates: false,
               report_gems:       false,
               report_modules:    false,
               full_path:         false,
               metadata:          false)

  @options = {
    report_routed:     report_routed,
    report_duplicates: report_duplicates,
    report_gems:       report_gems,
    report_modules:    report_modules,
    full_path:         full_path,
    metadata:          metadata,
  }

  @all_action_methods = analyze_action_methods(route_analysis: route_analysis)

  @by_controller = @all_action_methods.group_by(&:controller_class)
  @unused_controllers = find_unused_controllers
end

Public Instance Methods

print_actions_report() click to toggle source
print_report() click to toggle source

Protected Instance Methods

actions_for(controller) click to toggle source
# File lib/rails_routes_analyzer/action_analysis.rb, line 119
def actions_for(controller)
  @by_controller[controller] || []
end
actions_to_report(controller) click to toggle source
# File lib/rails_routes_analyzer/action_analysis.rb, line 123
def actions_to_report(controller)
  actions_for(controller).select do |action|
    action.needs_reporting?(**options)
  end.sort_by(&:action_name)
end
analyze_action_methods(route_analysis:) click to toggle source
# File lib/rails_routes_analyzer/action_analysis.rb, line 189
def analyze_action_methods(route_analysis:)
  implemented_routes = Set.new(route_analysis.implemented_routes)

  [].tap do |result|
    ActionController::Base.descendants.each do |controller|
      next if default_controller?(controller)

      action_methods = controller.action_methods.to_a.map(&:to_sym)

      if (parent_controller = controller.superclass) == ActionController::Base
        parent_controller = nil
        parent_actions    = []
      else
        parent_actions    = parent_controller.action_methods.to_a.map(&:to_sym)
      end

      action_methods.each do |action_name|
        controller_name = controller.name
        source_location = get_source_location(controller, action_name)

        owner = controller.instance_method(action_name).owner

        # A very strong likelyhood is that if the method is not defined by a module
        # it comes directly from any ActionController::Base subclass.
        #
        # This knowledge helps us ignore methods which might come for example from
        # someone (possibly unwisely) including a helper directly into a controller.
        from_module = owner.class == Module

        is_inherited = parent_actions.include?(action_name) \
                         && get_source_location(parent_controller, action_name) == source_location

        route_missing = !implemented_routes.include?([controller_name, action_name])

        sanitized_location = RailsRoutesAnalyzer.sanitize_source_location(source_location, options.slice(:full_path))

        result << ActionMethod.new(
          controller_name: controller_name,
          action_name:     action_name,
          is_inherited:    is_inherited,
          route_missing:   route_missing,
          source_location: sanitized_location,
          from_gem:        GemManager.identify_gem(source_location),
          owner:           owner,
          from_module:     from_module,
        )
      end
    end
  end
end
controller_has_no_routes?(controller) click to toggle source
# File lib/rails_routes_analyzer/action_analysis.rb, line 184
def controller_has_no_routes?(controller)
  no_direct_routes_to?(controller) \
    && controller.subclasses.all? { |c| controller_has_no_routes?(c) }
end
controller_likely_from_gem?(controller) click to toggle source
# File lib/rails_routes_analyzer/action_analysis.rb, line 154
def controller_likely_from_gem?(controller)
  actions_for(controller).all? { |action| action.from_gem? || action.from_module? || action.inherited? }
end
controller_needs_reporting?(controller) click to toggle source
# File lib/rails_routes_analyzer/action_analysis.rb, line 158
def controller_needs_reporting?(controller)
  if @controller_reporting_cache.key?(controller)
    return @controller_reporting_cache[controller]
  end

  @controller_reporting_cache[controller] = \
    actions_to_report(controller).any? \
    || controller.subclasses.any? { |subclass| controller_needs_reporting?(subclass) }
end
default_controller?(controller) click to toggle source
# File lib/rails_routes_analyzer/action_analysis.rb, line 176
def default_controller?(controller)
  defined?(::Rails::ApplicationController) && controller <= ::Rails::ApplicationController
end
find_unused_controllers() click to toggle source
# File lib/rails_routes_analyzer/action_analysis.rb, line 168
def find_unused_controllers
  ActionController::Base.descendants.select do |controller|
    controller_has_no_routes?(controller) \
      && !default_controller?(controller) \
      && (options[:report_gems] || !controller_likely_from_gem?(controller))
  end
end
get_source_location(controller_class_or_name, action_name) click to toggle source
# File lib/rails_routes_analyzer/action_analysis.rb, line 240
def get_source_location(controller_class_or_name, action_name)
  controller = controller_class_or_name
  controller = controller.constantize if controller.is_a?(String)

  controller.instance_method(action_name).source_location.join(':')
end
no_direct_routes_to?(controller) click to toggle source
# File lib/rails_routes_analyzer/action_analysis.rb, line 180
def no_direct_routes_to?(controller)
  actions_for(controller).all?(&:route_missing?)
end
print_missing_routes_report_preamble() click to toggle source
report_actions_recursive(controllers = ActionController::Base.subclasses, level = 0) click to toggle source
# File lib/rails_routes_analyzer/action_analysis.rb, line 129
def report_actions_recursive(controllers = ActionController::Base.subclasses, level = 0)
  controllers.each do |controller|
    next unless controller_needs_reporting?(controller)

    puts "#{'  ' * level}#{controller.name}"

    if (actions = actions_to_report(controller)).any?
      action_level = level + 1

      if controller.subclasses.any? { |subclass| controller_needs_reporting?(subclass) }
        puts "#{'  ' * action_level}Actions:"
        action_level += 1
      end

      max_action_length = [MAX_ACTION_LENGTH, actions.map { |a| a.action_name.size }.max].min

      actions.each do |action|
        puts "#{'  ' * action_level}#{action.pretty(max_action_length: max_action_length, **options)}"
      end
    end

    report_actions_recursive(controller.subclasses.sort_by(&:name), level + 1)
  end
end
unused_actions_present?() click to toggle source
# File lib/rails_routes_analyzer/action_analysis.rb, line 113
def unused_actions_present?
  @all_action_methods.any? do |action|
    action.needs_reporting?(**options)
  end
end