22
33# Extensions to ActiveRelationResource for cross-schema support
44module 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
5+ module ActiveRelationResourceExtensions
6+ def self . included ( base )
7+ base . class_eval do
8+ # Override find_related_fragments to handle cross-schema relationships
9+ def self . find_related_fragments ( source_rids , relationship_name , options = { } )
10+ relationship = _relationship ( relationship_name )
11+
12+ if defined? ( _cross_schema_relationships ) && _cross_schema_relationships && ( cross_schema_info = _cross_schema_relationships [ relationship_name . to_sym ] )
13+ # Handle cross-schema relationship
14+ handle_cross_schema_relationship ( source_rids , relationship , cross_schema_info , options )
15+ else
16+ # Call the original implementation
17+ super ( source_rids , relationship_name , options )
18+ end
1819 end
1920
20- @cross_schema_support_setup = true
21- end
21+ # Override find_included_fragments to handle cross-schema relationships
22+ def self . find_included_fragments ( source , relationship_name , options )
23+ relationship = _relationship ( relationship_name )
2224
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
25+ if defined? ( _cross_schema_relationships ) && _cross_schema_relationships && ( cross_schema_info = _cross_schema_relationships [ relationship_name . to_sym ] )
26+ # Handle cross-schema relationship
27+ handle_cross_schema_included ( source , relationship , cross_schema_info , options )
28+ else
29+ # Call the original implementation
30+ super ( source , relationship_name , options )
31+ end
32+ end
2633
27- relationship = _relationship ( relationship_name )
34+ private
2835
29- if defined? ( _cross_schema_relationships ) && _cross_schema_relationships && ( cross_schema_info = _cross_schema_relationships [ relationship_name . to_sym ] )
30- # Handle cross-schema relationship
36+ def self . handle_cross_schema_relationship ( source_rids , relationship , cross_schema_info , options )
3137 schema = cross_schema_info [ :schema ]
3238
3339 # Get the source records
@@ -39,24 +45,9 @@ def find_related_fragments(source_rids, relationship_name, options = {})
3945 else
4046 handle_cross_schema_to_many ( source_records , relationship , schema , options )
4147 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
4948 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
5549
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
50+ def self . handle_cross_schema_included ( source , relationship , cross_schema_info , options )
6051 schema = cross_schema_info [ :schema ]
6152
6253 # Extract IDs from source - it could be a hash of resource fragments
@@ -78,92 +69,80 @@ def find_included_fragments(source, relationship_name, options)
7869 else
7970 handle_cross_schema_to_many ( source_records , relationship , schema , options )
8071 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 )
8872 end
89- end
9073
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 )
74+ def self . handle_cross_schema_to_one ( source_records , relationship , schema , options )
75+ # For has_one or belongs_to with cross-schema
76+ related_klass = relationship . resource_klass
77+ foreign_key = relationship . foreign_key
78+
79+ # Get the foreign key values from source records
80+ foreign_key_values = source_records . map { |r | r . _model . send ( foreign_key ) } . compact . uniq
81+
82+ return { } if foreign_key_values . empty?
83+
84+ # Query the related table with schema prefix
85+ # This should be configured based on the actual schema and table
86+ full_table_name = "#{ schema } .#{ relationship . table_name || related_klass . _table_name } "
87+
88+ # Use ActiveRecord to query cross-schema with proper connection
89+ connection = ActiveRecord ::Base . connection
90+ quoted_table = connection . quote_table_name ( full_table_name )
91+ quoted_ids = foreign_key_values . map { |id | connection . quote ( id ) } . join ( ',' )
92+
93+ sql = "SELECT * FROM #{ quoted_table } WHERE id IN (#{ quoted_ids } )"
94+ related_records = connection . exec_query ( sql )
95+
96+ # Convert to fragments
97+ fragments = { }
98+ related_records . each do |record_hash |
99+ # Create a model instance from the hash
100+ model_class = related_klass . _model_class
101+ instance = model_class . instantiate ( record_hash )
102+ resource = related_klass . new ( instance , options [ :context ] )
103+ rid = JSONAPI ::ResourceIdentity . new ( related_klass , instance . id )
104+ fragments [ rid ] = JSONAPI ::ResourceFragment . new ( rid , resource : resource )
105+ end
94106
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 )
107+ fragments
99108 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
111109
112- return { } if foreign_key_values . empty?
110+ def self . handle_cross_schema_to_many ( source_records , relationship , schema , options )
111+ # For has_many with cross-schema
112+ related_klass = relationship . resource_klass
113113
114- # Query the related table with schema prefix
115- full_table_name = "#{ schema } .users_v1 "
114+ # Determine the foreign key based on the source model
115+ foreign_key = relationship . foreign_key || "#{ _type . to_s . singularize } _id "
116116
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- )
117+ # Get source IDs
118+ source_ids = source_records . map { |r | r . _model . send ( _primary_key ) } . compact . uniq
122119
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
120+ return { } if source_ids . empty?
139121
140- # Determine the foreign key based on the source model
141- foreign_key = "#{ _type . to_s . singularize } _id "
122+ # Query the related table with schema prefix
123+ full_table_name = "#{ schema } . #{ relationship . table_name || related_klass . _table_name } "
142124
143- # Get source IDs
144- source_ids = source_records . map { |r | r . _model . send ( _primary_key ) } . compact . uniq
125+ connection = ActiveRecord ::Base . connection
126+ quoted_table = connection . quote_table_name ( full_table_name )
127+ quoted_key = connection . quote_column_name ( foreign_key )
128+ quoted_ids = source_ids . map { |id | connection . quote ( id ) } . join ( ',' )
145129
146- return { } if source_ids . empty?
130+ sql = "SELECT * FROM #{ quoted_table } WHERE #{ quoted_key } IN (#{ quoted_ids } )"
131+ related_records = connection . exec_query ( sql )
147132
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 )
133+ # Convert to fragments
134+ fragments = { }
135+ related_records . each do |record_hash |
136+ # Create a model instance from the hash
137+ model_class = related_klass . _model_class
138+ instance = model_class . instantiate ( record_hash )
139+ resource = related_klass . new ( instance , options [ :context ] )
140+ rid = JSONAPI ::ResourceIdentity . new ( related_klass , instance . id )
141+ fragments [ rid ] = JSONAPI ::ResourceFragment . new ( rid , resource : resource )
142+ end
155143
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 )
144+ fragments
164145 end
165-
166- fragments
167146 end
168147 end
169148 end
0 commit comments