jsonapi-resources 0.9.6 → 0.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|