Class: Woods::Extractors::JobExtractor

Inherits:
Object
  • Object
show all
Includes:
SharedDependencyScanner, SharedUtilityMethods
Defined in:
lib/woods/extractors/job_extractor.rb

Overview

JobExtractor handles ActiveJob and Sidekiq job extraction.

Background jobs are critical for understanding async behavior. They often perform important business logic that would otherwise be unclear from just looking at models and controllers.

We extract:

  • Queue configuration

  • Retry/error handling configuration

  • Arguments (the job’s interface)

  • What the job calls (dependencies)

  • What triggers this job (reverse lookup via dependencies)

Examples:

extractor = JobExtractor.new
units = extractor.extract_all
order_job = units.find { |u| u.identifier == "ProcessOrderJob" }

Constant Summary collapse

JOB_DIRECTORIES =

Directories to scan for jobs

%w[
  app/jobs
  app/workers
  app/sidekiq
].freeze

Constants included from SharedDependencyScanner

SharedDependencyScanner::FORM_ACTION_HELPER, SharedDependencyScanner::ROUTE_HELPER_PATTERN

Instance Method Summary collapse

Methods included from SharedDependencyScanner

#extract_constantize_targets, #scan_common_dependencies, #scan_form_dependencies, #scan_job_dependencies, #scan_mailer_dependencies, #scan_model_dependencies, #scan_navigation_dependencies, #scan_service_dependencies

Methods included from SharedUtilityMethods

#app_source?, #condition_label, #count_loc, #detect_entry_points, #extract_action_filter_actions, #extract_callback_conditions, #extract_class_methods, #extract_custom_errors, #extract_initialize_params, #extract_namespace, #extract_parent_class, #extract_public_methods, #resolve_source_location, #skip_file?

Constructor Details

#initializeJobExtractor

Returns a new instance of JobExtractor.



37
38
39
40
# File 'lib/woods/extractors/job_extractor.rb', line 37

def initialize
  @directories = JOB_DIRECTORIES.map { |d| Rails.root.join(d) }
                                .select(&:directory?)
end

Instance Method Details

#extract_allArray<ExtractedUnit>

Extract all jobs in the application

Returns:



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/woods/extractors/job_extractor.rb', line 45

def extract_all
  units = []

  # File-based discovery (catches everything)
  @directories.each do |dir|
    Dir[dir.join('**/*.rb')].each do |file|
      unit = extract_job_file(file)
      units << unit if unit
    end
  end

  # Also try class-based discovery for ActiveJob
  if defined?(ApplicationJob)
    seen = units.to_set(&:identifier)
    ApplicationJob.descendants.each do |job_class|
      next if seen.include?(job_class.name)

      unit = extract_job_class(job_class)
      if unit
        units << unit
        seen << unit.identifier
      end
    end
  end

  units.compact
end

#extract_job_class(job_class) ⇒ ExtractedUnit?

Extract a job from its class (runtime introspection)

Parameters:

  • job_class (Class)

    The job class

Returns:



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/woods/extractors/job_extractor.rb', line 105

def extract_job_class(job_class)
  return nil if job_class.name.nil?

  file_path = source_file_for(job_class)
  source = file_path && File.exist?(file_path) ? File.read(file_path) : ''

  unit = ExtractedUnit.new(
    type: :job,
    identifier: job_class.name,
    file_path: file_path
  )

  unit.namespace = extract_namespace(job_class.name)
  unit.source_code = annotate_source(source, job_class.name)
  unit. = (job_class, source)
  unit.dependencies = extract_dependencies(source, job_class.name)

  unit
rescue StandardError => e
  Rails.logger.error("Failed to extract job #{job_class.name}: #{e.message}")
  nil
end

#extract_job_file(file_path) ⇒ ExtractedUnit?

Extract a job from its file

Parameters:

  • file_path (String)

    Path to the job file

Returns:



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/woods/extractors/job_extractor.rb', line 77

def extract_job_file(file_path)
  source = File.read(file_path)
  class_name = extract_class_name(file_path, source)

  return nil unless class_name
  return nil unless job_file?(source)

  unit = ExtractedUnit.new(
    type: :job,
    identifier: class_name,
    file_path: file_path
  )

  unit.namespace = extract_namespace(class_name)
  unit.source_code = annotate_source(source, class_name)
  unit. = (source, class_name)
  unit.dependencies = extract_dependencies(source, class_name)

  unit
rescue StandardError => e
  Rails.logger.error("Failed to extract job #{file_path}: #{e.message}")
  nil
end