1+ # frozen_string_literal: true
2+
3+ # Extensions to ActiveRelationResource for cross-schema support
4+ module JSONAPI
5+ class ActiveRelationResource
6+ class << self
7+ # Store original methods if they exist
8+ def setup_cross_schema_support
9+ return if @cross_schema_support_setup
10+
11+ # Only alias if the method exists and hasn't been aliased already
12+ if method_defined? ( :find_related_fragments ) && !method_defined? ( :original_find_related_fragments )
13+ alias_method :original_find_related_fragments , :find_related_fragments
14+ end
15+
16+ if method_defined? ( :find_included_fragments ) && !method_defined? ( :original_find_included_fragments )
17+ alias_method :original_find_included_fragments , :find_included_fragments
18+ end
19+
20+ @cross_schema_support_setup = true
21+ end
22+
23+ # Override find_related_fragments to handle cross-schema relationships
24+ def find_related_fragments ( source_rids , relationship_name , options = { } )
25+ setup_cross_schema_support
26+
27+ relationship = _relationship ( relationship_name )
28+
29+ if defined? ( _cross_schema_relationships ) && _cross_schema_relationships && ( cross_schema_info = _cross_schema_relationships [ relationship_name . to_sym ] )
30+ # Handle cross-schema relationship
31+ schema = cross_schema_info [ :schema ]
32+
33+ # Get the source records
34+ source_records = source_rids . map { |rid | find_by_key ( rid . id , options ) } . compact
35+
36+ # Build the cross-schema query
37+ if relationship . is_a? ( JSONAPI ::Relationship ::ToOne )
38+ handle_cross_schema_to_one ( source_records , relationship , schema , options )
39+ else
40+ handle_cross_schema_to_many ( source_records , relationship , schema , options )
41+ end
42+ else
43+ # Use the original method for normal relationships
44+ if respond_to? ( :original_find_related_fragments )
45+ original_find_related_fragments ( source_rids , relationship_name , options )
46+ else
47+ super ( source_rids , relationship_name , options )
48+ end
49+ end
50+ end
51+
52+ # Override find_included_fragments to handle cross-schema relationships
53+ def find_included_fragments ( source , relationship_name , options )
54+ setup_cross_schema_support
55+
56+ relationship = _relationship ( relationship_name )
57+
58+ if defined? ( _cross_schema_relationships ) && _cross_schema_relationships && ( cross_schema_info = _cross_schema_relationships [ relationship_name . to_sym ] )
59+ # Handle cross-schema relationship
60+ schema = cross_schema_info [ :schema ]
61+
62+ # Extract IDs from source - it could be a hash of resource fragments
63+ source_ids = if source . is_a? ( Hash )
64+ source . keys . map ( &:id )
65+ elsif source . is_a? ( Array ) && source . first . respond_to? ( :identity )
66+ # Array of resource fragments
67+ source . map { |fragment | fragment . identity . id }
68+ else
69+ source . map ( &:id )
70+ end
71+
72+ # Get the source records
73+ source_records = source_ids . map { |id | find_by_key ( id , options ) } . compact
74+
75+ # Build the cross-schema query
76+ if relationship . is_a? ( JSONAPI ::Relationship ::ToOne )
77+ handle_cross_schema_to_one ( source_records , relationship , schema , options )
78+ else
79+ handle_cross_schema_to_many ( source_records , relationship , schema , options )
80+ end
81+ elsif respond_to? ( :original_find_included_fragments )
82+ # Use the original method for normal relationships
83+ original_find_included_fragments ( source , relationship_name , options )
84+ else
85+ # This resource doesn't have find_included_fragments, delegate to parent
86+ # We'll use the default implementation from ActiveRelationResource
87+ find_included_fragments_default ( source , relationship_name , options )
88+ end
89+ end
90+
91+ # Default implementation for resources that don't have find_included_fragments
92+ def find_included_fragments_default ( source , relationship_name , options )
93+ relationship = _relationship ( relationship_name )
94+
95+ if relationship . polymorphic?
96+ find_related_polymorphic_fragments ( source , relationship_name , options , true )
97+ else
98+ find_related_monomorphic_fragments ( source , relationship , options , true )
99+ end
100+ end
101+
102+ private
103+
104+ def handle_cross_schema_to_one ( source_records , relationship , schema , options )
105+ # For has_one or belongs_to with cross-schema
106+ related_klass = relationship . resource_klass
107+ foreign_key = relationship . foreign_key
108+
109+ # Get the foreign key values from source records
110+ foreign_key_values = source_records . map { |r | r . _model . send ( foreign_key ) } . compact . uniq
111+
112+ return { } if foreign_key_values . empty?
113+
114+ # Query the related table with schema prefix
115+ full_table_name = "#{ schema } .users_v1"
116+
117+ # Use raw SQL to query cross-schema
118+ sql = "SELECT * FROM #{ full_table_name } WHERE id IN (?)"
119+ related_records = ActiveRecord ::Base . connection . exec_query (
120+ ActiveRecord ::Base . send ( :sanitize_sql_array , [ sql , foreign_key_values ] )
121+ )
122+
123+ # Convert to fragments
124+ fragments = { }
125+ related_records . each do |record_hash |
126+ # Create a mock Employee model instance from the hash
127+ employee = Employee . instantiate ( record_hash )
128+ resource = related_klass . new ( employee , options [ :context ] )
129+ rid = JSONAPI ::ResourceIdentity . new ( related_klass , employee . id )
130+ fragments [ rid ] = JSONAPI ::ResourceFragment . new ( rid , resource : resource )
131+ end
132+
133+ fragments
134+ end
135+
136+ def handle_cross_schema_to_many ( source_records , relationship , schema , options )
137+ # For has_many with cross-schema
138+ related_klass = relationship . resource_klass
139+
140+ # Determine the foreign key based on the source model
141+ foreign_key = "#{ _type . to_s . singularize } _id"
142+
143+ # Get source IDs
144+ source_ids = source_records . map { |r | r . _model . send ( _primary_key ) } . compact . uniq
145+
146+ return { } if source_ids . empty?
147+
148+ # Query the related table with schema prefix
149+ full_table_name = "#{ schema } .users_v1"
150+
151+ # For has_many employees, we need to handle the join table or direct relationship
152+ # This is a simplified version - you may need to adjust based on your actual schema
153+ sql = "SELECT * FROM #{ full_table_name } "
154+ related_records = ActiveRecord ::Base . connection . exec_query ( sql )
155+
156+ # Convert to fragments
157+ fragments = { }
158+ related_records . each do |record_hash |
159+ # Create a mock Employee model instance from the hash
160+ employee = Employee . instantiate ( record_hash )
161+ resource = related_klass . new ( employee , options [ :context ] )
162+ rid = JSONAPI ::ResourceIdentity . new ( related_klass , employee . id )
163+ fragments [ rid ] = JSONAPI ::ResourceFragment . new ( rid , resource : resource )
164+ end
165+
166+ fragments
167+ end
168+ end
169+ end
170+ end
0 commit comments