jsonapi-resources 0.9.6 → 0.9.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/jsonapi/acts_as_resource_controller.rb +1 -1
- data/lib/jsonapi/configuration.rb +5 -0
- data/lib/jsonapi/link_builder.rb +103 -72
- data/lib/jsonapi/operation_result.rb +1 -1
- data/lib/jsonapi/processor.rb +3 -3
- data/lib/jsonapi/relationship.rb +35 -0
- data/lib/jsonapi/request_parser.rb +18 -1
- data/lib/jsonapi/resource.rb +54 -0
- data/lib/jsonapi/resource_serializer.rb +39 -31
- data/lib/jsonapi/resources/version.rb +1 -1
- data/lib/jsonapi/response_document.rb +14 -9
- data/lib/jsonapi/routing_ext.rb +32 -16
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3fc3aa84a3eee153f95a97c3d982e6fef51146998c0860ed4986f3c3911eb5c4
|
4
|
+
data.tar.gz: c75e87dad876b378b4aef634c7103b6399b171b8392831b1a0365146c82c02f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a35bf550f5cdd014236b4ec680526bc1041a024644ba83a036e1d57c2c7e837456f713ad7f3f4bca95bacf7cc50bc7588fcfdf3550c96eb3802bf086a48dac43
|
7
|
+
data.tar.gz: b322f9b3d22e6a3667f4b938559c3cf73af93fc54ad18311b443acafce36594cac7e2c8166c3b204e9ef50b07cb39f17b39edcbfa342d7a41434b359e7c9bd35
|
@@ -234,7 +234,7 @@ module JSONAPI
|
|
234
234
|
end
|
235
235
|
|
236
236
|
render_options[:location] = content[:data]["links"][:self] if (
|
237
|
-
response_doc.status == :created && content[:data].class != Array
|
237
|
+
response_doc.status == :created && content[:data].class != Array && content[:data]["links"]
|
238
238
|
)
|
239
239
|
|
240
240
|
# For whatever reason, `render` ignores :status and :content_type when :body is set.
|
@@ -8,6 +8,7 @@ module JSONAPI
|
|
8
8
|
:resource_key_type,
|
9
9
|
:route_format,
|
10
10
|
:raise_if_parameters_not_allowed,
|
11
|
+
:warn_on_missing_routes,
|
11
12
|
:allow_include,
|
12
13
|
:allow_sort,
|
13
14
|
:allow_filter,
|
@@ -51,6 +52,8 @@ module JSONAPI
|
|
51
52
|
|
52
53
|
self.raise_if_parameters_not_allowed = true
|
53
54
|
|
55
|
+
self.warn_on_missing_routes = true
|
56
|
+
|
54
57
|
# :none, :offset, :paged, or a custom paginator name
|
55
58
|
self.default_paginator = :none
|
56
59
|
|
@@ -235,6 +238,8 @@ module JSONAPI
|
|
235
238
|
|
236
239
|
attr_writer :raise_if_parameters_not_allowed
|
237
240
|
|
241
|
+
attr_writer :warn_on_missing_routes
|
242
|
+
|
238
243
|
attr_writer :use_relationship_reflection
|
239
244
|
|
240
245
|
attr_writer :resource_cache
|
data/lib/jsonapi/link_builder.rb
CHANGED
@@ -2,32 +2,33 @@ module JSONAPI
|
|
2
2
|
class LinkBuilder
|
3
3
|
attr_reader :base_url,
|
4
4
|
:primary_resource_klass,
|
5
|
-
:
|
6
|
-
:
|
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
|
-
@
|
12
|
-
@engine_name = build_engine_name
|
11
|
+
@engine = build_engine
|
13
12
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
!!@
|
25
|
+
!!@engine
|
23
26
|
end
|
24
27
|
|
25
28
|
def primary_resources_url
|
26
|
-
|
27
|
-
|
28
|
-
|
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)
|
@@ -35,118 +36,148 @@ module JSONAPI
|
|
35
36
|
end
|
36
37
|
|
37
38
|
def relationships_related_link(source, relationship, query_params = {})
|
38
|
-
|
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
|
-
|
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
|
-
|
49
|
-
|
50
|
-
|
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
|
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
|
-
|
84
|
+
# :nocov:
|
85
|
+
rescue LoadError => _e
|
65
86
|
nil
|
87
|
+
# :nocov:
|
66
88
|
end
|
67
89
|
end
|
68
90
|
|
69
|
-
def
|
70
|
-
|
71
|
-
|
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
|
75
|
-
|
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
|
79
|
-
|
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
|
83
|
-
|
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
|
88
|
-
|
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
|
95
|
-
|
96
|
-
|
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
|
111
|
-
|
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
|
115
|
-
|
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
|
-
|
125
|
-
klass.
|
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
|
129
|
-
|
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
|
133
|
-
|
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
|
137
|
-
|
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
|
141
|
-
|
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
|
145
|
-
|
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
|
149
|
-
|
179
|
+
def module_scopes_from_class(klass)
|
180
|
+
klass.name.to_s.split("::")[0...-1]
|
150
181
|
end
|
151
182
|
end
|
152
183
|
end
|
@@ -53,7 +53,7 @@ module JSONAPI
|
|
53
53
|
end
|
54
54
|
end
|
55
55
|
|
56
|
-
class
|
56
|
+
class RelationshipOperationResult < OperationResult
|
57
57
|
attr_accessor :parent_resource, :relationship
|
58
58
|
|
59
59
|
def initialize(code, parent_resource, relationship, options = {})
|
data/lib/jsonapi/processor.rb
CHANGED
@@ -136,9 +136,9 @@ module JSONAPI
|
|
136
136
|
|
137
137
|
parent_resource = resource_klass.find_by_key(parent_key, context: context)
|
138
138
|
|
139
|
-
return JSONAPI::
|
140
|
-
|
141
|
-
|
139
|
+
return JSONAPI::RelationshipOperationResult.new(:ok,
|
140
|
+
parent_resource,
|
141
|
+
resource_klass._relationship(relationship_type))
|
142
142
|
end
|
143
143
|
|
144
144
|
def show_related_resource
|
data/lib/jsonapi/relationship.rb
CHANGED
@@ -14,6 +14,8 @@ module JSONAPI
|
|
14
14
|
@polymorphic = options.fetch(:polymorphic, false) == true
|
15
15
|
@always_include_linkage_data = options.fetch(:always_include_linkage_data, false) == true
|
16
16
|
@eager_load_on_include = options.fetch(:eager_load_on_include, true) == true
|
17
|
+
|
18
|
+
exclude_links(options.fetch(:exclude_links, :none))
|
17
19
|
end
|
18
20
|
|
19
21
|
alias_method :polymorphic?, :polymorphic
|
@@ -60,6 +62,27 @@ module JSONAPI
|
|
60
62
|
false
|
61
63
|
end
|
62
64
|
|
65
|
+
def exclude_links(exclude)
|
66
|
+
case exclude
|
67
|
+
when :default, "default"
|
68
|
+
@_exclude_links = [:self, :related]
|
69
|
+
when :none, "none"
|
70
|
+
@_exclude_links = []
|
71
|
+
when Array
|
72
|
+
@_exclude_links = exclude.collect {|link| link.to_sym}
|
73
|
+
else
|
74
|
+
fail "Invalid exclude_links"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def _exclude_links
|
79
|
+
@_exclude_links ||= []
|
80
|
+
end
|
81
|
+
|
82
|
+
def exclude_link?(link)
|
83
|
+
_exclude_links.include?(link.to_sym)
|
84
|
+
end
|
85
|
+
|
63
86
|
class ToOne < Relationship
|
64
87
|
attr_reader :foreign_key_on
|
65
88
|
|
@@ -70,6 +93,12 @@ module JSONAPI
|
|
70
93
|
@foreign_key_on = options.fetch(:foreign_key_on, :self)
|
71
94
|
end
|
72
95
|
|
96
|
+
def to_s
|
97
|
+
# :nocov:
|
98
|
+
"#{parent_resource}.#{name}(#{belongs_to? ? 'BelongsToOne' : 'ToOne'})"
|
99
|
+
# :nocov:
|
100
|
+
end
|
101
|
+
|
73
102
|
def belongs_to?
|
74
103
|
foreign_key_on == :self
|
75
104
|
end
|
@@ -89,6 +118,12 @@ module JSONAPI
|
|
89
118
|
@reflect = options.fetch(:reflect, true) == true
|
90
119
|
@inverse_relationship = options.fetch(:inverse_relationship, parent_resource._type.to_s.singularize.to_sym) if parent_resource
|
91
120
|
end
|
121
|
+
|
122
|
+
def to_s
|
123
|
+
# :nocov:
|
124
|
+
"#{parent_resource}.#{name}(ToMany)"
|
125
|
+
# :nocov:
|
126
|
+
end
|
92
127
|
end
|
93
128
|
end
|
94
129
|
end
|
@@ -52,6 +52,7 @@ module JSONAPI
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def setup_get_related_resource_action(params)
|
55
|
+
resolve_singleton_id(params)
|
55
56
|
initialize_source(params)
|
56
57
|
parse_fields(params[:fields])
|
57
58
|
parse_include_directives(params[:include])
|
@@ -63,6 +64,7 @@ module JSONAPI
|
|
63
64
|
end
|
64
65
|
|
65
66
|
def setup_get_related_resources_action(params)
|
67
|
+
resolve_singleton_id(params)
|
66
68
|
initialize_source(params)
|
67
69
|
parse_fields(params[:fields])
|
68
70
|
parse_include_directives(params[:include])
|
@@ -74,6 +76,7 @@ module JSONAPI
|
|
74
76
|
end
|
75
77
|
|
76
78
|
def setup_show_action(params)
|
79
|
+
resolve_singleton_id(params)
|
77
80
|
parse_fields(params[:fields])
|
78
81
|
parse_include_directives(params[:include])
|
79
82
|
parse_filters(params[:filter])
|
@@ -83,6 +86,7 @@ module JSONAPI
|
|
83
86
|
end
|
84
87
|
|
85
88
|
def setup_show_relationship_action(params)
|
89
|
+
resolve_singleton_id(params)
|
86
90
|
add_show_relationship_operation(params[:relationship], params.require(@resource_klass._as_parent_key))
|
87
91
|
end
|
88
92
|
|
@@ -93,24 +97,29 @@ module JSONAPI
|
|
93
97
|
end
|
94
98
|
|
95
99
|
def setup_create_relationship_action(params)
|
100
|
+
resolve_singleton_id(params)
|
96
101
|
parse_modify_relationship_action(params, :add)
|
97
102
|
end
|
98
103
|
|
99
104
|
def setup_update_relationship_action(params)
|
105
|
+
resolve_singleton_id(params)
|
100
106
|
parse_modify_relationship_action(params, :update)
|
101
107
|
end
|
102
108
|
|
103
109
|
def setup_update_action(params)
|
110
|
+
resolve_singleton_id(params)
|
104
111
|
parse_fields(params[:fields])
|
105
112
|
parse_include_directives(params[:include])
|
106
113
|
parse_replace_operation(params.require(:data), params[:id])
|
107
114
|
end
|
108
115
|
|
109
116
|
def setup_destroy_action(params)
|
117
|
+
resolve_singleton_id(params)
|
110
118
|
parse_remove_operation(params)
|
111
119
|
end
|
112
120
|
|
113
121
|
def setup_destroy_relationship_action(params)
|
122
|
+
resolve_singleton_id(params)
|
114
123
|
parse_modify_relationship_action(params, :remove)
|
115
124
|
end
|
116
125
|
|
@@ -694,7 +703,8 @@ module JSONAPI
|
|
694
703
|
end
|
695
704
|
|
696
705
|
def parse_replace_operation(data, keys)
|
697
|
-
parse_single_replace_operation(data, [keys],
|
706
|
+
parse_single_replace_operation(data, [keys],
|
707
|
+
id_key_presence_check_required: keys.present? && !@resource_klass.singleton?)
|
698
708
|
rescue JSONAPI::Exceptions::Error => e
|
699
709
|
@errors.concat(e.errors)
|
700
710
|
end
|
@@ -725,6 +735,13 @@ module JSONAPI
|
|
725
735
|
end
|
726
736
|
end
|
727
737
|
|
738
|
+
def resolve_singleton_id(params)
|
739
|
+
if @resource_klass.singleton? && params[:id].nil?
|
740
|
+
key = @resource_klass.singleton_key(context)
|
741
|
+
params[:id] = key
|
742
|
+
end
|
743
|
+
end
|
744
|
+
|
728
745
|
def format_key(key)
|
729
746
|
@key_formatter.format(key)
|
730
747
|
end
|
data/lib/jsonapi/resource.rb
CHANGED
@@ -435,6 +435,8 @@ module JSONAPI
|
|
435
435
|
subclass.abstract(false)
|
436
436
|
subclass.immutable(false)
|
437
437
|
subclass.caching(false)
|
438
|
+
subclass.singleton(singleton?, (_singleton_options.dup || {}))
|
439
|
+
subclass.exclude_links(_exclude_links)
|
438
440
|
subclass._attributes = (_attributes || {}).dup
|
439
441
|
|
440
442
|
subclass._model_hints = (_model_hints || {}).dup
|
@@ -606,6 +608,19 @@ module JSONAPI
|
|
606
608
|
_model_hints[model.to_s.gsub('::', '/').underscore] = resource_type.to_s
|
607
609
|
end
|
608
610
|
|
611
|
+
def singleton(*attrs)
|
612
|
+
@_singleton = (!!attrs[0] == attrs[0]) ? attrs[0] : true
|
613
|
+
@_singleton_options = attrs.extract_options!
|
614
|
+
end
|
615
|
+
|
616
|
+
def _singleton_options
|
617
|
+
@_singleton_options ||= {}
|
618
|
+
end
|
619
|
+
|
620
|
+
def singleton?
|
621
|
+
@_singleton ||= false
|
622
|
+
end
|
623
|
+
|
609
624
|
def filters(*attrs)
|
610
625
|
@_allowed_filters.merge!(attrs.inject({}) { |h, attr| h[attr] = {}; h })
|
611
626
|
end
|
@@ -908,6 +923,24 @@ module JSONAPI
|
|
908
923
|
@_resource_key_type ||= JSONAPI.configuration.resource_key_type
|
909
924
|
end
|
910
925
|
|
926
|
+
# override to all resolution of masked ids to actual ids. Because singleton routes do not specify the id this
|
927
|
+
# will be needed to allow lookup of singleton resources. Alternately singleton resources can override
|
928
|
+
# `verify_key`
|
929
|
+
def singleton_key(context)
|
930
|
+
if @_singleton_options && @_singleton_options[:singleton_key]
|
931
|
+
strategy = @_singleton_options[:singleton_key]
|
932
|
+
case strategy
|
933
|
+
when Proc
|
934
|
+
key = strategy.call(context)
|
935
|
+
when Symbol, String
|
936
|
+
key = send(strategy, context)
|
937
|
+
else
|
938
|
+
raise "singleton_key must be a proc or function name"
|
939
|
+
end
|
940
|
+
end
|
941
|
+
key
|
942
|
+
end
|
943
|
+
|
911
944
|
def verify_key(key, context = nil)
|
912
945
|
key_type = resource_key_type
|
913
946
|
|
@@ -1032,6 +1065,27 @@ module JSONAPI
|
|
1032
1065
|
!@immutable
|
1033
1066
|
end
|
1034
1067
|
|
1068
|
+
def exclude_links(exclude)
|
1069
|
+
case exclude
|
1070
|
+
when :default, "default"
|
1071
|
+
@_exclude_links = [:self]
|
1072
|
+
when :none, "none"
|
1073
|
+
@_exclude_links = []
|
1074
|
+
when Array
|
1075
|
+
@_exclude_links = exclude.collect {|link| link.to_sym}
|
1076
|
+
else
|
1077
|
+
fail "Invalid exclude_links"
|
1078
|
+
end
|
1079
|
+
end
|
1080
|
+
|
1081
|
+
def _exclude_links
|
1082
|
+
@_exclude_links ||= []
|
1083
|
+
end
|
1084
|
+
|
1085
|
+
def exclude_link?(link)
|
1086
|
+
_exclude_links.include?(link.to_sym)
|
1087
|
+
end
|
1088
|
+
|
1035
1089
|
def caching(val = true)
|
1036
1090
|
@caching = val
|
1037
1091
|
end
|
@@ -90,20 +90,19 @@ module JSONAPI
|
|
90
90
|
primary_hash
|
91
91
|
end
|
92
92
|
|
93
|
-
def
|
93
|
+
def serialize_to_relationship_hash(source, requested_relationship)
|
94
94
|
if requested_relationship.is_a?(JSONAPI::Relationship::ToOne)
|
95
95
|
data = to_one_linkage(source, requested_relationship)
|
96
96
|
else
|
97
97
|
data = to_many_linkage(source, requested_relationship)
|
98
98
|
end
|
99
99
|
|
100
|
-
{
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
}
|
100
|
+
rel_hash = { 'data': data }
|
101
|
+
|
102
|
+
links = default_relationship_links(source, requested_relationship)
|
103
|
+
rel_hash['links'] = links unless links.blank?
|
104
|
+
|
105
|
+
rel_hash
|
107
106
|
end
|
108
107
|
|
109
108
|
def query_link(query_params)
|
@@ -133,7 +132,6 @@ module JSONAPI
|
|
133
132
|
supplying_attribute_fields: supplying_attribute_fields(resource_klass).sort,
|
134
133
|
supplying_relationship_fields: supplying_relationship_fields(resource_klass).sort,
|
135
134
|
link_builder_base_url: link_builder.base_url,
|
136
|
-
route_formatter_class: link_builder.route_formatter.uncached.class.name,
|
137
135
|
key_formatter_class: key_formatter.uncached.class.name,
|
138
136
|
always_include_to_one_linkage_data: always_include_to_one_linkage_data,
|
139
137
|
always_include_to_many_linkage_data: always_include_to_many_linkage_data
|
@@ -152,7 +150,7 @@ module JSONAPI
|
|
152
150
|
obj_hash['attributes'] = source.attributes_json if source.attributes_json
|
153
151
|
|
154
152
|
relationships = cached_relationships_hash(source, include_directives)
|
155
|
-
obj_hash['relationships'] = relationships unless relationships.
|
153
|
+
obj_hash['relationships'] = relationships unless relationships.blank?
|
156
154
|
|
157
155
|
obj_hash['meta'] = source.meta_json if source.meta_json
|
158
156
|
else
|
@@ -173,7 +171,7 @@ module JSONAPI
|
|
173
171
|
obj_hash['attributes'] = attributes unless attributes.empty?
|
174
172
|
|
175
173
|
relationships = relationships_hash(source, fetchable_fields, include_directives)
|
176
|
-
obj_hash['relationships'] = relationships unless relationships.
|
174
|
+
obj_hash['relationships'] = relationships unless relationships.blank?
|
177
175
|
|
178
176
|
meta = meta_hash(source)
|
179
177
|
obj_hash['meta'] = meta unless meta.empty?
|
@@ -251,7 +249,9 @@ module JSONAPI
|
|
251
249
|
|
252
250
|
def links_hash(source)
|
253
251
|
links = custom_links_hash(source)
|
254
|
-
links
|
252
|
+
if !links.key?('self') && !source.class.exclude_link?(:self)
|
253
|
+
links['self'] = link_builder.self_link(source)
|
254
|
+
end
|
255
255
|
links.compact
|
256
256
|
end
|
257
257
|
|
@@ -289,7 +289,8 @@ module JSONAPI
|
|
289
289
|
|
290
290
|
options = { filters: ia && ia[:include_filters] || {} }
|
291
291
|
if field_set.include?(name)
|
292
|
-
|
292
|
+
ro = relationship_object(source, relationship, include_linkage)
|
293
|
+
hash[format_key(name)] = ro unless ro.blank?
|
293
294
|
end
|
294
295
|
|
295
296
|
# If the object has been serialized once it will be in the related objects list,
|
@@ -381,6 +382,13 @@ module JSONAPI
|
|
381
382
|
link_builder.relationships_related_link(source, relationship)
|
382
383
|
end
|
383
384
|
|
385
|
+
def default_relationship_links(source, relationship)
|
386
|
+
links = {}
|
387
|
+
links['self'] = self_link(source, relationship) unless relationship.exclude_link?(:self)
|
388
|
+
links['related'] = related_link(source, relationship) unless relationship.exclude_link?(:related)
|
389
|
+
links.compact
|
390
|
+
end
|
391
|
+
|
384
392
|
def to_one_linkage(source, relationship)
|
385
393
|
linkage_id = foreign_key_value(source, relationship)
|
386
394
|
linkage_type = format_key(relationship.type_for_source(source))
|
@@ -428,31 +436,32 @@ module JSONAPI
|
|
428
436
|
linkage
|
429
437
|
end
|
430
438
|
|
431
|
-
def
|
439
|
+
def relationship_object_to_one(source, relationship, include_linkage)
|
432
440
|
include_linkage = include_linkage | @always_include_to_one_linkage_data | relationship.always_include_linkage_data
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
441
|
+
relationship_object_hash = {}
|
442
|
+
|
443
|
+
links = default_relationship_links(source, relationship)
|
444
|
+
|
445
|
+
relationship_object_hash['links'] = links unless links.blank?
|
446
|
+
relationship_object_hash[:data] = to_one_linkage(source, relationship) if include_linkage
|
447
|
+
relationship_object_hash
|
439
448
|
end
|
440
449
|
|
441
|
-
def
|
450
|
+
def relationship_object_to_many(source, relationship, include_linkage)
|
442
451
|
include_linkage = include_linkage | relationship.always_include_linkage_data
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
452
|
+
relationship_object_hash = {}
|
453
|
+
|
454
|
+
links = default_relationship_links(source, relationship)
|
455
|
+
relationship_object_hash['links'] = links unless links.blank?
|
456
|
+
relationship_object_hash[:data] = to_many_linkage(source, relationship) if include_linkage
|
457
|
+
relationship_object_hash
|
449
458
|
end
|
450
459
|
|
451
|
-
def
|
460
|
+
def relationship_object(source, relationship, include_linkage = false)
|
452
461
|
if relationship.is_a?(JSONAPI::Relationship::ToOne)
|
453
|
-
|
462
|
+
relationship_object_to_one(source, relationship, include_linkage)
|
454
463
|
elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
|
455
|
-
|
464
|
+
relationship_object_to_many(source, relationship, include_linkage)
|
456
465
|
end
|
457
466
|
end
|
458
467
|
|
@@ -528,7 +537,6 @@ module JSONAPI
|
|
528
537
|
def generate_link_builder(primary_resource_klass, options)
|
529
538
|
LinkBuilder.new(
|
530
539
|
base_url: options.fetch(:base_url, ''),
|
531
|
-
route_formatter: options.fetch(:route_formatter, JSONAPI.configuration.route_formatter),
|
532
540
|
primary_resource_klass: primary_resource_klass,
|
533
541
|
)
|
534
542
|
end
|
@@ -64,14 +64,19 @@ module JSONAPI
|
|
64
64
|
|
65
65
|
# Build pagination links
|
66
66
|
if result.is_a?(JSONAPI::ResourcesOperationResult) || result.is_a?(JSONAPI::RelatedResourcesOperationResult)
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
67
|
+
result.pagination_params.each_pair do |link_name, params|
|
68
|
+
if result.is_a?(JSONAPI::RelatedResourcesOperationResult)
|
69
|
+
relationship = result.source_resource.class._relationships[result._type.to_sym]
|
70
|
+
unless relationship.exclude_link?(link_name)
|
71
|
+
link = @serializer.link_builder.relationships_related_link(result.source_resource, relationship, query_params(params))
|
72
|
+
end
|
73
|
+
else
|
74
|
+
unless @serializer.link_builder.primary_resource_klass.exclude_link?(link_name)
|
75
|
+
link = @serializer.link_builder.query_link(query_params(params))
|
73
76
|
end
|
74
77
|
end
|
78
|
+
links[link_name] = link unless link.blank?
|
79
|
+
end
|
75
80
|
end
|
76
81
|
end
|
77
82
|
|
@@ -109,9 +114,9 @@ module JSONAPI
|
|
109
114
|
@serializer.serialize_to_hash(result.resource)
|
110
115
|
when JSONAPI::ResourcesOperationResult
|
111
116
|
@serializer.serialize_to_hash(result.resources)
|
112
|
-
when JSONAPI::
|
113
|
-
@serializer.
|
114
|
-
|
117
|
+
when JSONAPI::RelationshipOperationResult
|
118
|
+
@serializer.serialize_to_relationship_hash(result.parent_resource,
|
119
|
+
result.relationship)
|
115
120
|
when JSONAPI::OperationResult
|
116
121
|
{}
|
117
122
|
end
|
data/lib/jsonapi/routing_ext.rb
CHANGED
@@ -20,6 +20,10 @@ module ActionDispatch
|
|
20
20
|
@resource_type = resources.first
|
21
21
|
res = JSONAPI::Resource.resource_for(resource_type_with_module_prefix(@resource_type))
|
22
22
|
|
23
|
+
unless res.singleton?
|
24
|
+
warn "Singleton routes created for non singleton resource #{res}. Links may not be generated correctly."
|
25
|
+
end
|
26
|
+
|
23
27
|
options = resources.extract_options!.dup
|
24
28
|
options[:controller] ||= @resource_type
|
25
29
|
options.merge!(res.routing_resource_options)
|
@@ -80,6 +84,10 @@ module ActionDispatch
|
|
80
84
|
@resource_type = resources.first
|
81
85
|
res = JSONAPI::Resource.resource_for(resource_type_with_module_prefix(@resource_type))
|
82
86
|
|
87
|
+
if res.singleton?
|
88
|
+
warn "Singleton resource #{res} should use `jsonapi_resource` instead."
|
89
|
+
end
|
90
|
+
|
83
91
|
options = resources.extract_options!.dup
|
84
92
|
options[:controller] ||= @resource_type
|
85
93
|
options.merge!(res.routing_resource_options)
|
@@ -153,19 +161,23 @@ module ActionDispatch
|
|
153
161
|
methods = links_methods(options)
|
154
162
|
|
155
163
|
if methods.include?(:show)
|
156
|
-
match "relationships/#{formatted_relationship_name}",
|
157
|
-
|
164
|
+
match "relationships/#{formatted_relationship_name}",
|
165
|
+
controller: options[:controller],
|
166
|
+
action: 'show_relationship', relationship: link_type.to_s, via: [:get],
|
167
|
+
as: "relationships/#{link_type}"
|
158
168
|
end
|
159
169
|
|
160
170
|
if res.mutable?
|
161
171
|
if methods.include?(:update)
|
162
|
-
match "relationships/#{formatted_relationship_name}",
|
163
|
-
|
172
|
+
match "relationships/#{formatted_relationship_name}",
|
173
|
+
controller: options[:controller],
|
174
|
+
action: 'update_relationship', relationship: link_type.to_s, via: [:put, :patch]
|
164
175
|
end
|
165
176
|
|
166
177
|
if methods.include?(:destroy)
|
167
|
-
match "relationships/#{formatted_relationship_name}",
|
168
|
-
|
178
|
+
match "relationships/#{formatted_relationship_name}",
|
179
|
+
controller: options[:controller],
|
180
|
+
action: 'destroy_relationship', relationship: link_type.to_s, via: [:delete]
|
169
181
|
end
|
170
182
|
end
|
171
183
|
end
|
@@ -182,23 +194,24 @@ module ActionDispatch
|
|
182
194
|
|
183
195
|
if methods.include?(:show)
|
184
196
|
match "relationships/#{formatted_relationship_name}", controller: options[:controller],
|
185
|
-
|
197
|
+
action: 'show_relationship', relationship: link_type.to_s, via: [:get],
|
198
|
+
as: "relationships/#{link_type}"
|
186
199
|
end
|
187
200
|
|
188
201
|
if res.mutable?
|
189
202
|
if methods.include?(:create)
|
190
203
|
match "relationships/#{formatted_relationship_name}", controller: options[:controller],
|
191
|
-
|
204
|
+
action: 'create_relationship', relationship: link_type.to_s, via: [:post]
|
192
205
|
end
|
193
206
|
|
194
207
|
if methods.include?(:update)
|
195
208
|
match "relationships/#{formatted_relationship_name}", controller: options[:controller],
|
196
|
-
|
209
|
+
action: 'update_relationship', relationship: link_type.to_s, via: [:put, :patch]
|
197
210
|
end
|
198
211
|
|
199
212
|
if methods.include?(:destroy)
|
200
213
|
match "relationships/#{formatted_relationship_name}", controller: options[:controller],
|
201
|
-
|
214
|
+
action: 'destroy_relationship', relationship: link_type.to_s, via: [:delete]
|
202
215
|
end
|
203
216
|
end
|
204
217
|
end
|
@@ -219,9 +232,10 @@ module ActionDispatch
|
|
219
232
|
options[:controller] ||= related_resource._type.to_s
|
220
233
|
end
|
221
234
|
|
222
|
-
match
|
223
|
-
|
224
|
-
|
235
|
+
match formatted_relationship_name, controller: options[:controller],
|
236
|
+
relationship: relationship.name, source: resource_type_with_module_prefix(source._type),
|
237
|
+
action: 'get_related_resource', via: [:get],
|
238
|
+
as: relationship_name
|
225
239
|
end
|
226
240
|
|
227
241
|
def jsonapi_related_resources(*relationship)
|
@@ -235,9 +249,11 @@ module ActionDispatch
|
|
235
249
|
related_resource = JSONAPI::Resource.resource_for(resource_type_with_module_prefix(relationship.class_name.underscore))
|
236
250
|
options[:controller] ||= related_resource._type.to_s
|
237
251
|
|
238
|
-
match
|
239
|
-
|
240
|
-
|
252
|
+
match formatted_relationship_name,
|
253
|
+
controller: options[:controller],
|
254
|
+
relationship: relationship.name, source: resource_type_with_module_prefix(source._type),
|
255
|
+
action: 'get_related_resources', via: [:get],
|
256
|
+
as: relationship_name
|
241
257
|
end
|
242
258
|
|
243
259
|
protected
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jsonapi-resources
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dan Gebhardt
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2019-
|
12
|
+
date: 2019-05-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|