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)


158
159
160
# File 'lib/active_cypher/fixtures.rb', line 158

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

.clear_allvoid

This method returns an undefined value.

Clear all nodes in all known connections.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/active_cypher/fixtures.rb', line 62

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
  wipe_connections(connections, 'clear_all')
  true
end

.conn_details_for(klass, cache) ⇒ Hash

Memoized connection-details lookup for a model class.

Parameters:

  • klass (Class)

    the model class

  • cache (Hash)

    class => details mapping, populated on miss

Returns:

  • (Hash)

    the connection details



95
96
97
# File 'lib/active_cypher/fixtures.rb', line 95

def self.conn_details_for(klass, cache)
  cache[klass] ||= connection_details(klass.connection)
end

.connection_details(conn) ⇒ Hash

Build a comparable connection fingerprint for cross-database detection.

Parameters:

  • conn (Object)

    a model connection

Returns:

  • (Hash)

    adapter/config/object_id details



83
84
85
86
87
88
89
# File 'lib/active_cypher/fixtures.rb', line 83

def self.connection_details(conn)
  {
    adapter: conn.class.name,
    config: conn.instance_variable_get(:@config),
    object_id: conn.object_id
  }
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
# 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
  wipe_connections(connections, 'load')

  # 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:



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
# File 'lib/active_cypher/fixtures.rb', line 114

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?

    # Store connection details for comparison
    model_connections[klass] = connection_details(klass.connection)
  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, refreshing the cache on miss
    from_conn_details = conn_details_for(from_class, model_connections)
    to_conn_details = conn_details_for(to_class, model_connections)

    # 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

.wipe_connections(connections, context) ⇒ void

This method returns an undefined value.

Detach-delete every node across the given connections, logging per-connection failures.

Parameters:

  • connections (Array)

    connections to wipe

  • context (String)

    caller name used in the warning prefix



103
104
105
106
107
108
109
# File 'lib/active_cypher/fixtures.rb', line 103

def self.wipe_connections(connections, context)
  connections.each do |conn|
    conn.execute_cypher('MATCH (n) DETACH DELETE n')
  rescue StandardError => e
    warn "[ActiveCypher::Fixtures.#{context}] Failed to clear connection #{conn.inspect}: #{e.class}: #{e.message}"
  end
end