@@ -2,74 +2,85 @@ module JSONAPI
2
2
class LinkBuilder
3
3
attr_reader :base_url ,
4
4
:primary_resource_klass ,
5
+ :route_formatter ,
5
6
:engine ,
6
- :routes
7
+ :engine_mount_point ,
8
+ :url_helpers
9
+
10
+ @@url_helper_methods = { }
7
11
8
12
def initialize ( config = { } )
9
- @base_url = config [ :base_url ]
13
+ @base_url = config [ :base_url ]
10
14
@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 ( { } ) : ""
18
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
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 ]
22
23
end
23
24
24
25
def engine?
25
26
!!@engine
26
27
end
27
28
28
29
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
32
40
end
33
41
34
42
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 } "
36
46
end
37
47
38
48
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
42
53
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
45
59
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
52
60
end
53
61
54
62
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 ) } "
58
65
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
61
71
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
67
72
end
68
73
69
74
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
73
84
end
74
85
75
86
private
@@ -81,105 +92,55 @@ def build_engine
81
92
unless scopes . empty?
82
93
"#{ scopes . first . to_s . camelize } ::Engine" . safe_constantize
83
94
end
95
+
84
96
# :nocov:
85
97
rescue LoadError => _e
86
98
nil
87
99
# :nocov:
88
100
end
89
101
end
90
102
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 )
95
105
end
96
106
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
101
113
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 ( '/' ) } /"
106
116
else
107
- call_url_helper ( url_helper_name , source . id )
117
+ "/"
108
118
end
109
119
end
110
120
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 ]
113
123
end
114
124
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 )
117
127
end
118
128
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 ) } "
134
131
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 } "
140
134
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
171
136
end
172
137
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 ) } "
179
140
end
180
141
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 )
183
144
end
184
145
end
185
146
end
0 commit comments