1+ # frozen_string_literal: true
2+
3+ # Monkey patch for ActiveRelationResource to support cross-schema relationships
4+ module JSONAPI
5+ class ActiveRelationResource
6+ class << self
7+ # Store original methods
8+ alias_method :original_find_included_fragments , :find_included_fragments
9+ alias_method :original_find_related_monomorphic_fragments , :find_related_monomorphic_fragments
10+
11+ # Override find_included_fragments to handle cross-schema relationships
12+ def find_included_fragments ( source , relationship_name , options )
13+ relationship = _relationship ( relationship_name )
14+
15+ # Check if this resource has cross-schema relationships defined
16+ if respond_to? ( :_cross_schema_relationships ) && _cross_schema_relationships && ( cross_schema_info = _cross_schema_relationships [ relationship_name . to_sym ] )
17+ handle_cross_schema_included ( source , relationship , cross_schema_info , options )
18+ else
19+ original_find_included_fragments ( source , relationship_name , options )
20+ end
21+ end
22+
23+ # Override find_related_monomorphic_fragments to handle cross-schema relationships
24+ def find_related_monomorphic_fragments ( source , relationship , options , connect_source_identity )
25+ # Check if this resource has cross-schema relationships defined
26+ if respond_to? ( :_cross_schema_relationships ) && _cross_schema_relationships && ( cross_schema_info = _cross_schema_relationships [ relationship . name . to_sym ] )
27+ handle_cross_schema_included ( source , relationship , cross_schema_info , options )
28+ else
29+ original_find_related_monomorphic_fragments ( source , relationship , options , connect_source_identity )
30+ end
31+ end
32+
33+ private
34+
35+ def handle_cross_schema_included ( source , relationship , cross_schema_info , options )
36+ schema = cross_schema_info [ :schema ]
37+
38+ # Extract IDs from source - it could be a hash of resource fragments
39+ source_ids = if source . is_a? ( Hash )
40+ source . keys . map ( &:id )
41+ elsif source . is_a? ( Array ) && source . first . respond_to? ( :identity )
42+ # Array of resource fragments
43+ source . map { |fragment | fragment . identity . id }
44+ else
45+ source . map ( &:id )
46+ end
47+
48+ # Get the source records
49+ source_records = source_ids . map { |id | find_by_key ( id , options ) } . compact
50+
51+ # Build the cross-schema query
52+ if relationship . is_a? ( JSONAPI ::Relationship ::ToOne )
53+ handle_cross_schema_to_one ( source_records , relationship , schema , options )
54+ else
55+ handle_cross_schema_to_many ( source_records , relationship , schema , options )
56+ end
57+ end
58+
59+ def handle_cross_schema_to_one ( source_records , relationship , schema , options )
60+ # For has_one or belongs_to with cross-schema
61+ related_klass = relationship . resource_klass
62+ foreign_key = relationship . foreign_key
63+
64+ # Get the foreign key values from source records
65+ foreign_key_values = source_records . map { |r | r . _model . send ( foreign_key ) } . compact . uniq
66+
67+ return { } if foreign_key_values . empty?
68+
69+ # Get the actual table name from the related resource's model
70+ related_model_class = related_klass . _model_class
71+ base_table_name = related_model_class . table_name . split ( '.' ) . last # Remove schema if present
72+ full_table_name = "#{ schema } .#{ base_table_name } "
73+
74+ # Use raw SQL to query cross-schema
75+ sql = "SELECT * FROM #{ full_table_name } WHERE id IN (?)"
76+ related_records = ActiveRecord ::Base . connection . exec_query (
77+ ActiveRecord ::Base . send ( :sanitize_sql_array , [ sql , foreign_key_values ] )
78+ )
79+
80+ # Convert to fragments
81+ fragments = { }
82+ related_records . each do |record_hash |
83+ # Create a model instance from the hash using the related model class
84+ model_instance = related_model_class . instantiate ( record_hash )
85+ resource = related_klass . new ( model_instance , options [ :context ] )
86+ rid = JSONAPI ::ResourceIdentity . new ( related_klass , model_instance . id )
87+ fragments [ rid ] = JSONAPI ::ResourceFragment . new ( rid , resource : resource )
88+ end
89+
90+ fragments
91+ end
92+
93+ def handle_cross_schema_to_many ( source_records , relationship , schema , options )
94+ # For has_many with cross-schema
95+ related_klass = relationship . resource_klass
96+
97+ # Determine the foreign key based on the source model
98+ foreign_key = "#{ _type . to_s . singularize } _id"
99+
100+ # Get source IDs
101+ source_ids = source_records . map { |r | r . _model . send ( _primary_key ) } . compact . uniq
102+
103+ return { } if source_ids . empty?
104+
105+ # Get the actual table name from the related resource's model
106+ related_model_class = related_klass . _model_class
107+ base_table_name = related_model_class . table_name . split ( '.' ) . last # Remove schema if present
108+ full_table_name = "#{ schema } .#{ base_table_name } "
109+
110+ # For has_many employees, we need to handle the join table or direct relationship
111+ # This is a simplified version - you may need to adjust based on your actual schema
112+ sql = "SELECT * FROM #{ full_table_name } "
113+ related_records = ActiveRecord ::Base . connection . exec_query ( sql )
114+
115+ # Convert to fragments
116+ fragments = { }
117+ related_records . each do |record_hash |
118+ # Create a model instance from the hash using the related model class
119+ model_instance = related_model_class . instantiate ( record_hash )
120+ resource = related_klass . new ( model_instance , options [ :context ] )
121+ rid = JSONAPI ::ResourceIdentity . new ( related_klass , model_instance . id )
122+ fragments [ rid ] = JSONAPI ::ResourceFragment . new ( rid , resource : resource )
123+ end
124+
125+ fragments
126+ end
127+ end
128+ end
129+ end
0 commit comments