@@ -2,74 +2,85 @@ module JSONAPI
22 class LinkBuilder
33 attr_reader :base_url ,
44 :primary_resource_klass ,
5+ :route_formatter ,
56 :engine ,
6- :routes
7+ :engine_mount_point ,
8+ :url_helpers
9+
10+ @@url_helper_methods = { }
711
812 def initialize ( config = { } )
9- @base_url = config [ :base_url ]
13+ @base_url = config [ :base_url ]
1014 @primary_resource_klass = config [ :primary_resource_klass ]
11- @engine = build_engine
12-
13- if engine?
14- @routes = @engine . routes
15- else
16- @routes = Rails . application . routes
17- end
15+ @route_formatter = config [ :route_formatter ]
16+ @engine = build_engine
17+ @engine_mount_point = @engine ? @engine . routes . find_script_name ( { } ) : ""
1818
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
19+ # url_helpers may be either a controller which has the route helper methods, or the application router's
20+ # url helpers module, `Rails.application.routes.url_helpers`. Because the method no longer behaves as a
21+ # singleton, and it's expensive to generate the module, the controller is preferred.
22+ @url_helpers = config [ :url_helpers ]
2223 end
2324
2425 def engine?
2526 !!@engine
2627 end
2728
2829 def primary_resources_url
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
30+ if @primary_resource_klass . _routed
31+ primary_resources_path = resources_path ( primary_resource_klass )
32+ @primary_resources_url_cached ||= "#{ base_url } #{ engine_mount_point } #{ primary_resources_path } "
33+ else
34+ if JSONAPI . configuration . warn_on_missing_routes && !@primary_resource_klass . _warned_missing_route
35+ warn "primary_resources_url for #{ @primary_resource_klass } could not be generated"
36+ @primary_resource_klass . _warned_missing_route = true
37+ end
38+ nil
39+ end
3240 end
3341
3442 def query_link ( query_params )
35- "#{ primary_resources_url } ?#{ query_params . to_query } "
43+ url = primary_resources_url
44+ return url if url . nil?
45+ "#{ url } ?#{ query_params . to_query } "
3646 end
3747
3848 def relationships_related_link ( source , relationship , query_params = { } )
39- if relationship . parent_resource . singleton?
40- url_helper_name = singleton_related_url_helper_name ( relationship )
41- url = call_url_helper ( url_helper_name )
49+ if relationship . _routed
50+ url = "#{ self_link ( source ) } /#{ route_for_relationship ( relationship ) } "
51+ url = "#{ url } ?#{ query_params . to_query } " if query_params . present?
52+ url
4253 else
43- url_helper_name = related_url_helper_name ( relationship )
44- url = call_url_helper ( url_helper_name , source . id )
54+ if JSONAPI . configuration . warn_on_missing_routes && !relationship . _warned_missing_route
55+ warn "related_link for #{ relationship } could not be generated"
56+ relationship . _warned_missing_route = true
57+ end
58+ nil
4559 end
46-
47- url = "#{ base_url } #{ url } "
48- url = "#{ url } ?#{ query_params . to_query } " if query_params . present?
49- url
50- rescue NoMethodError
51- warn "related_link for #{ relationship } could not be generated" if JSONAPI . configuration . warn_on_missing_routes
5260 end
5361
5462 def relationships_self_link ( source , 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 )
63+ if relationship . _routed
64+ "#{ self_link ( source ) } /relationships/#{ route_for_relationship ( relationship ) } "
5865 else
59- url_helper_name = relationship_self_url_helper_name ( relationship )
60- url = call_url_helper ( url_helper_name , source . id )
66+ if JSONAPI . configuration . warn_on_missing_routes && !relationship . _warned_missing_route
67+ warn "self_link for #{ relationship } could not be generated"
68+ relationship . _warned_missing_route = true
69+ end
70+ nil
6171 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
6772 end
6873
6974 def self_link ( source )
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
75+ if source . class . _routed
76+ resource_url ( source )
77+ else
78+ if JSONAPI . configuration . warn_on_missing_routes && !source . class . _warned_missing_route
79+ warn "self_link for #{ source . class } could not be generated"
80+ source . class . _warned_missing_route = true
81+ end
82+ nil
83+ end
7384 end
7485
7586 private
@@ -81,105 +92,55 @@ def build_engine
8192 unless scopes . empty?
8293 "#{ scopes . first . to_s . camelize } ::Engine" . safe_constantize
8394 end
95+
8496 # :nocov:
8597 rescue LoadError => _e
8698 nil
8799 # :nocov:
88100 end
89101 end
90102
91- def call_url_helper ( method , *args )
92- routes . url_helpers . public_send ( method , args )
93- rescue NoMethodError => e
94- raise e
103+ def format_route ( route )
104+ route_formatter . format ( route )
95105 end
96106
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 )
100- end
107+ def formatted_module_path_from_class ( klass )
108+ scopes = if @engine
109+ module_scopes_from_class ( klass ) [ 1 ..-1 ]
110+ else
111+ module_scopes_from_class ( klass )
112+ end
101113
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 )
114+ unless scopes . empty?
115+ "/#{ scopes . map { |scope | format_route ( scope . to_s . underscore ) } . compact . join ( '/' ) } /"
106116 else
107- call_url_helper ( url_helper_name , source . id )
117+ "/"
108118 end
109119 end
110120
111- def primary_resources_path
112- path_from_resource_class ( primary_resource_klass )
121+ def module_scopes_from_class ( klass )
122+ klass . name . to_s . split ( "::" ) [ 0 ...- 1 ]
113123 end
114124
115- def url_helper_name_from_parts ( parts )
116- ( parts << "path" ) . reject ( & :blank? ) . join ( "_" )
125+ def resources_path ( source_klass )
126+ formatted_module_path_from_class ( source_klass ) + format_route ( source_klass . _type . to_s )
117127 end
118128
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
125-
126- base_path_name = scopes . map { |scope | scope . underscore } . join ( "_" )
127- end_path_name = klass . _type . to_s
128- [ base_path_name , end_path_name ]
129- end
130-
131- def resources_url_helper_name_from_class ( klass )
132- url_helper_name_from_parts ( resources_path_parts_from_class ( klass ) )
133- end
129+ def resource_path ( source )
130+ url = "#{ resources_path ( source . class ) } "
134131
135- def resource_path_parts_from_class ( klass )
136- if engine?
137- scopes = module_scopes_from_class ( klass ) [ 1 ..-1 ]
138- else
139- scopes = module_scopes_from_class ( klass )
132+ unless source . class . singleton?
133+ url = "#{ url } /#{ source . id } "
140134 end
141-
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 ]
145- end
146-
147- def resource_url_helper_name_from_source ( source )
148- url_helper_name_from_parts ( resource_path_parts_from_class ( source . class ) )
149- end
150-
151- def related_url_helper_name ( relationship )
152- relationship_parts = resource_path_parts_from_class ( relationship . parent_resource )
153- relationship_parts << "related"
154- relationship_parts << relationship . name
155- url_helper_name_from_parts ( relationship_parts )
156- end
157-
158- def singleton_related_url_helper_name ( relationship )
159- relationship_parts = [ ]
160- relationship_parts << "related"
161- relationship_parts << relationship . name
162- relationship_parts += resource_path_parts_from_class ( relationship . parent_resource )
163- url_helper_name_from_parts ( relationship_parts )
164- end
165-
166- def relationship_self_url_helper_name ( relationship )
167- relationship_parts = resource_path_parts_from_class ( relationship . parent_resource )
168- relationship_parts << "relationships"
169- relationship_parts << relationship . name
170- url_helper_name_from_parts ( relationship_parts )
135+ url
171136 end
172137
173- def singleton_relationship_self_url_helper_name ( relationship )
174- relationship_parts = [ ]
175- relationship_parts << "relationships"
176- relationship_parts << relationship . name
177- relationship_parts += resource_path_parts_from_class ( relationship . parent_resource )
178- url_helper_name_from_parts ( relationship_parts )
138+ def resource_url ( source )
139+ "#{ base_url } #{ engine_mount_point } #{ resource_path ( source ) } "
179140 end
180141
181- def module_scopes_from_class ( klass )
182- klass . name . to_s . split ( "::" ) [ 0 ...- 1 ]
142+ def route_for_relationship ( relationship )
143+ format_route ( relationship . name )
183144 end
184145 end
185146end
0 commit comments