class RailsRoutesAnalyzer::RouteAnalysis

Attributes

app[RW]
only_except[RW]
only_only[RW]
route_calls[RW]
route_lines[RW]
route_log[RW]
verbose[RW]

Public Class Methods

new(app: Rails.application, verbose: false, only_only: false, only_except: false) click to toggle source
# File lib/rails_routes_analyzer/route_analysis.rb, line 14
def initialize(app: Rails.application, verbose: false, only_only: false, only_except: false)
  self.app         = app
  self.verbose     = verbose
  self.only_only   = only_only
  self.only_except = only_except

  analyze!
end

Public Instance Methods

all_unique_issues_file_names() click to toggle source
# File lib/rails_routes_analyzer/route_analysis.rb, line 134
def all_unique_issues_file_names
  issues.map(&:full_filename).uniq.sort
end
analyse_route_call(**kwargs) click to toggle source
# File lib/rails_routes_analyzer/route_analysis.rb, line 68
def analyse_route_call(**kwargs)
  controller_class_name = "#{kwargs[:controller_name]}_controller".camelize

  opts = kwargs.merge(controller_class_name: controller_class_name)

  route_call = RouteCall.new(opts)
  route_calls << route_call

  controller = nil
  begin
    controller = Object.const_get(controller_class_name)
  rescue LoadError, RuntimeError, NameError => e
    route_call.add_issue RouteIssue::NoController.new(error: e.message)
    return
  end

  if controller.nil?
    route_call.add_issue RouteIssue::NoController.new(error: "#{controller_class_name} is nil")
    return
  end

  analyze_action_availability(controller, route_call, **opts)
end
analyze!() click to toggle source
# File lib/rails_routes_analyzer/route_analysis.rb, line 39
def analyze!
  clear_data
  prepare_for_analysis

  RouteInterceptor.route_data.each do |(file_location, route_creation_method, controller_name), action_names|
    analyse_route_call(
      file_location:         file_location,
      route_creation_method: route_creation_method,
      controller_name:       controller_name,
      action_names:          action_names.uniq.sort,
    )
  end

  route_log.concat RouteInterceptor.route_log
  generate_route_lines
end
analyze_action_availability(controller, route_call, **opts) click to toggle source

Checks which if any actions referred to by the route don't exist.

# File lib/rails_routes_analyzer/route_analysis.rb, line 93
def analyze_action_availability(controller, route_call, **opts)
  present, missing = opts[:action_names].partition { |name| controller.action_methods.include?(name.to_s) }

  route_call[:present_actions] = present if present.any?

  if SINGLE_METHODS.include?(opts[:route_creation_method])
    # NOTE a single call like 'get' can add multiple actions if called in a loop
    if missing.present?
      route_call.add_issue RouteIssue::NoAction.new(missing_actions: missing)
    end
    return
  end

  return if missing.empty? # Everything is perfect, all routes match an action

  if present.sort == RESOURCE_ACTIONS.sort
    # Should happen only if RESOURCE_ACTIONS doesn't match which actions rails supports
    raise "shouldn't get all methods being present and yet some missing at the same time: #{present.inspect} #{missing.inspect}"
  end

  suggested_param = resource_route_suggested_param(present)

  route_call.add_issue RouteIssue::Resources.new(suggested_param: suggested_param)
end
clear_data() click to toggle source
# File lib/rails_routes_analyzer/route_analysis.rb, line 23
def clear_data
  self.route_lines = []
  self.route_calls = []
  self.route_log   = []
end
generate_route_lines() click to toggle source
# File lib/rails_routes_analyzer/route_analysis.rb, line 56
def generate_route_lines
  calls_per_line = route_calls.group_by do |record|
    [record.full_filename, record.line_number]
  end

  calls_per_line.each do |(full_filename, line_number), records|
    route_lines << RouteLine.new(full_filename: full_filename,
                                 line_number:   line_number,
                                 records:       records)
  end
end
implemented_routes() click to toggle source
# File lib/rails_routes_analyzer/route_analysis.rb, line 142
def implemented_routes
  Set.new.tap do |implemented_routes|
    route_calls.each do |route_call|
      (route_call.present_actions || []).each do |action|
        implemented_routes << [route_call.controller_class_name, action]
      end
    end
  end
end
issues() click to toggle source
# File lib/rails_routes_analyzer/route_analysis.rb, line 126
def issues
  route_calls.select(&:issue?)
end
non_issues() click to toggle source
# File lib/rails_routes_analyzer/route_analysis.rb, line 130
def non_issues
  route_calls.reject(&:issue?)
end
prepare_for_analysis() click to toggle source
# File lib/rails_routes_analyzer/route_analysis.rb, line 29
def prepare_for_analysis
  app.eager_load! # all controller classes need to be loaded

  ::ActionDispatch::Routing::Mapper::Mapping.prepend RouteInterceptor

  RouteInterceptor.route_log.clear

  app.reload_routes!
end
print_report() click to toggle source
resource_route_suggested_param(present) click to toggle source
# File lib/rails_routes_analyzer/route_analysis.rb, line 118
def resource_route_suggested_param(present)
  if (present.size < 4 || only_only) && !only_except
    "only: [#{present.sort.map { |x| ":#{x}" }.join(', ')}]"
  else
    "except: [#{(RESOURCE_ACTIONS - present).sort.map { |x| ":#{x}" }.join(', ')}]"
  end
end
route_calls_for_file_name(full_filename) click to toggle source
# File lib/rails_routes_analyzer/route_analysis.rb, line 138
def route_calls_for_file_name(full_filename)
  route_calls.select { |record| record.full_filename == full_filename.to_s }
end
route_lines_for_file(full_filename) click to toggle source
# File lib/rails_routes_analyzer/route_analysis.rb, line 152
def route_lines_for_file(full_filename)
  route_lines.select { |line| line.full_filename == full_filename.to_s }
end