@@ -2,151 +2,182 @@ module JSONAPI
2
2
class LinkBuilder
3
3
attr_reader :base_url ,
4
4
:primary_resource_klass ,
5
- :route_formatter ,
6
- :engine_name
5
+ :engine ,
6
+ :routes
7
7
8
8
def initialize ( config = { } )
9
9
@base_url = config [ :base_url ]
10
10
@primary_resource_klass = config [ :primary_resource_klass ]
11
- @route_formatter = config [ :route_formatter ]
12
- @engine_name = build_engine_name
11
+ @engine = build_engine
13
12
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
18
17
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
19
22
end
20
23
21
24
def engine?
22
- !!@engine_name
25
+ !!@engine
23
26
end
24
27
25
28
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
31
32
end
32
33
33
34
def query_link ( query_params )
34
35
"#{ primary_resources_url } ?#{ query_params . to_query } "
35
36
end
36
37
37
38
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 } "
39
48
url = "#{ url } ?#{ query_params . to_query } " if query_params . present?
40
49
url
50
+ rescue NoMethodError
51
+ warn "related_link for #{ relationship } could not be generated" if JSONAPI . configuration . warn_on_missing_routes
41
52
end
42
53
43
54
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
45
67
end
46
68
47
69
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
53
73
end
54
74
55
75
private
56
76
57
- def build_engine_name
77
+ def build_engine
58
78
scopes = module_scopes_from_class ( primary_resource_klass )
59
79
60
80
begin
61
81
unless scopes . empty?
62
82
"#{ scopes . first . to_s . camelize } ::Engine" . safe_constantize
63
83
end
64
- rescue LoadError => e
84
+ # :nocov:
85
+ rescue LoadError => _e
65
86
nil
87
+ # :nocov:
66
88
end
67
89
end
68
90
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
72
95
end
73
96
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 )
76
100
end
77
101
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
80
109
end
81
110
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 )
85
113
end
86
114
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 ( "_" )
92
117
end
93
118
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
97
125
98
- def engine_resources_path_name_from_class ( klass )
99
- scopes = module_scopes_from_class ( klass ) [ 1 ..-1 ]
100
126
base_path_name = scopes . map { |scope | scope . underscore } . join ( "_" )
101
127
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 ]
108
129
end
109
130
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 ) )
112
133
end
113
134
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 ]
119
138
else
120
- "/"
139
+ scopes = module_scopes_from_class ( klass )
121
140
end
122
- end
123
141
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 ]
126
145
end
127
146
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 ) )
130
149
end
131
150
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 )
134
155
end
135
156
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 )
138
162
end
139
163
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 )
142
169
end
143
170
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 )
146
177
end
147
178
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 ]
150
181
end
151
182
end
152
183
end
0 commit comments