Class: Cyrel::Node
- Inherits:
-
Object
- Object
- Cyrel::Node
- Defined in:
- lib/cyrel/node.rb
Overview
The base class for building Cypher queries.
Instance Method Summary collapse
-
#all ⇒ Object
Match all nodes of this type.
- #as_path(path_variable) ⇒ Object
-
#call ⇒ Object
Invokes a subquery block.
- #create(properties) ⇒ Object
-
#detach_delete ⇒ Object
Schedules the node for DEATH, WITH DETACHMENT.
-
#initialize(label, as: nil) ⇒ Node
constructor
A new instance of Node.
- #limit(amount) ⇒ Object
-
#match(pattern) ⇒ Object
Defines the pattern to MATCH.
- #merge(properties) ⇒ Object
-
#node(label, as: nil) ⇒ Object
Specifies a related node.
-
#optional_outgoing(relationship) ⇒ Object
Specifies an optional outgoing relationship.
- #order_by(field, direction = :asc) ⇒ Object
-
#outgoing(relationship) ⇒ Object
Specifies an outgoing relationship.
- #remove(*properties) ⇒ Object
-
#return(*fields) ⇒ Object
Specifies what to return in the query.
- #set(properties) ⇒ Object
- #skip(amount) ⇒ Object
-
#to_cypher ⇒ String
This method assembles the final Cypher query like Dr.
-
#where(conditions) ⇒ Object
Adds conditions to the query.
-
#where_exists ⇒ Object
Builds a WHERE EXISTS subquery.
- #with(clause) ⇒ Object
Constructor Details
#initialize(label, as: nil) ⇒ Node
Returns a new instance of Node.
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
# File 'lib/cyrel/node.rb', line 8 def initialize(label, as: nil) @label = label @alias = as || label.to_s.underscore.to_sym @conditions = {} @raw_conditions = [] = {} @optional_match = false @return_fields = [] @order_clauses = [] @skip_value = nil @limit_value = nil @with_clause = nil @where_after_with = nil @remove_props = [] @detach_delete = false @path_variable = nil end |
Instance Method Details
#all ⇒ Object
Match all nodes of this type
50 51 52 |
# File 'lib/cyrel/node.rb', line 50 def all self end |
#as_path(path_variable) ⇒ Object
125 126 127 128 |
# File 'lib/cyrel/node.rb', line 125 def as_path(path_variable) @path_variable = path_variable self end |
#call ⇒ Object
Invokes a subquery block. Like a Matryoshka of complexity. Because why write one query when you can write two for double the confusion?
149 150 151 152 153 154 155 156 157 158 |
# File 'lib/cyrel/node.rb', line 149 def call(&) if block_given? subquery = self.class.new(@label, as: @alias) subquery.instance_eval(&) @call_subquery = subquery else # This is for standalone call end self end |
#create(properties) ⇒ Object
83 84 85 86 |
# File 'lib/cyrel/node.rb', line 83 def create(properties) @create_properties = properties self end |
#detach_delete ⇒ Object
Schedules the node for DEATH, WITH DETACHMENT. Cold, clean, and emotionally unavailable. Just like your ex.
100 101 102 103 |
# File 'lib/cyrel/node.rb', line 100 def detach_delete @detach_delete = true self end |
#limit(amount) ⇒ Object
115 116 117 118 |
# File 'lib/cyrel/node.rb', line 115 def limit(amount) @limit_value = amount self end |
#match(pattern) ⇒ Object
Defines the pattern to MATCH. Not to be confused with your desperate search for compatibility on dating apps.
73 74 75 76 |
# File 'lib/cyrel/node.rb', line 73 def match(pattern) @match_pattern = pattern self end |
#merge(properties) ⇒ Object
88 89 90 91 |
# File 'lib/cyrel/node.rb', line 88 def merge(properties) @merge_properties = properties self end |
#node(label, as: nil) ⇒ Object
Specifies a related node.
178 179 180 181 182 |
# File 'lib/cyrel/node.rb', line 178 def node(label, as: nil) = label = as || label.to_s.underscore.to_sym self end |
#optional_outgoing(relationship) ⇒ Object
Specifies an optional outgoing relationship. It’s not ghosting, it’s just… optional commitment.
169 170 171 172 173 |
# File 'lib/cyrel/node.rb', line 169 def optional_outgoing(relationship) @outgoing_relationship = relationship @optional_match = true self end |
#order_by(field, direction = :asc) ⇒ Object
105 106 107 108 |
# File 'lib/cyrel/node.rb', line 105 def order_by(field, direction = :asc) @order_clauses << { field: field, direction: direction } self end |
#outgoing(relationship) ⇒ Object
Specifies an outgoing relationship.
162 163 164 165 |
# File 'lib/cyrel/node.rb', line 162 def outgoing(relationship) @outgoing_relationship = relationship self end |
#remove(*properties) ⇒ Object
93 94 95 96 |
# File 'lib/cyrel/node.rb', line 93 def remove(*properties) @remove_props.concat(properties) self end |
#return(*fields) ⇒ Object
Specifies what to return in the query. Also raises an exception if you try to be too clever—because your cleverness is not welcome here.
57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/cyrel/node.rb', line 57 def return(*fields) fields.each do |field| # Skip validation for path variables and variables from subqueries next if field.is_a?(String) && (field == @path_variable.to_s || (field.match(/^\w+$/) && defined_in_query?(field))) if field.is_a?(Symbol) || (field.is_a?(String) && !field.include?('.') && !field.include?(' as ') && !field.include?('(') && !field.match(/\[.+\]/)) raise StandardError, 'Ambiguous name. Please use a string with alias.' end end @return_fields = fields self end |
#set(properties) ⇒ Object
78 79 80 81 |
# File 'lib/cyrel/node.rb', line 78 def set(properties) @set_properties = properties self end |
#skip(amount) ⇒ Object
110 111 112 113 |
# File 'lib/cyrel/node.rb', line 110 def skip(amount) @skip_value = amount self end |
#to_cypher ⇒ String
This method assembles the final Cypher query like Dr. Frankenstein assembling a monster: a bit of this, a stitch of that, and screaming at lightning until it runs. If this explodes, blame the architecture, not the architect.
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 |
# File 'lib/cyrel/node.rb', line 188 def to_cypher parts = [] # CREATE or MERGE clauses if @create_properties formatted_props = format_properties(@create_properties) parts << "CREATE (#{@alias}:#{@label} {#{formatted_props}})" elsif @merge_properties formatted_props = format_properties(@merge_properties) parts << "MERGE (#{@alias}:#{@label} {#{formatted_props}})" else # Build MATCH clause match_clause = build_match_clause parts << match_clause if match_clause end # Add CALL subquery if present if @call_subquery subquery_cypher = @call_subquery.to_cypher # Format for subquery in CALL subquery_cypher = subquery_cypher.gsub(/^MATCH /, '') parts << "CALL { MATCH #{subquery_cypher} }" end # WITH clause parts << "WITH #{@with_clause}" if @with_clause parts << "WHERE #{@where_after_with}" if @where_after_with && @with_clause # SET clause for property updates if @set_properties set_parts = @set_properties.map { |k, v| "#{@alias}.#{k} = #{format_value(v)}" } parts << "SET #{set_parts.join(', ')}" end # REMOVE clause if @remove_props.any? remove_parts = @remove_props.map { |prop| "#{@alias}.#{prop}" } parts << "REMOVE #{remove_parts.join(', ')}" end # DETACH DELETE clause parts << "DETACH DELETE #{@alias}" if @detach_delete # RETURN clause if @return_fields.any? return_parts = @return_fields.map do |field| if field.is_a?(Symbol) "#{@alias}.#{field}" elsif field.is_a?(String) # Check if it's a path variable if @path_variable && field == @path_variable.to_s field # Check if it might be a variable from a subquery elsif field.match(/^\w+$/) && defined_in_query?(field) field # Handle function calls (prevent node alias prefix on CASE/function keywords) elsif field.start_with?('CASE ') || field.match(/^\w+\s*\(/) field.gsub(' as ', ' AS ') # Handle pattern comprehensions elsif field.match(/\[.+\]/) field.gsub(' as ', ' AS ') # Handle field with alias syntax elsif field.include?(' as ') modified_field = field.gsub(' as ', ' AS ') if modified_field.include?('.') modified_field else "#{@alias}.#{modified_field}" end # Handle field without dot notation elsif !field.include?('.') "#{@alias}.#{field}" else field end else field end end parts << "RETURN #{return_parts.join(', ')}" end # ORDER BY clause if @order_clauses.any? order_parts = @order_clauses.map do |clause| "#{@alias}.#{clause[:field]} #{clause[:direction].to_s.upcase}" end parts << "ORDER BY #{order_parts.join(', ')}" end # SKIP and LIMIT parts << "SKIP #{@skip_value}" if @skip_value parts << "LIMIT #{@limit_value}" if @limit_value parts.join(' ') end |
#where(conditions) ⇒ Object
Adds conditions to the query.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/cyrel/node.rb', line 28 def where(conditions) if conditions.is_a?(String) # Process string-based conditions if @with_clause && !@where_after_with.nil? @where_after_with = "#{@where_after_with} AND #{conditions}" elsif @with_clause @where_after_with = conditions else @raw_conditions << conditions end else conditions = conditions.transform_keys(&:to_s) if @outgoing_relationship && .merge!(conditions) else @conditions.merge!(conditions) end end self end |
#where_exists ⇒ Object
Builds a WHERE EXISTS subquery. A fancy way to say, “Does this thing even exist?”—the same question your self-esteem asks daily.
132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/cyrel/node.rb', line 132 def where_exists(&) subquery = self.class.new(@label, as: @alias) subquery.instance_eval(&) pattern = "(#{@alias})" if subquery.instance_variable_get(:@outgoing_relationship) rel = subquery.instance_variable_get(:@outgoing_relationship) rel_node_label = subquery.instance_variable_get(:@related_node_label) pattern += "-[:#{rel}]->(:#{rel_node_label})" end @raw_conditions << "EXISTS(#{pattern})" self end |
#with(clause) ⇒ Object
120 121 122 123 |
# File 'lib/cyrel/node.rb', line 120 def with(clause) @with_clause = clause self end |