jsonapi-resources 0.9.12 → 0.10.0.beta1
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 +5 -5
- data/LICENSE.txt +1 -1
- data/README.md +34 -11
- data/lib/bug_report_templates/rails_5_latest.rb +125 -0
- data/lib/bug_report_templates/rails_5_master.rb +140 -0
- data/lib/jsonapi-resources.rb +8 -3
- data/lib/jsonapi/active_relation_resource_finder.rb +640 -0
- data/lib/jsonapi/active_relation_resource_finder/join_tree.rb +126 -0
- data/lib/jsonapi/acts_as_resource_controller.rb +121 -106
- data/lib/jsonapi/{cached_resource_fragment.rb → cached_response_fragment.rb} +13 -30
- data/lib/jsonapi/compiled_json.rb +11 -1
- data/lib/jsonapi/configuration.rb +44 -18
- data/lib/jsonapi/error.rb +27 -0
- data/lib/jsonapi/exceptions.rb +43 -40
- data/lib/jsonapi/formatter.rb +3 -3
- data/lib/jsonapi/include_directives.rb +2 -45
- data/lib/jsonapi/link_builder.rb +87 -80
- data/lib/jsonapi/operation.rb +16 -5
- data/lib/jsonapi/operation_result.rb +74 -16
- data/lib/jsonapi/processor.rb +233 -112
- data/lib/jsonapi/relationship.rb +77 -53
- data/lib/jsonapi/request_parser.rb +378 -423
- data/lib/jsonapi/resource.rb +224 -524
- data/lib/jsonapi/resource_controller_metal.rb +2 -2
- data/lib/jsonapi/resource_fragment.rb +47 -0
- data/lib/jsonapi/resource_id_tree.rb +112 -0
- data/lib/jsonapi/resource_identity.rb +42 -0
- data/lib/jsonapi/resource_serializer.rb +133 -301
- data/lib/jsonapi/resource_set.rb +108 -0
- data/lib/jsonapi/resources/version.rb +1 -1
- data/lib/jsonapi/response_document.rb +100 -88
- data/lib/jsonapi/routing_ext.rb +21 -43
- metadata +29 -45
- data/lib/jsonapi/operation_dispatcher.rb +0 -88
- data/lib/jsonapi/operation_results.rb +0 -35
- data/lib/jsonapi/relationship_builder.rb +0 -167
data/lib/jsonapi/relationship.rb
CHANGED
@@ -2,9 +2,10 @@ module JSONAPI
|
|
2
2
|
class Relationship
|
3
3
|
attr_reader :acts_as_set, :foreign_key, :options, :name,
|
4
4
|
:class_name, :polymorphic, :always_include_linkage_data,
|
5
|
-
:parent_resource, :eager_load_on_include
|
5
|
+
:parent_resource, :eager_load_on_include, :custom_methods,
|
6
|
+
:inverse_relationship, :allow_include
|
6
7
|
|
7
|
-
|
8
|
+
attr_writer :allow_include
|
8
9
|
|
9
10
|
def initialize(name, options = {})
|
10
11
|
@name = name.to_s
|
@@ -14,26 +15,51 @@ module JSONAPI
|
|
14
15
|
@parent_resource = options[:parent_resource]
|
15
16
|
@relation_name = options.fetch(:relation_name, @name)
|
16
17
|
@polymorphic = options.fetch(:polymorphic, false) == true
|
18
|
+
@polymorphic_relations = options[:polymorphic_relations]
|
17
19
|
@always_include_linkage_data = options.fetch(:always_include_linkage_data, false) == true
|
18
|
-
@eager_load_on_include = options.fetch(:eager_load_on_include,
|
19
|
-
@
|
20
|
-
@
|
20
|
+
@eager_load_on_include = options.fetch(:eager_load_on_include, false) == true
|
21
|
+
@allow_include = options[:allow_include]
|
22
|
+
@class_name = nil
|
23
|
+
@inverse_relationship = nil
|
21
24
|
|
22
|
-
|
25
|
+
# Custom methods are reserved for use in resource finders. Not used in the default ActiveRelationResourceFinder
|
26
|
+
@custom_methods = options.fetch(:custom_methods, {})
|
23
27
|
end
|
24
28
|
|
25
29
|
alias_method :polymorphic?, :polymorphic
|
26
30
|
|
27
31
|
def primary_key
|
32
|
+
# :nocov:
|
28
33
|
@primary_key ||= resource_klass._primary_key
|
34
|
+
# :nocov:
|
29
35
|
end
|
30
36
|
|
31
37
|
def resource_klass
|
32
|
-
@resource_klass ||= @parent_resource.
|
38
|
+
@resource_klass ||= @parent_resource.resource_klass_for(@class_name)
|
33
39
|
end
|
34
40
|
|
35
41
|
def table_name
|
42
|
+
# :nocov:
|
36
43
|
@table_name ||= resource_klass._table_name
|
44
|
+
# :nocov:
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.polymorphic_types(name)
|
48
|
+
@poly_hash ||= {}.tap do |hash|
|
49
|
+
ObjectSpace.each_object do |klass|
|
50
|
+
next unless Module === klass
|
51
|
+
if ActiveRecord::Base > klass
|
52
|
+
klass.reflect_on_all_associations(:has_many).select{|r| r.options[:as] }.each do |reflection|
|
53
|
+
(hash[reflection.options[:as]] ||= []) << klass.name.downcase
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
@poly_hash[name.to_sym]
|
59
|
+
end
|
60
|
+
|
61
|
+
def polymorphic_relations
|
62
|
+
@polymorphic_relations ||= self.class.polymorphic_types(@relation_name)
|
37
63
|
end
|
38
64
|
|
39
65
|
def type
|
@@ -53,44 +79,14 @@ module JSONAPI
|
|
53
79
|
end
|
54
80
|
end
|
55
81
|
|
56
|
-
def type_for_source(source)
|
57
|
-
if polymorphic?
|
58
|
-
# try polymorphic type column before asking it from the resource record
|
59
|
-
if source._model.respond_to?(polymorphic_type)
|
60
|
-
model_type = source._model.send(polymorphic_type)
|
61
|
-
source.class.resource_for(model_type)._type if model_type
|
62
|
-
else
|
63
|
-
resource = source.public_send(name)
|
64
|
-
resource.class._type if resource
|
65
|
-
end
|
66
|
-
else
|
67
|
-
type
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
82
|
def belongs_to?
|
83
|
+
# :nocov:
|
72
84
|
false
|
85
|
+
# :nocov:
|
73
86
|
end
|
74
87
|
|
75
|
-
def
|
76
|
-
|
77
|
-
when :default, "default"
|
78
|
-
@_exclude_links = [:self, :related]
|
79
|
-
when :none, "none"
|
80
|
-
@_exclude_links = []
|
81
|
-
when Array
|
82
|
-
@_exclude_links = exclude.collect {|link| link.to_sym}
|
83
|
-
else
|
84
|
-
fail "Invalid exclude_links"
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
def _exclude_links
|
89
|
-
@_exclude_links ||= []
|
90
|
-
end
|
91
|
-
|
92
|
-
def exclude_link?(link)
|
93
|
-
_exclude_links.include?(link.to_sym)
|
88
|
+
def readonly?
|
89
|
+
@options[:readonly]
|
94
90
|
end
|
95
91
|
|
96
92
|
class ToOne < Relationship
|
@@ -101,38 +97,66 @@ module JSONAPI
|
|
101
97
|
@class_name = options.fetch(:class_name, name.to_s.camelize)
|
102
98
|
@foreign_key ||= "#{name}_id".to_sym
|
103
99
|
@foreign_key_on = options.fetch(:foreign_key_on, :self)
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
# :nocov:
|
108
|
-
"#{parent_resource}.#{name}(#{belongs_to? ? 'BelongsToOne' : 'ToOne'})"
|
109
|
-
# :nocov:
|
100
|
+
if parent_resource
|
101
|
+
@inverse_relationship = options.fetch(:inverse_relationship, parent_resource._type)
|
102
|
+
end
|
110
103
|
end
|
111
104
|
|
112
105
|
def belongs_to?
|
106
|
+
# :nocov:
|
113
107
|
foreign_key_on == :self
|
108
|
+
# :nocov:
|
114
109
|
end
|
115
110
|
|
116
111
|
def polymorphic_type
|
117
112
|
"#{name}_type" if polymorphic?
|
118
113
|
end
|
114
|
+
|
115
|
+
def allow_include?(context = nil)
|
116
|
+
strategy = if @allow_include.nil?
|
117
|
+
JSONAPI.configuration.default_allow_include_to_one
|
118
|
+
else
|
119
|
+
@allow_include
|
120
|
+
end
|
121
|
+
|
122
|
+
if !!strategy == strategy #check for boolean
|
123
|
+
return strategy
|
124
|
+
elsif strategy.is_a?(Symbol) || strategy.is_a?(String)
|
125
|
+
parent_resource.send(strategy, context)
|
126
|
+
else
|
127
|
+
strategy.call(context)
|
128
|
+
end
|
129
|
+
end
|
119
130
|
end
|
120
131
|
|
121
132
|
class ToMany < Relationship
|
122
|
-
attr_reader :reflect
|
133
|
+
attr_reader :reflect
|
123
134
|
|
124
135
|
def initialize(name, options = {})
|
125
136
|
super
|
126
137
|
@class_name = options.fetch(:class_name, name.to_s.camelize.singularize)
|
127
138
|
@foreign_key ||= "#{name.to_s.singularize}_ids".to_sym
|
128
139
|
@reflect = options.fetch(:reflect, true) == true
|
129
|
-
|
140
|
+
if parent_resource
|
141
|
+
@inverse_relationship = options.fetch(:inverse_relationship, parent_resource._type.to_s.singularize.to_sym)
|
142
|
+
end
|
130
143
|
end
|
131
144
|
|
132
|
-
def
|
133
|
-
|
134
|
-
|
135
|
-
|
145
|
+
def allow_include?(context = nil)
|
146
|
+
strategy = if @allow_include.nil?
|
147
|
+
JSONAPI.configuration.default_allow_include_to_many
|
148
|
+
else
|
149
|
+
@allow_include
|
150
|
+
end
|
151
|
+
|
152
|
+
if !!strategy == strategy #check for boolean
|
153
|
+
return strategy
|
154
|
+
elsif strategy.is_a?(Symbol) || strategy.is_a?(String)
|
155
|
+
parent_resource.send(strategy, context)
|
156
|
+
else
|
157
|
+
strategy.call(context)
|
158
|
+
end
|
159
|
+
|
136
160
|
end
|
137
161
|
end
|
138
162
|
end
|
@@ -1,132 +1,253 @@
|
|
1
|
-
require 'jsonapi/operation'
|
2
|
-
require 'jsonapi/paginator'
|
3
|
-
|
4
1
|
module JSONAPI
|
5
2
|
class RequestParser
|
6
|
-
attr_accessor :fields, :include, :filters, :sort_criteria, :errors, :
|
7
|
-
:
|
3
|
+
attr_accessor :fields, :include, :filters, :sort_criteria, :errors, :controller_module_path,
|
4
|
+
:context, :paginator, :source_klass, :source_id,
|
8
5
|
:include_directives, :params, :warnings, :server_error_callbacks
|
9
6
|
|
10
7
|
def initialize(params = nil, options = {})
|
11
8
|
@params = params
|
9
|
+
if params
|
10
|
+
controller_path = params.fetch(:controller, '')
|
11
|
+
@controller_module_path = controller_path.include?('/') ? controller_path.rpartition('/').first + '/' : ''
|
12
|
+
else
|
13
|
+
@controller_module_path = ''
|
14
|
+
end
|
15
|
+
|
12
16
|
@context = options[:context]
|
13
17
|
@key_formatter = options.fetch(:key_formatter, JSONAPI.configuration.key_formatter)
|
14
18
|
@errors = []
|
15
19
|
@warnings = []
|
16
|
-
@operations = []
|
17
|
-
@fields = {}
|
18
|
-
@filters = {}
|
19
|
-
@sort_criteria = nil
|
20
|
-
@source_klass = nil
|
21
|
-
@source_id = nil
|
22
|
-
@include_directives = nil
|
23
|
-
@paginator = nil
|
24
|
-
@id = nil
|
25
20
|
@server_error_callbacks = options.fetch(:server_error_callbacks, [])
|
21
|
+
end
|
22
|
+
|
23
|
+
def error_object_overrides
|
24
|
+
{}
|
25
|
+
end
|
26
|
+
|
27
|
+
def each(_response_document)
|
28
|
+
operation = setup_base_op(params)
|
29
|
+
if @errors.any?
|
30
|
+
fail JSONAPI::Exceptions::Errors.new(@errors)
|
31
|
+
else
|
32
|
+
yield operation
|
33
|
+
end
|
34
|
+
rescue ActionController::ParameterMissing => e
|
35
|
+
fail JSONAPI::Exceptions::ParameterMissing.new(e.param, error_object_overrides)
|
36
|
+
end
|
26
37
|
|
27
|
-
|
38
|
+
def transactional?
|
39
|
+
case params[:action]
|
40
|
+
when 'index', 'show_related_resource', 'index_related_resources', 'show', 'show_relationship'
|
41
|
+
return false
|
42
|
+
else
|
43
|
+
return true
|
44
|
+
end
|
28
45
|
end
|
29
46
|
|
30
|
-
def
|
47
|
+
def setup_base_op(params)
|
31
48
|
return if params.nil?
|
32
49
|
|
33
|
-
|
50
|
+
resource_klass = Resource.resource_klass_for(params[:controller]) if params[:controller]
|
34
51
|
|
35
52
|
setup_action_method_name = "setup_#{params[:action]}_action"
|
36
53
|
if respond_to?(setup_action_method_name)
|
37
54
|
raise params[:_parser_exception] if params[:_parser_exception]
|
38
|
-
send(setup_action_method_name, params)
|
55
|
+
send(setup_action_method_name, params, resource_klass)
|
39
56
|
end
|
40
57
|
rescue ActionController::ParameterMissing => e
|
41
|
-
@errors.concat(JSONAPI::Exceptions::ParameterMissing.new(e.param).errors)
|
58
|
+
@errors.concat(JSONAPI::Exceptions::ParameterMissing.new(e.param, error_object_overrides).errors)
|
59
|
+
rescue JSONAPI::Exceptions::Error => e
|
60
|
+
e.error_object_overrides.merge! error_object_overrides
|
61
|
+
@errors.concat(e.errors)
|
42
62
|
end
|
43
63
|
|
44
|
-
def setup_index_action(params)
|
45
|
-
parse_fields(params[:fields])
|
46
|
-
parse_include_directives(params[:include])
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
64
|
+
def setup_index_action(params, resource_klass)
|
65
|
+
fields = parse_fields(resource_klass, params[:fields])
|
66
|
+
include_directives = parse_include_directives(resource_klass, params[:include])
|
67
|
+
filters = parse_filters(resource_klass, params[:filter])
|
68
|
+
sort_criteria = parse_sort_criteria(resource_klass, params[:sort])
|
69
|
+
paginator = parse_pagination(resource_klass, params[:page])
|
70
|
+
|
71
|
+
JSONAPI::Operation.new(
|
72
|
+
:find,
|
73
|
+
resource_klass,
|
74
|
+
context: context,
|
75
|
+
filters: filters,
|
76
|
+
include_directives: include_directives,
|
77
|
+
sort_criteria: sort_criteria,
|
78
|
+
paginator: paginator,
|
79
|
+
fields: fields
|
80
|
+
)
|
52
81
|
end
|
53
82
|
|
54
|
-
def
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
83
|
+
def setup_show_related_resource_action(params, resource_klass)
|
84
|
+
source_klass = Resource.resource_klass_for(params.require(:source))
|
85
|
+
source_id = source_klass.verify_key(params.require(source_klass._as_parent_key), @context)
|
86
|
+
|
87
|
+
fields = parse_fields(resource_klass, params[:fields])
|
88
|
+
include_directives = parse_include_directives(resource_klass, params[:include])
|
89
|
+
|
90
|
+
relationship_type = params[:relationship].to_sym
|
91
|
+
|
92
|
+
JSONAPI::Operation.new(
|
93
|
+
:show_related_resource,
|
94
|
+
resource_klass,
|
95
|
+
context: @context,
|
96
|
+
relationship_type: relationship_type,
|
97
|
+
source_klass: source_klass,
|
98
|
+
source_id: source_id,
|
99
|
+
fields: fields,
|
100
|
+
include_directives: include_directives
|
101
|
+
)
|
64
102
|
end
|
65
103
|
|
66
|
-
def
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
parse_filters(params[:filter])
|
73
|
-
parse_sort_criteria(params[:sort])
|
74
|
-
parse_pagination(params[:page])
|
75
|
-
|
104
|
+
def setup_index_related_resources_action(params, resource_klass)
|
105
|
+
source_klass = Resource.resource_klass_for(params.require(:source))
|
106
|
+
source_id = source_klass.verify_key(params.require(source_klass._as_parent_key), @context)
|
107
|
+
|
108
|
+
fields = parse_fields(resource_klass, params[:fields])
|
109
|
+
include_directives = parse_include_directives(resource_klass, params[:include])
|
110
|
+
filters = parse_filters(resource_klass, params[:filter])
|
111
|
+
sort_criteria = parse_sort_criteria(resource_klass, params[:sort])
|
112
|
+
paginator = parse_pagination(resource_klass, params[:page])
|
113
|
+
relationship_type = params[:relationship]
|
114
|
+
|
115
|
+
JSONAPI::Operation.new(
|
116
|
+
:show_related_resources,
|
117
|
+
resource_klass,
|
118
|
+
context: @context,
|
119
|
+
relationship_type: relationship_type,
|
120
|
+
source_klass: source_klass,
|
121
|
+
source_id: source_id,
|
122
|
+
filters: filters,
|
123
|
+
sort_criteria: sort_criteria,
|
124
|
+
paginator: paginator,
|
125
|
+
fields: fields,
|
126
|
+
include_directives: include_directives
|
127
|
+
)
|
76
128
|
end
|
77
129
|
|
78
|
-
def setup_show_action(params)
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
parse_filters(params[:filter])
|
130
|
+
def setup_show_action(params, resource_klass)
|
131
|
+
fields = parse_fields(resource_klass, params[:fields])
|
132
|
+
include_directives = parse_include_directives(resource_klass, params[:include])
|
133
|
+
id = params[:id]
|
83
134
|
|
84
|
-
|
85
|
-
|
135
|
+
JSONAPI::Operation.new(
|
136
|
+
:show,
|
137
|
+
resource_klass,
|
138
|
+
context: @context,
|
139
|
+
id: id,
|
140
|
+
include_directives: include_directives,
|
141
|
+
fields: fields,
|
142
|
+
allowed_resources: params[:allowed_resources]
|
143
|
+
)
|
86
144
|
end
|
87
145
|
|
88
|
-
def setup_show_relationship_action(params)
|
89
|
-
|
90
|
-
|
146
|
+
def setup_show_relationship_action(params, resource_klass)
|
147
|
+
relationship_type = params[:relationship]
|
148
|
+
parent_key = params.require(resource_klass._as_parent_key)
|
149
|
+
include_directives = parse_include_directives(resource_klass, params[:include])
|
150
|
+
filters = parse_filters(resource_klass, params[:filter])
|
151
|
+
sort_criteria = parse_sort_criteria(resource_klass, params[:sort])
|
152
|
+
paginator = parse_pagination(resource_klass, params[:page])
|
153
|
+
|
154
|
+
JSONAPI::Operation.new(
|
155
|
+
:show_relationship,
|
156
|
+
resource_klass,
|
157
|
+
context: @context,
|
158
|
+
relationship_type: relationship_type,
|
159
|
+
parent_key: resource_klass.verify_key(parent_key),
|
160
|
+
filters: filters,
|
161
|
+
sort_criteria: sort_criteria,
|
162
|
+
paginator: paginator,
|
163
|
+
fields: fields,
|
164
|
+
include_directives: include_directives
|
165
|
+
)
|
91
166
|
end
|
92
167
|
|
93
|
-
def setup_create_action(params)
|
94
|
-
parse_fields(params[:fields])
|
95
|
-
parse_include_directives(params[:include])
|
96
|
-
|
168
|
+
def setup_create_action(params, resource_klass)
|
169
|
+
fields = parse_fields(resource_klass, params[:fields])
|
170
|
+
include_directives = parse_include_directives(resource_klass, params[:include])
|
171
|
+
|
172
|
+
data = params.require(:data)
|
173
|
+
|
174
|
+
unless data.respond_to?(:each_pair)
|
175
|
+
fail JSONAPI::Exceptions::InvalidDataFormat.new(error_object_overrides)
|
176
|
+
end
|
177
|
+
|
178
|
+
verify_type(data[:type], resource_klass)
|
179
|
+
|
180
|
+
data = parse_params(resource_klass, data, resource_klass.creatable_fields(@context))
|
181
|
+
|
182
|
+
JSONAPI::Operation.new(
|
183
|
+
:create_resource,
|
184
|
+
resource_klass,
|
185
|
+
context: @context,
|
186
|
+
data: data,
|
187
|
+
fields: fields,
|
188
|
+
include_directives: include_directives,
|
189
|
+
warnings: @warnings
|
190
|
+
)
|
97
191
|
end
|
98
192
|
|
99
|
-
def setup_create_relationship_action(params)
|
100
|
-
|
101
|
-
parse_modify_relationship_action(params, :add)
|
193
|
+
def setup_create_relationship_action(params, resource_klass)
|
194
|
+
parse_modify_relationship_action(:add, params, resource_klass)
|
102
195
|
end
|
103
196
|
|
104
|
-
def setup_update_relationship_action(params)
|
105
|
-
|
106
|
-
parse_modify_relationship_action(params, :update)
|
197
|
+
def setup_update_relationship_action(params, resource_klass)
|
198
|
+
parse_modify_relationship_action(:update, params, resource_klass)
|
107
199
|
end
|
108
200
|
|
109
|
-
def setup_update_action(params)
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
201
|
+
def setup_update_action(params, resource_klass)
|
202
|
+
fields = parse_fields(resource_klass, params[:fields])
|
203
|
+
include_directives = parse_include_directives(resource_klass, params[:include])
|
204
|
+
|
205
|
+
data = params.require(:data)
|
206
|
+
key = params[:id]
|
207
|
+
|
208
|
+
fail JSONAPI::Exceptions::InvalidDataFormat.new(error_object_overrides) unless data.respond_to?(:each_pair)
|
209
|
+
|
210
|
+
fail JSONAPI::Exceptions::MissingKey.new(error_object_overrides) if data[:id].nil?
|
211
|
+
|
212
|
+
resource_id = data.require(:id)
|
213
|
+
# Singleton resources may not have the ID set in the URL
|
214
|
+
if key
|
215
|
+
fail JSONAPI::Exceptions::KeyNotIncludedInURL.new(resource_id) if key.to_s != resource_id.to_s
|
216
|
+
end
|
217
|
+
|
218
|
+
data.delete(:id)
|
219
|
+
|
220
|
+
verify_type(data[:type], resource_klass)
|
221
|
+
|
222
|
+
JSONAPI::Operation.new(
|
223
|
+
:replace_fields,
|
224
|
+
resource_klass,
|
225
|
+
context: @context,
|
226
|
+
resource_id: resource_id,
|
227
|
+
data: parse_params(resource_klass, data, resource_klass.updatable_fields(@context)),
|
228
|
+
fields: fields,
|
229
|
+
include_directives: include_directives,
|
230
|
+
warnings: @warnings
|
231
|
+
)
|
114
232
|
end
|
115
233
|
|
116
|
-
def setup_destroy_action(params)
|
117
|
-
|
118
|
-
|
234
|
+
def setup_destroy_action(params, resource_klass)
|
235
|
+
JSONAPI::Operation.new(
|
236
|
+
:remove_resource,
|
237
|
+
resource_klass,
|
238
|
+
context: @context,
|
239
|
+
resource_id: resource_klass.verify_key(params.require(:id), @context))
|
119
240
|
end
|
120
241
|
|
121
|
-
def setup_destroy_relationship_action(params)
|
122
|
-
|
123
|
-
parse_modify_relationship_action(params, :remove)
|
242
|
+
def setup_destroy_relationship_action(params, resource_klass)
|
243
|
+
parse_modify_relationship_action(:remove, params, resource_klass)
|
124
244
|
end
|
125
245
|
|
126
|
-
def parse_modify_relationship_action(params,
|
246
|
+
def parse_modify_relationship_action(modification_type, params, resource_klass)
|
127
247
|
relationship_type = params.require(:relationship)
|
128
|
-
|
129
|
-
|
248
|
+
|
249
|
+
parent_key = params.require(resource_klass._as_parent_key)
|
250
|
+
relationship = resource_klass._relationship(relationship_type)
|
130
251
|
|
131
252
|
# Removals of to-one relationships are done implicitly and require no specification of data
|
132
253
|
data_required = !(modification_type == :remove && relationship.is_a?(JSONAPI::Relationship::ToOne))
|
@@ -134,75 +255,74 @@ module JSONAPI
|
|
134
255
|
if data_required
|
135
256
|
data = params.fetch(:data)
|
136
257
|
object_params = { relationships: { format_key(relationship.name) => { data: data } } }
|
137
|
-
verified_params = parse_params(object_params, @resource_klass.updatable_fields(@context))
|
138
258
|
|
139
|
-
|
259
|
+
verified_params = parse_params(resource_klass, object_params, resource_klass.updatable_fields(@context))
|
260
|
+
|
261
|
+
parse_arguments = [resource_klass, verified_params, relationship, parent_key]
|
140
262
|
else
|
141
|
-
parse_arguments = [params, relationship, parent_key]
|
263
|
+
parse_arguments = [resource_klass, params, relationship, parent_key]
|
142
264
|
end
|
143
265
|
|
144
266
|
send(:"parse_#{modification_type}_relationship_operation", *parse_arguments)
|
145
267
|
end
|
146
268
|
|
147
|
-
def
|
148
|
-
|
149
|
-
|
269
|
+
def parse_pagination(resource_klass, page)
|
270
|
+
paginator_name = resource_klass._paginator
|
271
|
+
JSONAPI::Paginator.paginator_for(paginator_name).new(page) unless paginator_name == :none
|
150
272
|
end
|
151
273
|
|
152
|
-
def
|
153
|
-
paginator_name = @resource_klass._paginator
|
154
|
-
@paginator = JSONAPI::Paginator.paginator_for(paginator_name).new(page) unless paginator_name == :none
|
155
|
-
rescue JSONAPI::Exceptions::Error => e
|
156
|
-
@errors.concat(e.errors)
|
157
|
-
end
|
158
|
-
|
159
|
-
def parse_fields(fields)
|
160
|
-
return if fields.nil?
|
161
|
-
|
274
|
+
def parse_fields(resource_klass, fields)
|
162
275
|
extracted_fields = {}
|
163
276
|
|
277
|
+
return extracted_fields if fields.nil?
|
278
|
+
|
164
279
|
# Extract the fields for each type from the fields parameters
|
165
280
|
if fields.is_a?(ActionController::Parameters)
|
166
281
|
fields.each do |field, value|
|
167
|
-
|
282
|
+
if value.is_a?(Array)
|
283
|
+
resource_fields = value
|
284
|
+
else
|
285
|
+
resource_fields = value.split(',') unless value.nil? || value.empty?
|
286
|
+
end
|
168
287
|
extracted_fields[field] = resource_fields
|
169
288
|
end
|
170
289
|
else
|
171
|
-
fail JSONAPI::Exceptions::InvalidFieldFormat.new
|
290
|
+
fail JSONAPI::Exceptions::InvalidFieldFormat.new(error_object_overrides)
|
172
291
|
end
|
173
292
|
|
174
293
|
# Validate the fields
|
294
|
+
validated_fields = {}
|
175
295
|
extracted_fields.each do |type, values|
|
176
296
|
underscored_type = unformat_key(type)
|
177
|
-
|
297
|
+
validated_fields[type] = []
|
178
298
|
begin
|
179
299
|
if type != format_key(type)
|
180
|
-
fail JSONAPI::Exceptions::InvalidResource.new(type)
|
300
|
+
fail JSONAPI::Exceptions::InvalidResource.new(type, error_object_overrides)
|
181
301
|
end
|
182
|
-
type_resource = Resource.
|
302
|
+
type_resource = Resource.resource_klass_for(resource_klass.module_path + underscored_type.to_s)
|
183
303
|
rescue NameError
|
184
|
-
fail JSONAPI::Exceptions::InvalidResource.new(type)
|
304
|
+
fail JSONAPI::Exceptions::InvalidResource.new(type, error_object_overrides)
|
185
305
|
end
|
186
306
|
|
187
307
|
if type_resource.nil?
|
188
|
-
fail JSONAPI::Exceptions::InvalidResource.new(type)
|
308
|
+
fail JSONAPI::Exceptions::InvalidResource.new(type, error_object_overrides)
|
189
309
|
else
|
190
310
|
unless values.nil?
|
191
311
|
valid_fields = type_resource.fields.collect { |key| format_key(key) }
|
192
312
|
values.each do |field|
|
193
313
|
if valid_fields.include?(field)
|
194
|
-
|
314
|
+
validated_fields[type].push unformat_key(field)
|
195
315
|
else
|
196
|
-
fail JSONAPI::Exceptions::InvalidField.new(type, field)
|
316
|
+
fail JSONAPI::Exceptions::InvalidField.new(type, field, error_object_overrides)
|
197
317
|
end
|
198
318
|
end
|
199
319
|
else
|
200
|
-
fail JSONAPI::Exceptions::InvalidField.new(type, 'nil')
|
320
|
+
fail JSONAPI::Exceptions::InvalidField.new(type, 'nil', error_object_overrides)
|
201
321
|
end
|
202
322
|
end
|
203
323
|
end
|
204
324
|
|
205
|
-
|
325
|
+
validated_fields.deep_transform_keys { |key| unformat_key(key) }
|
206
326
|
end
|
207
327
|
|
208
328
|
def check_include(resource_klass, include_parts)
|
@@ -210,243 +330,146 @@ module JSONAPI
|
|
210
330
|
|
211
331
|
relationship = resource_klass._relationship(relationship_name)
|
212
332
|
if relationship && format_key(relationship_name) == include_parts.first
|
333
|
+
unless relationship.allow_include?(context)
|
334
|
+
fail JSONAPI::Exceptions::InvalidInclude.new(format_key(resource_klass._type), include_parts.first)
|
335
|
+
end
|
336
|
+
|
213
337
|
unless include_parts.last.empty?
|
214
|
-
check_include(Resource.
|
338
|
+
check_include(Resource.resource_klass_for(resource_klass.module_path + relationship.class_name.to_s.underscore),
|
339
|
+
include_parts.last.partition('.'))
|
215
340
|
end
|
216
341
|
else
|
217
342
|
fail JSONAPI::Exceptions::InvalidInclude.new(format_key(resource_klass._type), include_parts.first)
|
218
343
|
end
|
344
|
+
true
|
219
345
|
end
|
220
346
|
|
221
|
-
def parse_include_directives(raw_include)
|
347
|
+
def parse_include_directives(resource_klass, raw_include)
|
222
348
|
return unless raw_include
|
223
349
|
|
224
|
-
unless JSONAPI.configuration.allow_include
|
225
|
-
fail JSONAPI::Exceptions::ParameterNotAllowed.new(:include)
|
226
|
-
end
|
227
|
-
|
228
350
|
included_resources = []
|
229
351
|
begin
|
230
|
-
included_resources += Array
|
352
|
+
included_resources += raw_include.is_a?(Array) ? raw_include : CSV.parse_line(raw_include) || []
|
231
353
|
rescue CSV::MalformedCSVError
|
232
|
-
fail JSONAPI::Exceptions::InvalidInclude.new(format_key(
|
354
|
+
fail JSONAPI::Exceptions::InvalidInclude.new(format_key(resource_klass._type), raw_include)
|
233
355
|
end
|
234
356
|
|
235
|
-
return if included_resources.
|
357
|
+
return if included_resources.nil?
|
236
358
|
|
237
359
|
begin
|
238
360
|
result = included_resources.compact.map do |included_resource|
|
239
|
-
check_include(
|
361
|
+
check_include(resource_klass, included_resource.partition('.'))
|
240
362
|
unformat_key(included_resource).to_s
|
241
363
|
end
|
242
364
|
|
243
|
-
|
365
|
+
return JSONAPI::IncludeDirectives.new(resource_klass, result)
|
244
366
|
rescue JSONAPI::Exceptions::InvalidInclude => e
|
245
367
|
@errors.concat(e.errors)
|
246
|
-
|
368
|
+
return {}
|
247
369
|
end
|
248
370
|
end
|
249
371
|
|
250
|
-
def parse_filters(filters)
|
251
|
-
|
372
|
+
def parse_filters(resource_klass, filters)
|
373
|
+
parsed_filters = {}
|
252
374
|
|
253
|
-
|
254
|
-
|
375
|
+
# apply default filters
|
376
|
+
resource_klass._allowed_filters.each do |filter, opts|
|
377
|
+
next if opts[:default].nil? || !parsed_filters[filter].nil?
|
378
|
+
parsed_filters[filter] = opts[:default]
|
255
379
|
end
|
256
380
|
|
381
|
+
return parsed_filters unless filters
|
382
|
+
|
257
383
|
unless filters.class.method_defined?(:each)
|
258
384
|
@errors.concat(JSONAPI::Exceptions::InvalidFiltersSyntax.new(filters).errors)
|
259
|
-
return
|
385
|
+
return {}
|
260
386
|
end
|
261
387
|
|
262
|
-
|
263
|
-
|
264
|
-
unformatted_key = unformat_key(key)
|
265
|
-
if resource_klass._allowed_filter?(unformatted_key)
|
266
|
-
@filters[unformatted_key] = value
|
267
|
-
elsif unformatted_key.to_s.include?('.')
|
268
|
-
parse_relationship_filter(unformatted_key, value)
|
269
|
-
else
|
270
|
-
return @errors.concat(Exceptions::FilterNotAllowed.new(unformatted_key).errors)
|
271
|
-
end
|
388
|
+
unless JSONAPI.configuration.allow_filter
|
389
|
+
fail JSONAPI::Exceptions::ParameterNotAllowed.new(:filter)
|
272
390
|
end
|
273
|
-
end
|
274
|
-
|
275
|
-
def parse_relationship_filter(key, value)
|
276
|
-
included_resource_name, filter_method = key.to_s.split('.')
|
277
|
-
filter_method = filter_method.to_sym if filter_method.present?
|
278
|
-
|
279
|
-
if included_resource_name
|
280
|
-
relationship = resource_klass._relationship(included_resource_name || '')
|
281
|
-
|
282
|
-
unless relationship
|
283
|
-
return @errors.concat(Exceptions::FilterNotAllowed.new(filter_method).errors)
|
284
|
-
end
|
285
|
-
|
286
|
-
unless relationship.resource_klass._allowed_filter?(filter_method)
|
287
|
-
return @errors.concat(Exceptions::FilterNotAllowed.new(filter_method).errors)
|
288
|
-
end
|
289
391
|
|
290
|
-
|
291
|
-
|
392
|
+
filters.each do |key, value|
|
393
|
+
filter = unformat_key(key)
|
394
|
+
if resource_klass._allowed_filter?(filter)
|
395
|
+
parsed_filters[filter] = value
|
396
|
+
else
|
397
|
+
fail JSONAPI::Exceptions::FilterNotAllowed.new(key)
|
292
398
|
end
|
293
|
-
|
294
|
-
verified_filter = relationship.resource_klass.verify_filters({ filter_method => value }, @context)
|
295
|
-
@include_directives.merge_filter(relationship.name, verified_filter)
|
296
|
-
else
|
297
|
-
return @errors.concat(Exceptions::FilterNotAllowed.new(filter_method).errors)
|
298
399
|
end
|
299
|
-
end
|
300
400
|
|
301
|
-
|
302
|
-
@resource_klass._allowed_filters.each do |filter, opts|
|
303
|
-
next if opts[:default].nil? || !@filters[filter].nil?
|
304
|
-
@filters[filter] = opts[:default]
|
305
|
-
end
|
401
|
+
parsed_filters
|
306
402
|
end
|
307
403
|
|
308
|
-
def parse_sort_criteria(sort_criteria)
|
404
|
+
def parse_sort_criteria(resource_klass, sort_criteria)
|
309
405
|
return unless sort_criteria.present?
|
310
406
|
|
311
407
|
unless JSONAPI.configuration.allow_sort
|
312
408
|
fail JSONAPI::Exceptions::ParameterNotAllowed.new(:sort)
|
313
409
|
end
|
314
410
|
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
411
|
+
if sort_criteria.is_a?(Array)
|
412
|
+
sorts = sort_criteria
|
413
|
+
elsif sort_criteria.is_a?(String)
|
414
|
+
begin
|
415
|
+
raw = URI.unescape(sort_criteria)
|
416
|
+
sorts = CSV.parse_line(raw)
|
417
|
+
rescue CSV::MalformedCSVError
|
418
|
+
fail JSONAPI::Exceptions::InvalidSortCriteria.new(format_key(resource_klass._type), raw)
|
419
|
+
end
|
321
420
|
end
|
322
421
|
|
323
422
|
@sort_criteria = sorts.collect do |sort|
|
324
423
|
if sort.start_with?('-')
|
325
|
-
|
326
|
-
|
424
|
+
criteria = { field: unformat_key(sort[1..-1]).to_s }
|
425
|
+
criteria[:direction] = :desc
|
327
426
|
else
|
328
|
-
|
329
|
-
|
427
|
+
criteria = { field: unformat_key(sort).to_s }
|
428
|
+
criteria[:direction] = :asc
|
330
429
|
end
|
331
430
|
|
332
|
-
check_sort_criteria(
|
333
|
-
|
431
|
+
check_sort_criteria(resource_klass, criteria)
|
432
|
+
criteria
|
334
433
|
end
|
335
434
|
end
|
336
435
|
|
337
436
|
def check_sort_criteria(resource_klass, sort_criteria)
|
338
437
|
sort_field = sort_criteria[:field]
|
339
|
-
sortable_fields = resource_klass.sortable_fields(context)
|
340
438
|
|
341
|
-
unless
|
439
|
+
unless resource_klass.sortable_field?(sort_field.to_sym, context)
|
342
440
|
fail JSONAPI::Exceptions::InvalidSortCriteria.new(format_key(resource_klass._type), sort_field)
|
343
441
|
end
|
344
442
|
end
|
345
443
|
|
346
|
-
def
|
347
|
-
@operations.push JSONAPI::Operation.new(:find,
|
348
|
-
@resource_klass,
|
349
|
-
context: @context,
|
350
|
-
filters: @filters,
|
351
|
-
include_directives: @include_directives,
|
352
|
-
sort_criteria: @sort_criteria,
|
353
|
-
paginator: @paginator,
|
354
|
-
fields: @fields
|
355
|
-
)
|
356
|
-
end
|
357
|
-
|
358
|
-
def add_show_operation
|
359
|
-
@operations.push JSONAPI::Operation.new(:show,
|
360
|
-
@resource_klass,
|
361
|
-
context: @context,
|
362
|
-
id: @id,
|
363
|
-
include_directives: @include_directives,
|
364
|
-
fields: @fields
|
365
|
-
)
|
366
|
-
end
|
367
|
-
|
368
|
-
def add_show_relationship_operation(relationship_type, parent_key)
|
369
|
-
@operations.push JSONAPI::Operation.new(:show_relationship,
|
370
|
-
@resource_klass,
|
371
|
-
context: @context,
|
372
|
-
relationship_type: relationship_type,
|
373
|
-
parent_key: @resource_klass.verify_key(parent_key)
|
374
|
-
)
|
375
|
-
end
|
376
|
-
|
377
|
-
def add_show_related_resource_operation(relationship_type)
|
378
|
-
@operations.push JSONAPI::Operation.new(:show_related_resource,
|
379
|
-
@resource_klass,
|
380
|
-
context: @context,
|
381
|
-
relationship_type: relationship_type,
|
382
|
-
source_klass: @source_klass,
|
383
|
-
source_id: @source_id,
|
384
|
-
fields: @fields,
|
385
|
-
include_directives: @include_directives
|
386
|
-
)
|
387
|
-
end
|
388
|
-
|
389
|
-
def add_show_related_resources_operation(relationship_type)
|
390
|
-
@operations.push JSONAPI::Operation.new(:show_related_resources,
|
391
|
-
@resource_klass,
|
392
|
-
context: @context,
|
393
|
-
relationship_type: relationship_type,
|
394
|
-
source_klass: @source_klass,
|
395
|
-
source_id: @source_id,
|
396
|
-
filters: @filters,
|
397
|
-
sort_criteria: @sort_criteria,
|
398
|
-
paginator: @paginator,
|
399
|
-
fields: @fields,
|
400
|
-
include_directives: @include_directives
|
401
|
-
)
|
402
|
-
end
|
403
|
-
|
404
|
-
def parse_add_operation(params)
|
405
|
-
fail JSONAPI::Exceptions::InvalidDataFormat unless params.respond_to?(:each_pair)
|
406
|
-
|
407
|
-
verify_type(params[:type])
|
408
|
-
|
409
|
-
data = parse_params(params, @resource_klass.creatable_fields(@context))
|
410
|
-
@operations.push JSONAPI::Operation.new(:create_resource,
|
411
|
-
@resource_klass,
|
412
|
-
context: @context,
|
413
|
-
data: data,
|
414
|
-
fields: @fields,
|
415
|
-
include_directives: @include_directives
|
416
|
-
)
|
417
|
-
rescue JSONAPI::Exceptions::Error => e
|
418
|
-
@errors.concat(e.errors)
|
419
|
-
end
|
420
|
-
|
421
|
-
def verify_type(type)
|
444
|
+
def verify_type(type, resource_klass)
|
422
445
|
if type.nil?
|
423
446
|
fail JSONAPI::Exceptions::ParameterMissing.new(:type)
|
424
|
-
elsif unformat_key(type).to_sym !=
|
425
|
-
fail JSONAPI::Exceptions::InvalidResource.new(type)
|
447
|
+
elsif unformat_key(type).to_sym != resource_klass._type
|
448
|
+
fail JSONAPI::Exceptions::InvalidResource.new(type, error_object_overrides)
|
426
449
|
end
|
427
450
|
end
|
428
451
|
|
429
452
|
def parse_to_one_links_object(raw)
|
430
453
|
if raw.nil?
|
431
454
|
return {
|
432
|
-
|
433
|
-
|
455
|
+
type: nil,
|
456
|
+
id: nil
|
434
457
|
}
|
435
458
|
end
|
436
459
|
|
437
460
|
if !(raw.is_a?(Hash) || raw.is_a?(ActionController::Parameters)) ||
|
438
|
-
|
439
|
-
fail JSONAPI::Exceptions::InvalidLinksObject.new
|
461
|
+
raw.keys.length != 2 || !(raw.key?('type') && raw.key?('id'))
|
462
|
+
fail JSONAPI::Exceptions::InvalidLinksObject.new(error_object_overrides)
|
440
463
|
end
|
441
464
|
|
442
465
|
{
|
443
|
-
|
444
|
-
|
466
|
+
type: unformat_key(raw['type']).to_s,
|
467
|
+
id: raw['id']
|
445
468
|
}
|
446
469
|
end
|
447
470
|
|
448
471
|
def parse_to_many_links_object(raw)
|
449
|
-
fail JSONAPI::Exceptions::InvalidLinksObject.new if raw.nil?
|
472
|
+
fail JSONAPI::Exceptions::InvalidLinksObject.new(error_object_overrides) if raw.nil?
|
450
473
|
|
451
474
|
links_object = {}
|
452
475
|
if raw.is_a?(Array)
|
@@ -456,12 +479,12 @@ module JSONAPI
|
|
456
479
|
links_object[link_object[:type]].push(link_object[:id])
|
457
480
|
end
|
458
481
|
else
|
459
|
-
fail JSONAPI::Exceptions::InvalidLinksObject.new
|
482
|
+
fail JSONAPI::Exceptions::InvalidLinksObject.new(error_object_overrides)
|
460
483
|
end
|
461
484
|
links_object
|
462
485
|
end
|
463
486
|
|
464
|
-
def parse_params(params, allowed_fields)
|
487
|
+
def parse_params(resource_klass, params, allowed_fields)
|
465
488
|
verify_permitted_params(params, allowed_fields)
|
466
489
|
|
467
490
|
checked_attributes = {}
|
@@ -470,37 +493,37 @@ module JSONAPI
|
|
470
493
|
|
471
494
|
params.each do |key, value|
|
472
495
|
case key.to_s
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
496
|
+
when 'relationships'
|
497
|
+
value.each do |link_key, link_value|
|
498
|
+
param = unformat_key(link_key)
|
499
|
+
relationship = resource_klass._relationship(param)
|
500
|
+
|
501
|
+
if relationship.is_a?(JSONAPI::Relationship::ToOne)
|
502
|
+
checked_to_one_relationships[param] = parse_to_one_relationship(resource_klass, link_value, relationship)
|
503
|
+
elsif relationship.is_a?(JSONAPI::Relationship::ToMany)
|
504
|
+
parse_to_many_relationship(resource_klass, link_value, relationship) do |result_val|
|
505
|
+
checked_to_many_relationships[param] = result_val
|
506
|
+
end
|
483
507
|
end
|
484
508
|
end
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
end
|
509
|
+
when 'id'
|
510
|
+
checked_attributes['id'] = unformat_value(resource_klass, :id, value)
|
511
|
+
when 'attributes'
|
512
|
+
value.each do |key, value|
|
513
|
+
param = unformat_key(key)
|
514
|
+
checked_attributes[param] = unformat_value(resource_klass, param, value)
|
515
|
+
end
|
493
516
|
end
|
494
517
|
end
|
495
518
|
|
496
519
|
return {
|
497
|
-
|
498
|
-
|
499
|
-
|
520
|
+
'attributes' => checked_attributes,
|
521
|
+
'to_one' => checked_to_one_relationships,
|
522
|
+
'to_many' => checked_to_many_relationships
|
500
523
|
}.deep_transform_keys { |key| unformat_key(key) }
|
501
524
|
end
|
502
525
|
|
503
|
-
def parse_to_one_relationship(link_value, relationship)
|
526
|
+
def parse_to_one_relationship(resource_klass, link_value, relationship)
|
504
527
|
if link_value.nil?
|
505
528
|
linkage = nil
|
506
529
|
else
|
@@ -509,12 +532,12 @@ module JSONAPI
|
|
509
532
|
|
510
533
|
links_object = parse_to_one_links_object(linkage)
|
511
534
|
if !relationship.polymorphic? && links_object[:type] && (links_object[:type].to_s != relationship.type.to_s)
|
512
|
-
fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
|
535
|
+
fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type], error_object_overrides)
|
513
536
|
end
|
514
537
|
|
515
538
|
unless links_object[:id].nil?
|
516
|
-
resource =
|
517
|
-
relationship_resource = resource.
|
539
|
+
resource = resource_klass || Resource
|
540
|
+
relationship_resource = resource.resource_klass_for(unformat_key(relationship.options[:class_name] || links_object[:type]).to_s)
|
518
541
|
relationship_id = relationship_resource.verify_key(links_object[:id], @context)
|
519
542
|
if relationship.polymorphic?
|
520
543
|
{ id: relationship_id, type: unformat_key(links_object[:type].to_s) }
|
@@ -526,57 +549,35 @@ module JSONAPI
|
|
526
549
|
end
|
527
550
|
end
|
528
551
|
|
529
|
-
def parse_to_many_relationship(link_value, relationship, &add_result)
|
530
|
-
if link_value.is_a?(
|
531
|
-
linkage = []
|
532
|
-
elsif (link_value.is_a?(Hash) || link_value.is_a?(ActionController::Parameters))
|
552
|
+
def parse_to_many_relationship(resource_klass, link_value, relationship, &add_result)
|
553
|
+
if (link_value.is_a?(Hash) || link_value.is_a?(ActionController::Parameters))
|
533
554
|
linkage = link_value[:data]
|
534
555
|
else
|
535
|
-
fail JSONAPI::Exceptions::InvalidLinksObject.new
|
556
|
+
fail JSONAPI::Exceptions::InvalidLinksObject.new(error_object_overrides)
|
536
557
|
end
|
537
558
|
|
538
559
|
links_object = parse_to_many_links_object(linkage)
|
539
560
|
|
561
|
+
# Since we do not yet support polymorphic to_many relationships we will raise an error if the type does not match the
|
562
|
+
# relationship's type.
|
563
|
+
# ToDo: Support Polymorphic relationships
|
564
|
+
|
540
565
|
if links_object.length == 0
|
541
566
|
add_result.call([])
|
542
567
|
else
|
543
|
-
if relationship.
|
544
|
-
|
545
|
-
|
546
|
-
links_object.each_pair do |type, keys|
|
547
|
-
resource = self.resource_klass || Resource
|
548
|
-
type_name = unformat_key(type).to_s
|
549
|
-
|
550
|
-
relationship_resource_klass = resource.resource_for(relationship.class_name)
|
551
|
-
relationship_klass = relationship_resource_klass._model_class
|
552
|
-
|
553
|
-
linkage_object_resource_klass = resource.resource_for(type_name)
|
554
|
-
linkage_object_klass = linkage_object_resource_klass._model_class
|
555
|
-
|
556
|
-
unless linkage_object_klass == relationship_klass || linkage_object_klass.in?(relationship_klass.subclasses)
|
557
|
-
fail JSONAPI::Exceptions::TypeMismatch.new(type_name)
|
558
|
-
end
|
559
|
-
|
560
|
-
relationship_ids = relationship_resource_klass.verify_keys(keys, @context)
|
561
|
-
polymorphic_results << { type: type, ids: relationship_ids }
|
562
|
-
end
|
563
|
-
|
564
|
-
add_result.call polymorphic_results
|
565
|
-
else
|
566
|
-
relationship_type = unformat_key(relationship.type).to_s
|
567
|
-
|
568
|
-
if links_object.length > 1 || !links_object.has_key?(relationship_type)
|
569
|
-
fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type])
|
570
|
-
end
|
568
|
+
if links_object.length > 1 || !links_object.has_key?(unformat_key(relationship.type).to_s)
|
569
|
+
fail JSONAPI::Exceptions::TypeMismatch.new(links_object[:type], error_object_overrides)
|
570
|
+
end
|
571
571
|
|
572
|
-
|
573
|
-
|
572
|
+
links_object.each_pair do |type, keys|
|
573
|
+
relationship_resource = Resource.resource_klass_for(resource_klass.module_path + unformat_key(type).to_s)
|
574
|
+
add_result.call relationship_resource.verify_keys(keys, @context)
|
574
575
|
end
|
575
576
|
end
|
576
577
|
end
|
577
578
|
|
578
|
-
def unformat_value(attribute, value)
|
579
|
-
value_formatter = JSONAPI::ValueFormatter.value_formatter_for(
|
579
|
+
def unformat_value(resource_klass, attribute, value)
|
580
|
+
value_formatter = JSONAPI::ValueFormatter.value_formatter_for(resource_klass._attribute_options(attribute)[:format])
|
580
581
|
value_formatter.unformat(value)
|
581
582
|
end
|
582
583
|
|
@@ -586,45 +587,45 @@ module JSONAPI
|
|
586
587
|
|
587
588
|
params.each do |key, value|
|
588
589
|
case key.to_s
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
595
|
-
|
596
|
-
|
590
|
+
when 'relationships'
|
591
|
+
value.keys.each do |links_key|
|
592
|
+
unless formatted_allowed_fields.include?(links_key.to_sym)
|
593
|
+
if JSONAPI.configuration.raise_if_parameters_not_allowed
|
594
|
+
fail JSONAPI::Exceptions::ParameterNotAllowed.new(links_key, error_object_overrides)
|
595
|
+
else
|
596
|
+
params_not_allowed.push(links_key)
|
597
|
+
value.delete links_key
|
598
|
+
end
|
597
599
|
end
|
598
600
|
end
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
601
|
+
when 'attributes'
|
602
|
+
value.each do |attr_key, _attr_value|
|
603
|
+
unless formatted_allowed_fields.include?(attr_key.to_sym)
|
604
|
+
if JSONAPI.configuration.raise_if_parameters_not_allowed
|
605
|
+
fail JSONAPI::Exceptions::ParameterNotAllowed.new(attr_key, error_object_overrides)
|
606
|
+
else
|
607
|
+
params_not_allowed.push(attr_key)
|
608
|
+
value.delete attr_key
|
609
|
+
end
|
610
|
+
end
|
611
|
+
end
|
612
|
+
when 'type'
|
613
|
+
when 'id'
|
614
|
+
unless formatted_allowed_fields.include?(:id)
|
603
615
|
if JSONAPI.configuration.raise_if_parameters_not_allowed
|
604
|
-
fail JSONAPI::Exceptions::ParameterNotAllowed.new(
|
616
|
+
fail JSONAPI::Exceptions::ParameterNotAllowed.new(:id, error_object_overrides)
|
605
617
|
else
|
606
|
-
params_not_allowed.push(
|
607
|
-
|
618
|
+
params_not_allowed.push(:id)
|
619
|
+
params.delete :id
|
608
620
|
end
|
609
621
|
end
|
610
|
-
|
611
|
-
when 'type'
|
612
|
-
when 'id'
|
613
|
-
unless formatted_allowed_fields.include?(:id)
|
622
|
+
else
|
614
623
|
if JSONAPI.configuration.raise_if_parameters_not_allowed
|
615
|
-
fail JSONAPI::Exceptions::ParameterNotAllowed.new(
|
624
|
+
fail JSONAPI::Exceptions::ParameterNotAllowed.new(key, error_object_overrides)
|
616
625
|
else
|
617
|
-
params_not_allowed.push(
|
618
|
-
params.delete
|
626
|
+
params_not_allowed.push(key)
|
627
|
+
params.delete key
|
619
628
|
end
|
620
|
-
end
|
621
|
-
else
|
622
|
-
if JSONAPI.configuration.raise_if_parameters_not_allowed
|
623
|
-
fail JSONAPI::Exceptions::ParameterNotAllowed.new(key)
|
624
|
-
else
|
625
|
-
params_not_allowed.push(key)
|
626
|
-
params.delete key
|
627
|
-
end
|
628
629
|
end
|
629
630
|
end
|
630
631
|
|
@@ -638,23 +639,24 @@ module JSONAPI
|
|
638
639
|
end
|
639
640
|
end
|
640
641
|
|
641
|
-
def parse_add_relationship_operation(verified_params, relationship, parent_key)
|
642
|
+
def parse_add_relationship_operation(resource_klass, verified_params, relationship, parent_key)
|
642
643
|
if relationship.is_a?(JSONAPI::Relationship::ToMany)
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
644
|
+
return JSONAPI::Operation.new(
|
645
|
+
:create_to_many_relationships,
|
646
|
+
resource_klass,
|
647
|
+
context: @context,
|
648
|
+
resource_id: parent_key,
|
649
|
+
relationship_type: relationship.name,
|
650
|
+
data: verified_params[:to_many].values[0]
|
649
651
|
)
|
650
652
|
end
|
651
653
|
end
|
652
654
|
|
653
|
-
def parse_update_relationship_operation(verified_params, relationship, parent_key)
|
655
|
+
def parse_update_relationship_operation(resource_klass, verified_params, relationship, parent_key)
|
654
656
|
options = {
|
655
|
-
|
656
|
-
|
657
|
-
|
657
|
+
context: @context,
|
658
|
+
resource_id: parent_key,
|
659
|
+
relationship_type: relationship.name
|
658
660
|
}
|
659
661
|
|
660
662
|
if relationship.is_a?(JSONAPI::Relationship::ToOne)
|
@@ -675,70 +677,23 @@ module JSONAPI
|
|
675
677
|
operation_type = :replace_to_many_relationships
|
676
678
|
end
|
677
679
|
|
678
|
-
|
680
|
+
JSONAPI::Operation.new(operation_type, resource_klass, options)
|
679
681
|
end
|
680
682
|
|
681
|
-
def
|
682
|
-
fail JSONAPI::Exceptions::InvalidDataFormat unless data.respond_to?(:each_pair)
|
683
|
-
|
684
|
-
fail JSONAPI::Exceptions::MissingKey.new if data[:id].nil?
|
685
|
-
|
686
|
-
key = data[:id].to_s
|
687
|
-
if id_key_presence_check_required && !keys.include?(key)
|
688
|
-
fail JSONAPI::Exceptions::KeyNotIncludedInURL.new(key)
|
689
|
-
end
|
690
|
-
|
691
|
-
data.delete(:id) unless keys.include?(:id)
|
692
|
-
|
693
|
-
verify_type(data[:type])
|
694
|
-
|
695
|
-
@operations.push JSONAPI::Operation.new(:replace_fields,
|
696
|
-
@resource_klass,
|
697
|
-
context: @context,
|
698
|
-
resource_id: key,
|
699
|
-
data: parse_params(data, @resource_klass.updatable_fields(@context)),
|
700
|
-
fields: @fields,
|
701
|
-
include_directives: @include_directives
|
702
|
-
)
|
703
|
-
end
|
704
|
-
|
705
|
-
def parse_replace_operation(data, keys)
|
706
|
-
parse_single_replace_operation(data, [keys],
|
707
|
-
id_key_presence_check_required: keys.present? && !@resource_klass.singleton?)
|
708
|
-
rescue JSONAPI::Exceptions::Error => e
|
709
|
-
@errors.concat(e.errors)
|
710
|
-
end
|
711
|
-
|
712
|
-
def parse_remove_operation(params)
|
713
|
-
@operations.push JSONAPI::Operation.new(:remove_resource,
|
714
|
-
@resource_klass,
|
715
|
-
context: @context,
|
716
|
-
resource_id: @resource_klass.verify_key(params.require(:id), context))
|
717
|
-
rescue JSONAPI::Exceptions::Error => e
|
718
|
-
@errors.concat(e.errors)
|
719
|
-
end
|
720
|
-
|
721
|
-
def parse_remove_relationship_operation(params, relationship, parent_key)
|
683
|
+
def parse_remove_relationship_operation(resource_klass, params, relationship, parent_key)
|
722
684
|
operation_base_args = [resource_klass].push(
|
723
|
-
|
724
|
-
|
725
|
-
|
685
|
+
context: @context,
|
686
|
+
resource_id: parent_key,
|
687
|
+
relationship_type: relationship.name
|
726
688
|
)
|
727
689
|
|
728
690
|
if relationship.is_a?(JSONAPI::Relationship::ToMany)
|
729
691
|
operation_args = operation_base_args.dup
|
730
692
|
keys = params[:to_many].values[0]
|
731
693
|
operation_args[1] = operation_args[1].merge(associated_keys: keys)
|
732
|
-
|
694
|
+
JSONAPI::Operation.new(:remove_to_many_relationships, *operation_args)
|
733
695
|
else
|
734
|
-
|
735
|
-
end
|
736
|
-
end
|
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
|
696
|
+
JSONAPI::Operation.new(:remove_to_one_relationship, *operation_base_args)
|
742
697
|
end
|
743
698
|
end
|
744
699
|
|