activecypher 0.3.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/lib/active_cypher/base.rb +28 -8
  3. data/lib/active_cypher/bolt/driver.rb +6 -16
  4. data/lib/active_cypher/bolt/session.rb +62 -50
  5. data/lib/active_cypher/bolt/transaction.rb +95 -90
  6. data/lib/active_cypher/connection_adapters/abstract_adapter.rb +28 -0
  7. data/lib/active_cypher/connection_adapters/abstract_bolt_adapter.rb +40 -32
  8. data/lib/active_cypher/connection_adapters/memgraph_adapter.rb +44 -0
  9. data/lib/active_cypher/connection_adapters/neo4j_adapter.rb +27 -1
  10. data/lib/active_cypher/connection_adapters/registry.rb +94 -0
  11. data/lib/active_cypher/connection_handler.rb +18 -3
  12. data/lib/active_cypher/connection_pool.rb +5 -23
  13. data/lib/active_cypher/connection_url_resolver.rb +14 -3
  14. data/lib/active_cypher/cypher_config.rb +2 -1
  15. data/lib/active_cypher/fixtures/dsl_context.rb +41 -0
  16. data/lib/active_cypher/fixtures/evaluator.rb +37 -0
  17. data/lib/active_cypher/fixtures/node_builder.rb +53 -0
  18. data/lib/active_cypher/fixtures/parser.rb +23 -0
  19. data/lib/active_cypher/fixtures/registry.rb +43 -0
  20. data/lib/active_cypher/fixtures/rel_builder.rb +96 -0
  21. data/lib/active_cypher/fixtures.rb +177 -0
  22. data/lib/active_cypher/generators/node_generator.rb +32 -3
  23. data/lib/active_cypher/generators/relationship_generator.rb +29 -2
  24. data/lib/active_cypher/generators/templates/application_graph_node.rb +1 -0
  25. data/lib/active_cypher/generators/templates/node.rb.erb +10 -6
  26. data/lib/active_cypher/generators/templates/relationship.rb.erb +7 -6
  27. data/lib/active_cypher/instrumentation.rb +186 -0
  28. data/lib/active_cypher/model/callbacks.rb +5 -13
  29. data/lib/active_cypher/model/connection_handling.rb +37 -52
  30. data/lib/active_cypher/model/connection_owner.rb +41 -33
  31. data/lib/active_cypher/model/core.rb +4 -12
  32. data/lib/active_cypher/model/countable.rb +10 -3
  33. data/lib/active_cypher/model/destruction.rb +23 -18
  34. data/lib/active_cypher/model/labelling.rb +45 -0
  35. data/lib/active_cypher/model/persistence.rb +52 -26
  36. data/lib/active_cypher/model/querying.rb +49 -25
  37. data/lib/active_cypher/railtie.rb +40 -5
  38. data/lib/active_cypher/relation.rb +10 -2
  39. data/lib/active_cypher/relationship.rb +77 -17
  40. data/lib/active_cypher/version.rb +1 -1
  41. data/lib/activecypher.rb +4 -1
  42. data/lib/cyrel/clause/set.rb +20 -10
  43. data/lib/cyrel/expression/property_access.rb +2 -0
  44. data/lib/cyrel/functions.rb +3 -1
  45. data/lib/cyrel/logging.rb +43 -0
  46. data/lib/cyrel/plus.rb +11 -0
  47. data/lib/cyrel/query.rb +7 -1
  48. data/lib/cyrel.rb +77 -18
  49. metadata +13 -2
  50. data/lib/active_cypher/connection_factory.rb +0 -130
@@ -47,7 +47,7 @@ module ActiveCypher
47
47
  # --------------------------------------------------------------
48
48
  # Attributes
49
49
  # --------------------------------------------------------------
50
- attribute :internal_id, :string
50
+ attribute :internal_id, :integer
51
51
 
52
52
  # --------------------------------------------------------------
53
53
  # Connection fallback
@@ -59,6 +59,11 @@ module ActiveCypher
59
59
  # WorksAtRelationship.connection # -> PersonNode.connection
60
60
  #
61
61
  def self.connection
62
+ # If a node_base_class is set (directly or by convention), always delegate to its connection
63
+ if (klass = node_base_class)
64
+ return klass.connection
65
+ end
66
+
62
67
  return @connection if defined?(@connection) && @connection
63
68
 
64
69
  begin
@@ -74,10 +79,51 @@ module ActiveCypher
74
79
  class_attribute :_from_class_name, instance_writer: false
75
80
  class_attribute :_to_class_name, instance_writer: false
76
81
  class_attribute :_relationship_type, instance_writer: false
82
+ class_attribute :_node_base_class, instance_writer: false
77
83
 
78
84
  class << self
79
85
  attr_reader :last_internal_id
80
86
 
87
+ # DSL for setting or getting the node base class for connection delegation
88
+ def node_base_class(klass = nil)
89
+ if klass.nil?
90
+ # If not set, try convention: XxxRelationship -> XxxNode
91
+ return _node_base_class if _node_base_class
92
+
93
+ if name&.end_with?('Relationship')
94
+ node_base_name = name.sub(/Relationship\z/, 'Node')
95
+ begin
96
+ node_base_klass = node_base_name.constantize
97
+ if node_base_klass.respond_to?(:abstract_class?) && node_base_klass.abstract_class?
98
+ self._node_base_class = node_base_klass
99
+ return node_base_klass
100
+ end
101
+ rescue NameError
102
+ # Do nothing, fallback to nil
103
+ end
104
+ end
105
+ return _node_base_class
106
+ end
107
+ # Only allow setting on abstract relationship base classes
108
+ raise "Cannot set node_base_class on non-abstract relationship class #{name}" unless abstract_class?
109
+ unless klass.respond_to?(:abstract_class?) && klass.abstract_class?
110
+ raise ArgumentError, "node_base_class must be an abstract node base class (got #{klass})"
111
+ end
112
+
113
+ self._node_base_class = klass
114
+ end
115
+
116
+ # Prevent subclasses from overriding node_base_class
117
+ def inherited(subclass)
118
+ super
119
+ return unless _node_base_class
120
+
121
+ subclass._node_base_class = _node_base_class
122
+ def subclass.node_base_class(*)
123
+ raise "Cannot override node_base_class in subclass #{name}; it is locked to #{_node_base_class}"
124
+ end
125
+ end
126
+
81
127
  # -- endpoints ------------------------------------------------
82
128
  def from_class(value = nil)
83
129
  return _from_class_name if value.nil?
@@ -160,8 +206,10 @@ module ActiveCypher
160
206
  raise 'Cannot destroy a new relationship' if new_record?
161
207
  raise 'Relationship already destroyed' if destroyed?
162
208
 
163
- cypher = 'MATCH ()-[r]-() WHERE elementId(r) = $id DELETE r'
164
- params = { id: internal_id }
209
+ adapter = self.class.connection.id_handler
210
+
211
+ cypher = "MATCH ()-[r]-() WHERE #{adapter.with_direct_id(internal_id)} DELETE r"
212
+ params = {}
165
213
 
166
214
  self.class.connection.execute_cypher(cypher, params, 'Destroy Relationship')
167
215
  @destroyed = true
@@ -186,22 +234,31 @@ module ActiveCypher
186
234
  rel_ty = self.class.relationship_type
187
235
  arrow = '->' # outgoing by default
188
236
 
189
- parts = []
190
- parts << 'MATCH (a) WHERE elementId(a) = $from_id'
191
- parts << 'MATCH (b) WHERE elementId(b) = $to_id'
192
- parts << "CREATE (a)-[r:#{rel_ty}]#{arrow}(b)"
237
+ adapter = self.class.connection.id_handler
238
+ parts = []
239
+
240
+ # Build the Cypher query based on the adapter
241
+ id_clause = adapter.with_direct_node_ids(from_node.internal_id, to_node.internal_id)
242
+ parts << "MATCH (p), (h) WHERE #{id_clause}"
243
+ parts << "CREATE (p)-[r:#{rel_ty}]#{arrow}(h)"
193
244
  parts << 'SET r += $props' unless props.empty? # only if we have props
194
- parts << 'RETURN elementId(r) AS rid'
245
+ parts << "RETURN #{adapter.return_id}"
195
246
 
196
247
  cypher = parts.join(' ')
197
- params = {
198
- from_id: from_node.internal_id,
199
- to_id: to_node.internal_id,
200
- props: props
201
- }
248
+ params = { props: props }
249
+
250
+ # Execute Cypher query
251
+ result = self.class.connection.execute_cypher(cypher, params, 'Create Relationship')
252
+
253
+ row = result.first
202
254
 
203
- row = self.class.connection.execute_cypher(cypher, params, 'Create Relationship').first
204
- rid = row && (row[:rid] || row['rid']) or raise 'Relationship creation returned no id'
255
+ # Try different ways to access the ID
256
+ rid_sym = row && row[:rid]
257
+ rid_str = row && row['rid']
258
+
259
+ rid = rid_sym || rid_str
260
+
261
+ raise 'Relationship creation returned no id' if rid.nil?
205
262
 
206
263
  self.internal_id = rid
207
264
  self.class.instance_variable_set(:@last_internal_id, rid)
@@ -217,11 +274,14 @@ module ActiveCypher
217
274
  changes = changes_to_save
218
275
  return true if changes.empty?
219
276
 
277
+ adapter = self.class.connection.id_handler
278
+
220
279
  cypher = <<~CYPHER
221
- MATCH ()-[r]-() WHERE elementId(r) = $id
280
+ MATCH ()-[r]-() WHERE #{adapter.with_direct_id(internal_id)}
222
281
  SET r += $props
223
282
  CYPHER
224
- params = { id: internal_id, props: changes }
283
+
284
+ params = { props: changes }
225
285
 
226
286
  self.class.connection.execute_cypher(cypher, params, 'Update Relationship')
227
287
  changes_applied
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveCypher
4
- VERSION = '0.3.0'
4
+ VERSION = '0.6.0'
5
5
  end
data/lib/activecypher.rb CHANGED
@@ -101,7 +101,10 @@ loader.ignore("#{__dir__}/active_cypher/railtie.rb")
101
101
  loader.ignore("#{__dir__}/active_cypher/generators")
102
102
  loader.ignore("#{__dir__}/activecypher.rb")
103
103
  loader.ignore("#{__dir__}/cyrel.rb")
104
- loader.inflector.inflect('activecypher' => 'ActiveCypher')
104
+ loader.inflector.inflect(
105
+ 'activecypher' => 'ActiveCypher',
106
+ 'dsl_context' => 'DSLContext'
107
+ )
105
108
  loader.push_dir("#{__dir__}/cyrel", namespace: Cyrel)
106
109
  loader.setup
107
110
 
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'cyrel/expression'
4
+ require 'cyrel/expression/property_access'
5
+
3
6
  module Cyrel
4
7
  module Clause
5
8
  # Represents a SET clause in a Cypher query.
@@ -12,6 +15,7 @@ module Cyrel
12
15
  # - Hash: { variable_or_prop_access => value_expression, ... }
13
16
  # e.g., { Cyrel.prop(:n, :name) => "New Name", Cyrel.prop(:r, :weight) => 10 }
14
17
  # e.g., { n: { name: "New Name", age: 30 } } # For SET n = properties or n += properties
18
+ # e.g., { Cyrel.plus(:n) => { name: "New Name" } } # For SET n += { name: ... }
15
19
  # - Array: [[variable, label_string], ...] # For SET n:Label
16
20
  # e.g., [[:n, "NewLabel"], [:m, "AnotherLabel"]]
17
21
  # Note: Mixing hash and array styles in one call is not directly supported, use multiple SET clauses if needed.
@@ -47,17 +51,20 @@ module Cyrel
47
51
  case assignments
48
52
  when Hash
49
53
  assignments.flat_map do |key, value|
50
- if key.is_a?(Expression::PropertyAccess)
54
+ case key
55
+ when Expression::PropertyAccess
51
56
  # SET n.prop = value
52
57
  [[:property, key, Expression.coerce(value)]]
53
- elsif key.is_a?(Symbol) || key.is_a?(String)
54
- # SET n = properties or SET n += properties
55
- # We need to decide which operator (= or +=). Defaulting to = for now.
56
- # User might need to specify via a different method/option.
57
- # Let's assume the value is a hash for this case.
58
+ when Symbol, String
59
+ # SET n = properties
58
60
  raise ArgumentError, 'Value for variable assignment must be a Hash (for SET n = {props})' unless value.is_a?(Hash)
59
61
 
60
- [[:variable_properties, key.to_sym, Expression.coerce(value)]]
62
+ [[:variable_properties, key.to_sym, Expression.coerce(value), :assign]]
63
+ when Cyrel::Plus
64
+ # SET n += properties
65
+ raise ArgumentError, 'Value for variable assignment must be a Hash (for SET n += {props})' unless value.is_a?(Hash)
66
+
67
+ [[:variable_properties, key.variable.to_sym, Expression.coerce(value), :merge]]
61
68
  else
62
69
  raise ArgumentError, "Invalid key type in SET assignments hash: #{key.class}"
63
70
  end
@@ -78,15 +85,18 @@ module Cyrel
78
85
  end
79
86
 
80
87
  def render_assignment(assignment, query)
81
- type, target, value = assignment
88
+ type, target, value, op = assignment
82
89
  case type
83
90
  when :property
84
91
  # target is PropertyAccess, value is Expression
85
92
  "#{target.render(query)} = #{value.render(query)}"
86
93
  when :variable_properties
87
94
  # target is variable symbol, value is Expression (Literal Hash)
88
- # Using '=' operator here. Could add support for '+=' later.
89
- "#{target} = #{value.render(query)}"
95
+ if op == :merge
96
+ "#{target} += #{value.render(query)}"
97
+ else
98
+ "#{target} = #{value.render(query)}"
99
+ end
90
100
  when :label
91
101
  # target is variable symbol, value is label string
92
102
  "#{target}:#{value}" # Labels are not parameterized
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'cyrel/expression/base'
4
+
3
5
  module Cyrel
4
6
  module Expression
5
7
  # Represents accessing a property on a variable (node or relationship alias).
@@ -22,7 +22,9 @@ module Cyrel
22
22
  Expression::FunctionCall.new(:elementId, Clause::Return::RawIdentifier.new(node_variable.to_s))
23
23
  end
24
24
 
25
- alias id element_id
25
+ def id(node_variable)
26
+ Expression::FunctionCall.new(:id, Clause::Return::RawIdentifier.new(node_variable.to_s))
27
+ end
26
28
 
27
29
  # Because apparently, COUNT(*) isn’t obvious enough.
28
30
  # Handles the 'give me everything and make it snappy' use case.
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+ require 'active_support/tagged_logging'
5
+
6
+ module Cyrel
7
+ # Basic logging support for Cyrel.
8
+ # Debug logging is disabled by default.
9
+ module Logging
10
+ LOG_TAG = 'Cyrel'
11
+
12
+ class << self
13
+ # @return [Logger] the configured logger
14
+ attr_accessor :backend
15
+
16
+ def resolve_log_level(log_level_str)
17
+ Logger.const_get(log_level_str.upcase)
18
+ rescue StandardError
19
+ Logger::UNKNOWN
20
+ end
21
+
22
+ def logger
23
+ self.backend ||= begin
24
+ log_level = ENV.fetch('CYREL_LOG_LEVEL', 'unknown')
25
+ logger_base = Logger.new($stdout)
26
+ logger_base.level = resolve_log_level(log_level)
27
+
28
+ # Return a TaggedLogging instance without calling 'tagged!'
29
+ ActiveSupport::TaggedLogging.new(logger_base)
30
+ end
31
+ end
32
+ end
33
+
34
+ def logger
35
+ Logging.logger.tagged(LOG_TAG)
36
+ end
37
+
38
+ def log_debug(msg) = logger.debug { msg }
39
+ def log_info(msg) = logger.info { msg }
40
+ def log_warn(msg) = logger.warn { msg }
41
+ def log_error(msg) = logger.error { msg }
42
+ end
43
+ end
data/lib/cyrel/plus.rb ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cyrel
4
+ class Plus
5
+ attr_reader :variable
6
+
7
+ def initialize(variable)
8
+ @variable = variable
9
+ end
10
+ end
11
+ end
data/lib/cyrel/query.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Require necessary clause and pattern types
3
+ # Base DSL components
4
+ require 'cyrel/parameterizable'
5
+ require 'cyrel/logging'
4
6
 
5
7
  # Require all clause types for DSL methods
6
8
 
@@ -18,6 +20,7 @@ module Cyrel
18
20
  # # Manages clauses, parameters, and final query generation, because string interpolation is for amateurs.
19
21
  class Query
20
22
  include Parameterizable
23
+ include Logging
21
24
  attr_reader :parameters, :clauses # Expose clauses for merge logic
22
25
 
23
26
  def initialize
@@ -58,6 +61,9 @@ module Cyrel
58
61
  .reject(&:blank?)
59
62
  .join("\n")
60
63
 
64
+ log_debug("QUERY: #{cypher_string}")
65
+ log_debug("PARAMS: #{@parameters.inspect}") unless @parameters.empty?
66
+
61
67
  [cypher_string, @parameters]
62
68
  end
63
69
  end
data/lib/cyrel.rb CHANGED
@@ -1,72 +1,131 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'irb' # Required for binding.irb
4
-
5
3
  module Cyrel
6
4
  module_function
7
5
 
8
- # Define all top-level helpers as instance methods first
6
+ # Cyrel DSL helper: creates a CALL clause for a procedure.
7
+ # Example: Cyrel.call('db.labels')
9
8
  def call(procedure)
10
9
  CallProcedure.new(procedure)
11
10
  end
12
11
 
12
+ # Cyrel DSL helper: creates a RETURN clause.
13
+ # Example: Cyrel.return(name: :n)
13
14
  def return(**return_values)
14
15
  ReturnOnly.new(return_values)
15
16
  end
16
- # Now make all defined instance methods module functions
17
17
 
18
- # --- Pattern Helpers ---
18
+ # Cyrel DSL helper: creates a node pattern.
19
+ # Example: Cyrel.node(:n, labels: ['Person'], properties: {name: 'Alice'})
19
20
  def node(alias_name, labels: [], properties: {})
20
21
  Pattern::Node.new(alias_name, labels: labels, properties: properties)
21
22
  end
22
- # Add helpers for Relationship, Path if needed
23
23
 
24
- # --- Query Building Starters ---
24
+ # Cyrel DSL helper: starts a CREATE query.
25
+ # Example: Cyrel.create(pattern)
25
26
  def create(pattern)
26
- Query.new.create(pattern) # Start a new query and call create
27
+ Query.new.create(pattern)
27
28
  end
28
29
 
30
+ # Cyrel DSL helper: starts a MATCH query.
31
+ # Example: Cyrel.match(pattern)
29
32
  def match(pattern, path_variable: nil)
30
- Query.new.match(pattern, path_variable: path_variable) # Start a new query and call match
33
+ Query.new.match(pattern, path_variable: path_variable)
31
34
  end
32
- # Add helpers for merge etc. if desired as query starters
33
35
 
34
- # --- Function Helpers (Delegated) ---
35
- # Keep id for now for compatibility? Or remove entirely? Let's keep it but delegate element_id too.
36
- # Delegate to the correct module function
36
+ # Cyrel DSL helper: returns the element id of a node/relationship.
37
+ # Example: Cyrel.id(:n)
37
38
  def id(...) = Functions.element_id(...)
39
+
40
+ # Cyrel DSL helper: returns the element id of a node/relationship (alias).
38
41
  def element_id(...) = Functions.element_id(...)
42
+
43
+ # Cyrel DSL helper: Cypher count() aggregation.
44
+ # Example: Cyrel.count(:n)
39
45
  def count(...) = Functions.count(...)
46
+
47
+ # Cyrel DSL helper: Cypher labels() function.
48
+ # Example: Cyrel.labels(:n)
40
49
  def labels(...) = Functions.labels(...)
50
+
51
+ # Cyrel DSL helper: Cypher type() function.
52
+ # Example: Cyrel.type(:r)
41
53
  def type(...) = Functions.type(...)
54
+
55
+ # Cyrel DSL helper: Cypher properties() function.
56
+ # Example: Cyrel.properties(:n)
42
57
  def properties(...) = Functions.properties(...)
58
+
59
+ # Cyrel DSL helper: Cypher coalesce() function.
60
+ # Example: Cyrel.coalesce(:a, :b)
43
61
  def coalesce(...) = Functions.coalesce(...)
62
+
63
+ # Cyrel DSL helper: Cypher timestamp() function.
64
+ # Example: Cyrel.timestamp
44
65
  def timestamp(...) = Functions.timestamp(...)
66
+
67
+ # Cyrel DSL helper: Cypher toString() function.
68
+ # Example: Cyrel.to_string(:n)
45
69
  def to_string(...) = Functions.to_string(...)
70
+
71
+ # Cyrel DSL helper: Cypher toInteger() function.
72
+ # Example: Cyrel.to_integer(:n)
46
73
  def to_integer(...) = Functions.to_integer(...)
74
+
75
+ # Cyrel DSL helper: Cypher toFloat() function.
76
+ # Example: Cyrel.to_float(:n)
47
77
  def to_float(...) = Functions.to_float(...)
78
+
79
+ # Cyrel DSL helper: Cypher toBoolean() function.
80
+ # Example: Cyrel.to_boolean(:n)
48
81
  def to_boolean(...) = Functions.to_boolean(...)
82
+
83
+ # Cyrel DSL helper: Cypher sum() aggregation.
84
+ # Example: Cyrel.sum(:n)
49
85
  def sum(...) = Functions.sum(...)
86
+
87
+ # Cyrel DSL helper: Cypher avg() aggregation.
88
+ # Example: Cyrel.avg(:n)
50
89
  def avg(...) = Functions.avg(...)
90
+
91
+ # Cyrel DSL helper: Cypher min() aggregation.
92
+ # Example: Cyrel.min(:n)
51
93
  def min(...) = Functions.min(...)
94
+
95
+ # Cyrel DSL helper: Cypher max() aggregation.
96
+ # Example: Cyrel.max(:n)
52
97
  def max(...) = Functions.max(...)
98
+
99
+ # Cyrel DSL helper: Cypher collect() aggregation.
100
+ # Example: Cyrel.collect(:n)
53
101
  def collect(...) = Functions.collect(...)
54
- def size(...) = Functions.size(...)
55
102
 
56
- # --- Expression Helpers (Delegated) ---
103
+ # Cyrel DSL helper: Cypher size() function.
104
+ # Example: Cyrel.size(:n)
105
+ def size(...) = Functions.size(...)
57
106
 
58
- # Helper for creating PropertyAccess expressions.
107
+ # Cyrel DSL helper: creates a PropertyAccess expression.
108
+ # Example: Cyrel.prop(:n, :name)
59
109
  def prop(variable, property_name)
60
110
  Expression.prop(variable, property_name)
61
111
  end
62
112
 
63
- # Helper for creating Exists expressions.
113
+ # Cyrel DSL helper: creates an Exists expression.
114
+ # Example: Cyrel.exists(pattern)
64
115
  def exists(pattern)
65
116
  Expression.exists(pattern)
66
117
  end
67
118
 
68
- # Helper for creating Logical NOT expressions.
119
+ # Cyrel DSL helper: creates a Logical NOT expression.
120
+ # Example: Cyrel.not(expression)
69
121
  def not(expression)
70
122
  Expression.not(expression)
71
123
  end
124
+
125
+ # Cyrel DSL helper: property merging (SET n += {props}).
126
+ # Use for updating only specified properties on a node or relationship.
127
+ # Example: Cyrel.plus(:n) => { name: "Alice" } generates SET n += $p1
128
+ def plus(variable)
129
+ Plus.new(variable)
130
+ end
72
131
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activecypher
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Abdelkader Boudih
@@ -119,11 +119,18 @@ files:
119
119
  - lib/active_cypher/connection_adapters/abstract_bolt_adapter.rb
120
120
  - lib/active_cypher/connection_adapters/memgraph_adapter.rb
121
121
  - lib/active_cypher/connection_adapters/neo4j_adapter.rb
122
- - lib/active_cypher/connection_factory.rb
122
+ - lib/active_cypher/connection_adapters/registry.rb
123
123
  - lib/active_cypher/connection_handler.rb
124
124
  - lib/active_cypher/connection_pool.rb
125
125
  - lib/active_cypher/connection_url_resolver.rb
126
126
  - lib/active_cypher/cypher_config.rb
127
+ - lib/active_cypher/fixtures.rb
128
+ - lib/active_cypher/fixtures/dsl_context.rb
129
+ - lib/active_cypher/fixtures/evaluator.rb
130
+ - lib/active_cypher/fixtures/node_builder.rb
131
+ - lib/active_cypher/fixtures/parser.rb
132
+ - lib/active_cypher/fixtures/registry.rb
133
+ - lib/active_cypher/fixtures/rel_builder.rb
127
134
  - lib/active_cypher/generators/install_generator.rb
128
135
  - lib/active_cypher/generators/node_generator.rb
129
136
  - lib/active_cypher/generators/relationship_generator.rb
@@ -132,6 +139,7 @@ files:
132
139
  - lib/active_cypher/generators/templates/cypher_databases.yml
133
140
  - lib/active_cypher/generators/templates/node.rb.erb
134
141
  - lib/active_cypher/generators/templates/relationship.rb.erb
142
+ - lib/active_cypher/instrumentation.rb
135
143
  - lib/active_cypher/logging.rb
136
144
  - lib/active_cypher/model/abstract.rb
137
145
  - lib/active_cypher/model/attributes.rb
@@ -142,6 +150,7 @@ files:
142
150
  - lib/active_cypher/model/countable.rb
143
151
  - lib/active_cypher/model/destruction.rb
144
152
  - lib/active_cypher/model/inspectable.rb
153
+ - lib/active_cypher/model/labelling.rb
145
154
  - lib/active_cypher/model/persistence.rb
146
155
  - lib/active_cypher/model/querying.rb
147
156
  - lib/active_cypher/railtie.rb
@@ -183,12 +192,14 @@ files:
183
192
  - lib/cyrel/expression/pattern_comprehension.rb
184
193
  - lib/cyrel/expression/property_access.rb
185
194
  - lib/cyrel/functions.rb
195
+ - lib/cyrel/logging.rb
186
196
  - lib/cyrel/node.rb
187
197
  - lib/cyrel/parameterizable.rb
188
198
  - lib/cyrel/pattern.rb
189
199
  - lib/cyrel/pattern/node.rb
190
200
  - lib/cyrel/pattern/path.rb
191
201
  - lib/cyrel/pattern/relationship.rb
202
+ - lib/cyrel/plus.rb
192
203
  - lib/cyrel/query.rb
193
204
  - lib/cyrel/return_only.rb
194
205
  - lib/cyrel/types/hash_type.rb
@@ -1,130 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveCypher
4
- # ConnectionFactory provides a simple API for creating connections to Cypher databases
5
- # using the ConnectionUrlResolver to parse database URLs.
6
- #
7
- # This factory simplifies the process of creating database connections by:
8
- # 1. Parsing connection URLs with ConnectionUrlResolver
9
- # 2. Creating the appropriate adapter based on the URL
10
- # 3. Establishing and configuring the connection with the right security settings
11
- #
12
- # Example:
13
- # factory = ConnectionFactory.new("neo4j://user:pass@localhost:7687")
14
- # driver = factory.create_driver
15
- #
16
- # driver.with_session do |session|
17
- # result = session.run("RETURN 'Connected!' AS message")
18
- # puts result.first[:message]
19
- # end
20
- class ConnectionFactory
21
- # Initialize with a database URL
22
- # @param url [String] A database connection URL
23
- # @param options [Hash] Additional options for the connection
24
- def initialize(url, options = {})
25
- @url = url
26
- @options = options
27
- @config = resolve_url(url)
28
- end
29
-
30
- # Create a Bolt driver based on the parsed URL
31
- # @param pool_size [Integer] Size of the connection pool
32
- # @return [ActiveCypher::Bolt::Driver, nil] The configured driver or nil if URL is invalid
33
- def create_driver(pool_size: 5)
34
- return nil unless @config
35
-
36
- # Create the adapter based on the resolved configuration
37
- adapter = create_adapter
38
- return nil unless adapter
39
-
40
- # Create and configure the Bolt driver
41
- uri = build_uri
42
- auth_token = build_auth_token
43
-
44
- ActiveCypher::Bolt::Driver.new(
45
- uri: uri,
46
- adapter: adapter,
47
- auth_token: auth_token,
48
- pool_size: pool_size
49
- )
50
- end
51
-
52
- # Get the parsed configuration
53
- # @return [Hash, nil] The parsed configuration or nil if URL is invalid
54
- attr_reader :config
55
-
56
- # Verify if the URL is valid and supported
57
- # @return [Boolean] True if the URL is valid and supported
58
- def valid?
59
- !@config.nil?
60
- end
61
-
62
- private
63
-
64
- # Resolve the URL into a configuration hash
65
- def resolve_url(url)
66
- resolver = ConnectionUrlResolver.new(url)
67
- resolver.to_hash
68
- end
69
-
70
- # Create the appropriate adapter based on the parsed URL
71
- def create_adapter
72
- case @config[:adapter]
73
- when 'neo4j'
74
- create_neo4j_adapter
75
- when 'memgraph'
76
- create_memgraph_adapter
77
- else
78
- nil
79
- end
80
- end
81
-
82
- # Create a Neo4j adapter
83
- def create_neo4j_adapter
84
- ConnectionAdapters::Neo4jAdapter.new({
85
- uri: build_uri,
86
- username: @config[:username],
87
- password: @config[:password],
88
- database: @config[:database]
89
- })
90
- end
91
-
92
- # Create a Memgraph adapter
93
- def create_memgraph_adapter
94
- ConnectionAdapters::MemgraphAdapter.new({
95
- uri: build_uri,
96
- username: @config[:username],
97
- password: @config[:password],
98
- database: @config[:database]
99
- })
100
- rescue NameError
101
- # Fall back to Neo4j adapter if Memgraph adapter is not available
102
- ConnectionAdapters::Neo4jAdapter.new({
103
- uri: build_uri,
104
- username: @config[:username],
105
- password: @config[:password],
106
- database: @config[:database]
107
- })
108
- end
109
-
110
- # Build the URI string with the appropriate scheme based on SSL settings
111
- def build_uri
112
- scheme = if @config[:ssl]
113
- @config[:ssc] ? 'bolt+ssc' : 'bolt+s'
114
- else
115
- 'bolt'
116
- end
117
-
118
- "#{scheme}://#{@config[:host]}:#{@config[:port]}"
119
- end
120
-
121
- # Build the authentication token for the Bolt driver
122
- def build_auth_token
123
- {
124
- scheme: 'basic',
125
- principal: @config[:username],
126
- credentials: @config[:password]
127
- }
128
- end
129
- end
130
- end