Class: Woods::Extractors::GraphQLExtractor

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

Overview

GraphQLExtractor handles graphql-ruby type and mutation extraction.

GraphQL schemas are rich in structure — types, fields, arguments, resolvers, and mutations form a typed API layer over the domain. We extract these with runtime introspection when available (via ‘GraphQL::Schema.types`) and fall back to file-based discovery when the schema isn’t fully loadable.

We extract:

  • Object types, input types, enum types, interface types, union types, scalar types

  • Mutations and their arguments/return fields

  • Query fields and resolvers

  • Standalone resolver classes

  • Field-level metadata (types, descriptions, complexity, arguments)

  • Authorization patterns (authorized?, pundit, cancan)

  • Dependencies on models, services, jobs, and other GraphQL types

Examples:

extractor = GraphQLExtractor.new
units = extractor.extract_all
user_type = units.find { |u| u.identifier == "Types::UserType" }

Constant Summary collapse

GRAPHQL_DIRECTORY =

Standard directory for graphql-ruby applications

'app/graphql'
CHUNK_THRESHOLD =

Token threshold for chunking large types

1500

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_public_methods, #resolve_source_location, #skip_file?

Constructor Details

#initializeGraphQLExtractor

Returns a new instance of GraphQLExtractor.



40
41
42
43
44
# File 'lib/woods/extractors/graphql_extractor.rb', line 40

def initialize
  @graphql_dir = defined?(Rails) ? Rails.root.join(GRAPHQL_DIRECTORY) : nil
  @schema_class = find_schema_class
  @runtime_types = load_runtime_types
end

Instance Method Details

#extract_allArray<ExtractedUnit>

Extract all GraphQL types, mutations, queries, and resolvers

Returns an empty array if graphql-ruby is not installed or no GraphQL files are found.

Returns:



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/woods/extractors/graphql_extractor.rb', line 52

def extract_all
  return [] unless graphql_available?

  units = []
  seen_identifiers = Set.new

  # First pass: runtime introspection (most accurate)
  if @runtime_types.any?
    @runtime_types.each_value do |type_class|
      unit = extract_from_runtime_type(type_class)
      next unless unit
      next if seen_identifiers.include?(unit.identifier)

      seen_identifiers << unit.identifier
      units << unit
    end
  end

  # Second pass: file-based discovery (catches everything)
  if @graphql_dir&.directory?
    Dir[@graphql_dir.join('**/*.rb')].each do |file_path|
      unit = extract_graphql_file(file_path)
      next unless unit
      next if seen_identifiers.include?(unit.identifier)

      seen_identifiers << unit.identifier
      units << unit
    end
  end

  units.compact
end

#extract_graphql_file(file_path) ⇒ ExtractedUnit?

Extract a single GraphQL file

Parameters:

  • file_path (String)

    Absolute path to a .rb file in app/graphql/

Returns:

  • (ExtractedUnit, nil)

    The extracted unit, or nil if the file does not contain a recognizable GraphQL class



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/woods/extractors/graphql_extractor.rb', line 90

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

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

  unit_type = classify_unit_type(file_path, source)
  runtime_class = class_name.safe_constantize

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

  unit.namespace = extract_namespace(class_name)
  unit.source_code = build_annotated_source(source, class_name, unit_type, runtime_class)
  unit. = (source, class_name, unit_type, runtime_class)
  unit.dependencies = extract_dependencies(source, class_name)
  unit.chunks = build_chunks(unit, runtime_class) if unit.needs_chunking?(threshold: CHUNK_THRESHOLD)

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