Module: ActiveCypher::Associations
- Extended by:
- ActiveSupport::Concern
- Defined in:
- lib/active_cypher/associations.rb,
lib/active_cypher/associations/collection_proxy.rb
Overview
Note:
Because every DSL wants to be ActiveRecord when it grows up.
Module to handle association definitions (has_many, belongs_to, etc.) for ActiveCypher models.
Defined Under Namespace
Classes: CollectionProxy
Class Method Summary collapse
-
.define_has_many_through_reader(reflection) ⇒ Object
Defines the reader method for a has_many :through association.
Class Method Details
.define_has_many_through_reader(reflection) ⇒ Object
Defines the reader method for a has_many :through association. Because sometimes you want to join tables, but with extra steps.
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 |
# File 'lib/active_cypher/associations.rb', line 456 def self.define_has_many_through_reader(reflection) name = reflection[:name] through_association_name = reflection[:through] source_association_name = reflection[:source] || name # Default source is same name on intermediate model define_method(name) do raise ActiveCypher::PersistenceError, 'Association load attempted on unsaved record' unless persisted? # 1. Get reflection for the intermediate association (e.g., :friendships) through_reflection = self.class._reflections[through_association_name] unless through_reflection raise ArgumentError, "Could not find association '#{through_association_name}' specified in :through option for '#{name}'" end intermediate_class = through_reflection[:class_name].constantize # 2. Get reflection for the source association on the intermediate model (e.g., :to_node on Friendship) # Note: This assumes the intermediate model also uses ActiveCypher::Associations source_reflection = intermediate_class._reflections[source_association_name] unless source_reflection raise ArgumentError, "Could not find association '#{source_association_name}' specified as :source (or inferred) on '#{intermediate_class.name}' for '#{name}'" end final_target_class = source_reflection[:class_name].constantize # 3. Build the multi-hop Cyrel query. # Because why settle for one hop when you can have two and still not get what you want? start_node_alias = :start_node intermediate_node_alias = :intermediate_node final_target_node_alias = :final_target # Start node pattern start_node_pattern = Cyrel.node(self.class.label_name).as(start_node_alias) .where(Cyrel.node_id(start_node_alias).eq(internal_id)) # Intermediate node pattern (based on through_reflection) intermediate_node_pattern = Cyrel.node(intermediate_class.label_name).as(intermediate_node_alias) through_rel_type = through_reflection[:relationship] through_direction = through_reflection[:direction] first_hop_pattern = case through_direction when :out then start_node_pattern.rel(:out, through_rel_type).to(intermediate_node_pattern) when :in then intermediate_node_pattern.rel(:out, through_rel_type).to(start_node_pattern) when :both then start_node_pattern.rel(:both, through_rel_type).to(intermediate_node_pattern) else raise ArgumentError, "Invalid direction in through_reflection: #{through_direction}" end # Final target node pattern (based on source_reflection) final_target_node_pattern = Cyrel.node(final_target_class.label_name).as(final_target_node_alias) source_rel_type = source_reflection[:relationship] source_direction = source_reflection[:direction] second_hop_pattern = case source_direction when :out then intermediate_node_pattern.rel(:out, source_rel_type).to(final_target_node_pattern) when :in then final_target_node_pattern.rel(:out, source_rel_type).to(intermediate_node_pattern) when :both then intermediate_node_pattern.rel(:both, source_rel_type).to(final_target_node_pattern) else raise ArgumentError, "Invalid direction in source_reflection: #{source_direction}" end # Combine patterns and return final target # Assuming Cyrel allows chaining matches or building complex patterns # This might need adjustment based on Cyrel's exact path-building API query = Cyrel.match(first_hop_pattern) .match(second_hop_pattern) # Assumes .match adds to the pattern .return(final_target_node_alias) # TODO: Add DISTINCT if needed? .return(Cyrel.distinct(final_target_node_alias)) # Return a Relation scoped to the final target class Relation.new(final_target_class, query) end end |