Class: Woods::Extractors::ConcernExtractor

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

Overview

ConcernExtractor handles ActiveSupport::Concern module extraction.

Concerns are mixins that extend model and controller behavior. They live in app/models/concerns/ and app/controllers/concerns/, as well as nested directories like app/models/gateway/stripe/concerns/.

We extract:

  • Module name and namespace
  • Included/extended hooks and class methods block
  • Instance methods and class methods added by the concern
  • Dependencies on models and other concerns

Examples:

extractor = ConcernExtractor.new
units = extractor.extract_all
searchable = units.find { |u| u.identifier == "Searchable" }

Constant Summary collapse

CONCERN_DIRECTORIES =

Canonical concern directories (used as fallback if glob finds nothing).

%w[
  app/models/concerns
  app/controllers/concerns
].freeze

Constants included from SharedDependencyScanner

SharedDependencyScanner::FORM_ACTION_HELPER, SharedDependencyScanner::ROUTE_HELPER_PATTERN

Instance Method Summary collapse

Methods included from SharedDependencyScanner

#consolidate_dependencies, #extract_constantize_targets, #scan_common_dependencies, #scan_form_dependencies, #scan_job_dependencies, #scan_mailer_dependencies, #scan_model_dependencies, #scan_navigation_dependencies, #scan_service_dependencies, #strip_line_comment, #strip_ruby_line_comments

Methods included from SharedUtilityMethods

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

Constructor Details

#initializeConcernExtractor

Returns a new instance of ConcernExtractor.



35
36
37
38
39
40
41
42
43
44
45
# File 'lib/woods/extractors/concern_extractor.rb', line 35

def initialize
  # Discover all concerns/ directories under app/, including deeply nested ones
  # like app/models/gateway/stripe/webhook/concerns/.
  @directories = Dir[Rails.root.join('app/**/concerns')].map { |d| Pathname.new(d) }
                                                        .select(&:directory?)
  # Fall back to canonical directories if glob finds nothing.
  return unless @directories.empty?

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

Instance Method Details

#extract_allArray<ExtractedUnit>

Extract all concern modules

Returns:



50
51
52
53
54
# File 'lib/woods/extractors/concern_extractor.rb', line 50

def extract_all
  find_files_in_directories(@directories).filter_map do |file|
    extract_concern_file(file)
  end
end

#extract_concern_file(file_path) ⇒ ExtractedUnit?

Extract a single concern file

Parameters:

  • file_path (String)

    Path to the concern file

Returns:

  • (ExtractedUnit, nil)

    The extracted unit or nil if not a concern



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/woods/extractors/concern_extractor.rb', line 60

def extract_concern_file(file_path)
  source = File.read(file_path)
  module_name = extract_module_name(file_path, source)

  return nil unless module_name
  return nil unless concern_module?(source)

  unit = ExtractedUnit.new(
    type: :concern,
    identifier: module_name,
    file_path: file_path
  )

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

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