Skip to content

Commit 6da78e6

Browse files
committed
feat: cross-schema relationships support
1 parent 486f985 commit 6da78e6

File tree

4 files changed

+132
-151
lines changed

4 files changed

+132
-151
lines changed

lib/jsonapi-resources.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
require 'jsonapi/compiled_json'
66
require 'jsonapi/basic_resource'
77
require 'jsonapi/cross_schema_relationships'
8-
require 'jsonapi/active_relation_resource_extensions'
98
require 'jsonapi/active_relation_resource'
109
require 'jsonapi/resource'
1110
require 'jsonapi/cached_response_fragment'
@@ -45,3 +44,6 @@
4544
require 'jsonapi/resource_set'
4645
require 'jsonapi/path'
4746
require 'jsonapi/path_segment'
47+
48+
# Apply cross-schema patch after everything is loaded
49+
require 'jsonapi/active_relation_resource_patch'

lib/jsonapi/active_relation_resource.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
module JSONAPI
44
class ActiveRelationResource < BasicResource
55
include CrossSchemaRelationships
6-
include ActiveRelationResourceExtensions
76

87
root_resource
98

lib/jsonapi/active_relation_resource_extensions.rb

Lines changed: 0 additions & 149 deletions
This file was deleted.
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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

Comments
 (0)