Module: ActiveCypher::Fixtures

Defined in:
lib/active_cypher/fixtures.rb,
lib/active_cypher/fixtures/parser.rb,
lib/active_cypher/fixtures/registry.rb,
lib/active_cypher/fixtures/evaluator.rb,
lib/active_cypher/fixtures/dsl_context.rb,
lib/active_cypher/fixtures/rel_builder.rb,
lib/active_cypher/fixtures/node_builder.rb

Defined Under Namespace

Classes: DSLContext, Evaluator, FixtureError, FixtureNotFoundError, NodeBuilder, Parser, Registry, RelBuilder

Class Method Summary collapse

Class Method Details

.[](ref) ⇒ Object

Fetch a node by logical ref.

Parameters:

  • ref (Symbol, String)

Returns:

  • (Object)


161
162
163
# File 'lib/active_cypher/fixtures.rb', line 161

def self.[](ref)
  Registry[ref]
end

.clear_allvoid

This method returns an undefined value.

Clear all nodes in all known connections.



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/active_cypher/fixtures.rb', line 66

def self.clear_all
  # Find all concrete (non-abstract) model classes inheriting from ActiveCypher::Base
  model_classes = []
  ObjectSpace.each_object(Class) do |klass|
    next unless klass < ActiveCypher::Base
    next if klass.respond_to?(:abstract_class?) && klass.abstract_class?

    model_classes << klass
  end

  # Gather unique connections from all model classes
  connections = model_classes.map(&:connection).compact.uniq

  # Wipe all nodes in each connection
  connections.each do |conn|
    conn.execute_cypher('MATCH (n) DETACH DELETE n')
  rescue StandardError => e
    warn "[ActiveCypher::Fixtures.clear_all] Failed to clear connection #{conn.inspect}: #{e.class}: #{e.message}"
  end
  true
end

.load(profile: nil) ⇒ void

This method returns an undefined value.

Load a graph fixture profile.

Parameters:

  • profile (Symbol, String, nil) (defaults to: nil)

    the profile name (default: :default)

Raises:



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/active_cypher/fixtures.rb', line 16

def self.load(profile: nil)
  # 1. Resolve file
  profile_name = (profile || :default).to_s
  fixtures_dir = File.expand_path('test/fixtures/graph', Dir.pwd)
  file = File.join(fixtures_dir, "#{profile_name}.rb")
  raise FixtureNotFoundError, "Fixture profile not found: #{profile_name} (#{file})" unless File.exist?(file)

  # 2. Reset registry
  Registry.reset!

  # 3. Parse the profile file (to discover which models are referenced)
  parser = Parser.new(file)
  dsl_context = parser.parse

  # 4. Validate relationships upfront (cross-DB)
  validate_relationships(dsl_context.relationships)

  # 5. Gather unique connections for all model classes referenced in this profile
  model_classes = dsl_context.nodes.map { |node| node[:model_class] }.uniq
  connections = model_classes.map(&:connection).compact.uniq

  # 6. Wipe all nodes in each relevant connection
  connections.each do |conn|
    conn.execute_cypher('MATCH (n) DETACH DELETE n')
  rescue StandardError => e
    warn "[ActiveCypher::Fixtures.load] Failed to clear connection #{conn.inspect}: #{e.class}: #{e.message}"
  end

  # 7. Evaluate nodes and relationships (batched if large)
  if dsl_context.nodes.size > 100 || dsl_context.relationships.size > 200
    NodeBuilder.bulk_build(dsl_context.nodes)
    # Create all nodes first, then validate relationships again with populated Registry
    validate_relationships(dsl_context.relationships)
    RelBuilder.bulk_build(dsl_context.relationships)
  else
    dsl_context.nodes.each do |node|
      NodeBuilder.build(node[:ref], node[:model_class], node[:props])
    end
    rel_builder = RelBuilder.new
    dsl_context.relationships.each do |rel|
      rel_builder.build(rel[:ref], rel[:from_ref], rel[:type], rel[:to_ref], rel[:props])
    end
  end

  # 8. Return registry for convenience
  Registry
end

.validate_relationships(relationships) ⇒ Object

Validates relationships for cross-DB issues

Parameters:

  • relationships (Array<Hash>)

    array of relationship definitions

Raises:



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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/active_cypher/fixtures.rb', line 91

def self.validate_relationships(relationships)
  model_connections = {}

  # First build a mapping of model class => connection details
  ObjectSpace.each_object(Class) do |klass|
    next unless klass < ActiveCypher::Base
    next if klass.respond_to?(:abstract_class?) && klass.abstract_class?

    conn = klass.connection
    # Store connection details for comparison
    model_connections[klass] = {
      adapter: conn.class.name,
      config: conn.instance_variable_get(:@config),
      object_id: conn.object_id
    }
  end

  relationships.each do |rel|
    from_ref = rel[:from_ref]
    to_ref = rel[:to_ref]

    # Get node classes from DSL context
    # In real data, nodes have already been created by this point
    from_node = Registry.get(from_ref)
    to_node = Registry.get(to_ref)

    # Skip if we can't find both nodes yet (will be caught later)
    next unless from_node && to_node

    from_class = from_node.class
    to_class = to_node.class

    # Look up connection details for each class
    from_conn_details = model_connections[from_class]
    to_conn_details = model_connections[to_class]

    # If either class isn't in our mapping, refresh it
    unless from_conn_details
      conn = from_class.connection
      from_conn_details = {
        adapter: conn.class.name,
        config: conn.instance_variable_get(:@config),
        object_id: conn.object_id
      }
      model_connections[from_class] = from_conn_details
    end

    unless to_conn_details
      conn = to_class.connection
      to_conn_details = {
        adapter: conn.class.name,
        config: conn.instance_variable_get(:@config),
        object_id: conn.object_id
      }
      model_connections[to_class] = to_conn_details
    end

    # Compare connection details
    next unless from_conn_details[:object_id] != to_conn_details[:object_id] ||
                from_conn_details[:adapter] != to_conn_details[:adapter] ||
                from_conn_details[:config][:database] != to_conn_details[:config][:database]

    raise FixtureError, 'Cross-database relationship? Sorry, your data has commitment issues. ' \
                        "Nodes #{from_ref} (#{from_class}) and #{to_ref} (#{to_class}) use different databases."
  end
end