@@ -2,59 +2,79 @@ 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
@@ -68,93 +88,96 @@ def build_engine_name
68
88
end
69
89
end
70
90
71
- def engine_path_from_resource_class ( klass )
72
- path_name = engine_resources_path_name_from_class ( klass )
73
- 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
74
95
end
75
96
76
- def engine_primary_resources_path
77
- 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 )
78
100
end
79
101
80
- def engine_primary_resources_url
81
- "#{ 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
82
109
end
83
110
84
- def engine_resource_path ( source )
85
- resource_path_name = engine_resource_path_name_from_source ( source )
86
- 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 )
87
113
end
88
114
89
- def engine_resource_path_name_from_source ( source )
90
- scopes = module_scopes_from_class ( source . class ) [ 1 ..-1 ]
91
- base_path_name = scopes . map { |scope | scope . underscore } . join ( "_" )
92
- end_path_name = source . class . _type . to_s . singularize
93
- [ base_path_name , end_path_name , "path" ] . reject ( &:blank? ) . join ( "_" )
115
+ def url_helper_name_from_parts ( parts )
116
+ ( parts << "path" ) . reject ( &:blank? ) . join ( "_" )
94
117
end
95
118
96
- def engine_resource_url ( source )
97
- "#{ base_url } #{ engine_resource_path ( source ) } "
98
- 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
99
125
100
- def engine_resources_path_name_from_class ( klass )
101
- scopes = module_scopes_from_class ( klass ) [ 1 ..-1 ]
102
126
base_path_name = scopes . map { |scope | scope . underscore } . join ( "_" )
103
127
end_path_name = klass . _type . to_s
104
-
105
- if base_path_name . blank?
106
- "#{ end_path_name } _path"
107
- else
108
- "#{ base_path_name } _#{ end_path_name } _path"
109
- end
128
+ [ base_path_name , end_path_name ]
110
129
end
111
130
112
- def format_route ( route )
113
- 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 ) )
114
133
end
115
134
116
- def formatted_module_path_from_class ( klass )
117
- scopes = module_scopes_from_class ( klass )
118
-
119
- unless scopes . empty?
120
- "/#{ scopes . map { |scope | format_route ( scope . to_s . underscore ) } . compact . join ( '/' ) } /"
135
+ def resource_path_parts_from_class ( klass )
136
+ if engine?
137
+ scopes = module_scopes_from_class ( klass ) [ 1 ..-1 ]
121
138
else
122
- "/"
139
+ scopes = module_scopes_from_class ( klass )
123
140
end
124
- end
125
141
126
- def module_scopes_from_class ( klass )
127
- 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 ]
128
145
end
129
146
130
- def regular_resources_path ( source_klass )
131
- @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 ) )
132
149
end
133
150
134
- def regular_primary_resources_path
135
- 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 )
136
155
end
137
156
138
- def regular_primary_resources_url
139
- "#{ 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 )
140
162
end
141
163
142
- def regular_resource_path ( source )
143
- if source . is_a? ( JSONAPI ::CachedResponseFragment )
144
- # :nocov:
145
- "#{ regular_resources_path ( source . resource_klass ) } /#{ source . id } "
146
- # :nocov:
147
- else
148
- "#{ regular_resources_path ( source . class ) } /#{ source . id } "
149
- end
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 )
150
169
end
151
170
152
- def regular_resource_url ( source )
153
- "#{ 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 )
154
177
end
155
178
156
- def route_for_relationship ( relationship )
157
- format_route ( relationship . name )
179
+ def module_scopes_from_class ( klass )
180
+ klass . name . to_s . split ( "::" ) [ 0 ...- 1 ]
158
181
end
159
182
end
160
183
end
0 commit comments