@@ -2,151 +2,182 @@ module JSONAPI
22 class LinkBuilder
33 attr_reader :base_url ,
44 :primary_resource_klass ,
5- :route_formatter ,
6- :engine_name
5+ :engine ,
6+ :routes
77
88 def initialize ( config = { } )
99 @base_url = config [ :base_url ]
1010 @primary_resource_klass = config [ :primary_resource_klass ]
11- @route_formatter = config [ :route_formatter ]
12- @engine_name = build_engine_name
11+ @engine = build_engine
1312
14- # Warning: These make LinkBuilder non-thread-safe. That's not a problem with the
15- # request-specific way it's currently used, though.
16- @resources_path_cache = JSONAPI :: NaiveCache . new do | source_klass |
17- formatted_module_path_from_class ( source_klass ) + format_route ( source_klass . _type . to_s )
13+ if engine?
14+ @routes = @engine . routes
15+ else
16+ @routes = Rails . application . routes
1817 end
18+
19+ # ToDo: Use NaiveCache for values. For this we need to not return nils and create composite keys which work
20+ # as efficient cache lookups. This could be an array of the [source.identifier, relationship] since the
21+ # ResourceIdentity will compare equality correctly
1922 end
2023
2124 def engine?
22- !!@engine_name
25+ !!@engine
2326 end
2427
2528 def primary_resources_url
26- if engine?
27- engine_primary_resources_url
28- else
29- regular_primary_resources_url
30- end
29+ @primary_resources_url_cached ||= "#{ base_url } #{ primary_resources_path } "
30+ rescue NoMethodError
31+ warn "primary_resources_url for #{ @primary_resource_klass } could not be generated" if JSONAPI . configuration . warn_on_missing_routes
3132 end
3233
3334 def query_link ( query_params )
3435 "#{ primary_resources_url } ?#{ query_params . to_query } "
3536 end
3637
3738 def relationships_related_link ( source , relationship , query_params = { } )
38- url = "#{ self_link ( source ) } /#{ route_for_relationship ( relationship ) } "
39+ if relationship . parent_resource . singleton?
40+ url_helper_name = singleton_related_url_helper_name ( relationship )
41+ url = call_url_helper ( url_helper_name )
42+ else
43+ url_helper_name = related_url_helper_name ( relationship )
44+ url = call_url_helper ( url_helper_name , source . id )
45+ end
46+
47+ url = "#{ base_url } #{ url } "
3948 url = "#{ url } ?#{ query_params . to_query } " if query_params . present?
4049 url
50+ rescue NoMethodError
51+ warn "related_link for #{ relationship } could not be generated" if JSONAPI . configuration . warn_on_missing_routes
4152 end
4253
4354 def relationships_self_link ( source , relationship )
44- "#{ self_link ( source ) } /relationships/#{ route_for_relationship ( relationship ) } "
55+ if relationship . parent_resource . singleton?
56+ url_helper_name = singleton_relationship_self_url_helper_name ( relationship )
57+ url = call_url_helper ( url_helper_name )
58+ else
59+ url_helper_name = relationship_self_url_helper_name ( relationship )
60+ url = call_url_helper ( url_helper_name , source . id )
61+ end
62+
63+ url = "#{ base_url } #{ url } "
64+ url
65+ rescue NoMethodError
66+ warn "self_link for #{ relationship } could not be generated" if JSONAPI . configuration . warn_on_missing_routes
4567 end
4668
4769 def self_link ( source )
48- if engine?
49- engine_resource_url ( source )
50- else
51- regular_resource_url ( source )
52- end
70+ "#{ base_url } #{ resource_path ( source ) } "
71+ rescue NoMethodError
72+ warn "self_link for #{ source . class } could not be generated" if JSONAPI . configuration . warn_on_missing_routes
5373 end
5474
5575 private
5676
57- def build_engine_name
77+ def build_engine
5878 scopes = module_scopes_from_class ( primary_resource_klass )
5979
6080 begin
6181 unless scopes . empty?
6282 "#{ scopes . first . to_s . camelize } ::Engine" . safe_constantize
6383 end
64- rescue LoadError => e
84+ # :nocov:
85+ rescue LoadError => _e
6586 nil
87+ # :nocov:
6688 end
6789 end
6890
69- def engine_path_from_resource_class ( klass )
70- path_name = engine_resources_path_name_from_class ( klass )
71- engine_name . routes . url_helpers . public_send ( path_name )
91+ def call_url_helper ( method , *args )
92+ routes . url_helpers . public_send ( method , args )
93+ rescue NoMethodError => e
94+ raise e
7295 end
7396
74- def engine_primary_resources_path
75- engine_path_from_resource_class ( primary_resource_klass )
97+ def path_from_resource_class ( klass )
98+ url_helper_name = resources_url_helper_name_from_class ( klass )
99+ call_url_helper ( url_helper_name )
76100 end
77101
78- def engine_primary_resources_url
79- "#{ base_url } #{ engine_primary_resources_path } "
102+ def resource_path ( source )
103+ url_helper_name = resource_url_helper_name_from_source ( source )
104+ if source . class . singleton?
105+ call_url_helper ( url_helper_name )
106+ else
107+ call_url_helper ( url_helper_name , source . id )
108+ end
80109 end
81110
82- def engine_resource_path ( source )
83- resource_path_name = engine_resource_path_name_from_source ( source )
84- engine_name . routes . url_helpers . public_send ( resource_path_name , source . id )
111+ def primary_resources_path
112+ path_from_resource_class ( primary_resource_klass )
85113 end
86114
87- def engine_resource_path_name_from_source ( source )
88- scopes = module_scopes_from_class ( source . class ) [ 1 ..-1 ]
89- base_path_name = scopes . map { |scope | scope . underscore } . join ( "_" )
90- end_path_name = source . class . _type . to_s . singularize
91- [ base_path_name , end_path_name , "path" ] . reject ( &:blank? ) . join ( "_" )
115+ def url_helper_name_from_parts ( parts )
116+ ( parts << "path" ) . reject ( &:blank? ) . join ( "_" )
92117 end
93118
94- def engine_resource_url ( source )
95- "#{ base_url } #{ engine_resource_path ( source ) } "
96- end
119+ def resources_path_parts_from_class ( klass )
120+ if engine?
121+ scopes = module_scopes_from_class ( klass ) [ 1 ..-1 ]
122+ else
123+ scopes = module_scopes_from_class ( klass )
124+ end
97125
98- def engine_resources_path_name_from_class ( klass )
99- scopes = module_scopes_from_class ( klass ) [ 1 ..-1 ]
100126 base_path_name = scopes . map { |scope | scope . underscore } . join ( "_" )
101127 end_path_name = klass . _type . to_s
102-
103- if base_path_name . blank?
104- "#{ end_path_name } _path"
105- else
106- "#{ base_path_name } _#{ end_path_name } _path"
107- end
128+ [ base_path_name , end_path_name ]
108129 end
109130
110- def format_route ( route )
111- route_formatter . format ( route )
131+ def resources_url_helper_name_from_class ( klass )
132+ url_helper_name_from_parts ( resources_path_parts_from_class ( klass ) )
112133 end
113134
114- def formatted_module_path_from_class ( klass )
115- scopes = module_scopes_from_class ( klass )
116-
117- unless scopes . empty?
118- "/#{ scopes . map { |scope | format_route ( scope . to_s . underscore ) } . join ( '/' ) } /"
135+ def resource_path_parts_from_class ( klass )
136+ if engine?
137+ scopes = module_scopes_from_class ( klass ) [ 1 ..-1 ]
119138 else
120- "/"
139+ scopes = module_scopes_from_class ( klass )
121140 end
122- end
123141
124- def module_scopes_from_class ( klass )
125- klass . name . to_s . split ( "::" ) [ 0 ...-1 ]
142+ base_path_name = scopes . map { |scope | scope . underscore } . join ( "_" )
143+ end_path_name = klass . _type . to_s . singularize
144+ [ base_path_name , end_path_name ]
126145 end
127146
128- def regular_resources_path ( source_klass )
129- @resources_path_cache . get ( source_klass )
147+ def resource_url_helper_name_from_source ( source )
148+ url_helper_name_from_parts ( resource_path_parts_from_class ( source . class ) )
130149 end
131150
132- def regular_primary_resources_path
133- regular_resources_path ( primary_resource_klass )
151+ def related_url_helper_name ( relationship )
152+ relationship_parts = resource_path_parts_from_class ( relationship . parent_resource )
153+ relationship_parts << relationship . name
154+ url_helper_name_from_parts ( relationship_parts )
134155 end
135156
136- def regular_primary_resources_url
137- "#{ base_url } #{ regular_primary_resources_path } "
157+ def singleton_related_url_helper_name ( relationship )
158+ relationship_parts = [ ]
159+ relationship_parts << relationship . name
160+ relationship_parts += resource_path_parts_from_class ( relationship . parent_resource )
161+ url_helper_name_from_parts ( relationship_parts )
138162 end
139163
140- def regular_resource_path ( source )
141- "#{ regular_resources_path ( source . class ) } /#{ source . id } "
164+ def relationship_self_url_helper_name ( relationship )
165+ relationship_parts = resource_path_parts_from_class ( relationship . parent_resource )
166+ relationship_parts << "relationships"
167+ relationship_parts << relationship . name
168+ url_helper_name_from_parts ( relationship_parts )
142169 end
143170
144- def regular_resource_url ( source )
145- "#{ base_url } #{ regular_resource_path ( source ) } "
171+ def singleton_relationship_self_url_helper_name ( relationship )
172+ relationship_parts = [ ]
173+ relationship_parts << "relationships"
174+ relationship_parts << relationship . name
175+ relationship_parts += resource_path_parts_from_class ( relationship . parent_resource )
176+ url_helper_name_from_parts ( relationship_parts )
146177 end
147178
148- def route_for_relationship ( relationship )
149- format_route ( relationship . name )
179+ def module_scopes_from_class ( klass )
180+ klass . name . to_s . split ( "::" ) [ 0 ...- 1 ]
150181 end
151182 end
152183end
0 commit comments