Class: ActiverecordCallbackLens::Resolver::MethodResolver

Inherits:
Object
  • Object
show all
Defined in:
lib/activerecord_callback_lens/resolver/method_resolver.rb

Overview

Resolves a callback’s MethodRefNode symbol (e.g. :sync_required?) into an expanded ConditionTree.

Given a model class and a method name, MethodResolver locates the method’s source via ‘instance_method(name).source_location`, parses the file with Prism, isolates the matching DefNode, and maps its body onto a ConditionTree with the shared Parser::AstWalker. Any MethodRefNode found in that tree is then resolved recursively, so a predicate that delegates to other predicates expands into a full boolean tree.

Two guards prevent runaway recursion:

- a `visited` Set closes direct (A -> A) and indirect (A -> B -> A) cycles
- a MAX_DEPTH cap bounds otherwise-acyclic but deep chains

Known limitation: a DefNode body is a StatementsNode and the resolver treats the last statement as the effective return value (the same assumption ConditionParser makes for lambda bodies). Methods with early returns or guard clauses are therefore only partially understood and are left unexpanded where the heuristic does not apply.

Defined Under Namespace

Classes: DefLocator

Constant Summary collapse

MAX_DEPTH =

Hard cap on recursion depth. A chain of method refs deeper than this is left unexpanded rather than followed further.

5

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model_class) ⇒ MethodResolver

Returns a new instance of MethodResolver.

Parameters:

  • model_class (Class)


55
56
57
# File 'lib/activerecord_callback_lens/resolver/method_resolver.rb', line 55

def initialize(model_class)
  @model_class = model_class
end

Class Method Details

.expand(definition, model_class) ⇒ Collector::CallbackDefinition

Per-definition expansion entry point. Walks a CallbackDefinition’s condition_tree, resolves every MethodRefNode against the model, and returns a new definition whose tree carries populated expanded_tree fields. Bridges the resolver core and the CLI/Rake integration.

Parameters:

Returns:



50
51
52
# File 'lib/activerecord_callback_lens/resolver/method_resolver.rb', line 50

def self.expand(definition, model_class)
  new(model_class).expand(definition)
end

.resolve(model_class, method_name) ⇒ Parser::ConditionTree::Node?

Returns the expanded tree, or nil when the method cannot be located/parsed.

Parameters:

  • model_class (Class)

    the ActiveRecord model the method is defined on

  • method_name (Symbol)

    the predicate/method to resolve

Returns:



38
39
40
# File 'lib/activerecord_callback_lens/resolver/method_resolver.rb', line 38

def self.resolve(model_class, method_name)
  new(model_class).resolve(method_name)
end

Instance Method Details

#expand(definition) ⇒ Collector::CallbackDefinition

Expands every MethodRefNode in the definition’s condition_tree.

A definition with no condition_tree (nil) is returned unchanged; so is a tree that contains no MethodRefNodes (expand_tree rebuilds it into a value-equal copy, leaving non-expansion output identical to v0.1).

Parameters:

Returns:



67
68
69
70
71
72
# File 'lib/activerecord_callback_lens/resolver/method_resolver.rb', line 67

def expand(definition)
  return definition unless definition.condition_tree

  expanded = expand_tree(definition.condition_tree)
  definition.with(condition_tree: expanded)
end

#resolve(method_name, depth: 0, visited: Set.new) ⇒ Parser::ConditionTree::Node?

Resolves a method name into an expanded ConditionTree.

Parameters:

  • method_name (Symbol)
  • depth (Integer) (defaults to: 0)

    current recursion depth (0 at the entry point)

  • visited (Set<Symbol>) (defaults to: Set.new)

    method names already on the current path

Returns:



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/activerecord_callback_lens/resolver/method_resolver.rb', line 80

def resolve(method_name, depth: 0, visited: Set.new)
  if depth >= MAX_DEPTH
    warn("[ActiverecordCallbackLens] Max resolution depth (#{MAX_DEPTH}) reached at #{method_name}")
    return nil
  end
  return nil if visited.include?(method_name)

  visited = visited.dup
  visited.add(method_name)

  node = locate_and_parse(method_name)
  return nil if node.nil?

  expand_refs(node, depth: depth + 1, visited: visited)
end