sanger-jsonapi-resources 0.1.0
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 +7 -0
- data/LICENSE.txt +22 -0
- data/README.md +53 -0
- data/lib/generators/jsonapi/USAGE +13 -0
- data/lib/generators/jsonapi/controller_generator.rb +14 -0
- data/lib/generators/jsonapi/resource_generator.rb +14 -0
- data/lib/generators/jsonapi/templates/jsonapi_controller.rb +4 -0
- data/lib/generators/jsonapi/templates/jsonapi_resource.rb +4 -0
- data/lib/jsonapi/acts_as_resource_controller.rb +320 -0
- data/lib/jsonapi/cached_resource_fragment.rb +127 -0
- data/lib/jsonapi/callbacks.rb +51 -0
- data/lib/jsonapi/compiled_json.rb +36 -0
- data/lib/jsonapi/configuration.rb +258 -0
- data/lib/jsonapi/error.rb +47 -0
- data/lib/jsonapi/error_codes.rb +60 -0
- data/lib/jsonapi/exceptions.rb +563 -0
- data/lib/jsonapi/formatter.rb +169 -0
- data/lib/jsonapi/include_directives.rb +100 -0
- data/lib/jsonapi/link_builder.rb +152 -0
- data/lib/jsonapi/mime_types.rb +41 -0
- data/lib/jsonapi/naive_cache.rb +30 -0
- data/lib/jsonapi/operation.rb +24 -0
- data/lib/jsonapi/operation_dispatcher.rb +88 -0
- data/lib/jsonapi/operation_result.rb +65 -0
- data/lib/jsonapi/operation_results.rb +35 -0
- data/lib/jsonapi/paginator.rb +209 -0
- data/lib/jsonapi/processor.rb +328 -0
- data/lib/jsonapi/relationship.rb +94 -0
- data/lib/jsonapi/relationship_builder.rb +167 -0
- data/lib/jsonapi/request_parser.rb +678 -0
- data/lib/jsonapi/resource.rb +1255 -0
- data/lib/jsonapi/resource_controller.rb +5 -0
- data/lib/jsonapi/resource_controller_metal.rb +16 -0
- data/lib/jsonapi/resource_serializer.rb +531 -0
- data/lib/jsonapi/resources/version.rb +5 -0
- data/lib/jsonapi/response_document.rb +135 -0
- data/lib/jsonapi/routing_ext.rb +262 -0
- data/lib/jsonapi-resources.rb +27 -0
- metadata +223 -0
@@ -0,0 +1,209 @@
|
|
1
|
+
module JSONAPI
|
2
|
+
class Paginator
|
3
|
+
def initialize(_params)
|
4
|
+
end
|
5
|
+
|
6
|
+
def apply(_relation, _order_options)
|
7
|
+
# relation
|
8
|
+
end
|
9
|
+
|
10
|
+
def links_page_params(_options = {})
|
11
|
+
# :nocov:
|
12
|
+
{}
|
13
|
+
# :nocov:
|
14
|
+
end
|
15
|
+
|
16
|
+
class << self
|
17
|
+
def requires_record_count
|
18
|
+
# :nocov:
|
19
|
+
false
|
20
|
+
# :nocov:
|
21
|
+
end
|
22
|
+
|
23
|
+
def paginator_for(paginator)
|
24
|
+
paginator_class_name = "#{paginator.to_s.camelize}Paginator"
|
25
|
+
paginator_class_name.safe_constantize if paginator_class_name
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class OffsetPaginator < JSONAPI::Paginator
|
32
|
+
attr_reader :limit, :offset
|
33
|
+
|
34
|
+
def initialize(params)
|
35
|
+
parse_pagination_params(params)
|
36
|
+
verify_pagination_params
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.requires_record_count
|
40
|
+
true
|
41
|
+
end
|
42
|
+
|
43
|
+
def apply(relation, _order_options)
|
44
|
+
relation.offset(@offset).limit(@limit)
|
45
|
+
end
|
46
|
+
|
47
|
+
def links_page_params(options = {})
|
48
|
+
record_count = options[:record_count]
|
49
|
+
links_page_params = {}
|
50
|
+
|
51
|
+
links_page_params['first'] = {
|
52
|
+
'offset' => 0,
|
53
|
+
'limit' => @limit
|
54
|
+
}
|
55
|
+
|
56
|
+
if @offset > 0
|
57
|
+
previous_offset = @offset - @limit
|
58
|
+
|
59
|
+
previous_offset = 0 if previous_offset < 0
|
60
|
+
|
61
|
+
links_page_params['prev'] = {
|
62
|
+
'offset' => previous_offset,
|
63
|
+
'limit' => @limit
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
next_offset = @offset + @limit
|
68
|
+
|
69
|
+
unless next_offset >= record_count
|
70
|
+
links_page_params['next'] = {
|
71
|
+
'offset' => next_offset,
|
72
|
+
'limit' => @limit
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
if record_count
|
77
|
+
last_offset = record_count - @limit
|
78
|
+
|
79
|
+
last_offset = 0 if last_offset < 0
|
80
|
+
|
81
|
+
links_page_params['last'] = {
|
82
|
+
'offset' => last_offset,
|
83
|
+
'limit' => @limit
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
links_page_params
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def parse_pagination_params(params)
|
93
|
+
if params.nil?
|
94
|
+
@offset = 0
|
95
|
+
@limit = JSONAPI.configuration.default_page_size
|
96
|
+
elsif params.is_a?(ActionController::Parameters)
|
97
|
+
validparams = params.permit(:offset, :limit)
|
98
|
+
|
99
|
+
@offset = validparams[:offset] ? validparams[:offset].to_i : 0
|
100
|
+
@limit = validparams[:limit] ? validparams[:limit].to_i : JSONAPI.configuration.default_page_size
|
101
|
+
else
|
102
|
+
fail JSONAPI::Exceptions::InvalidPageObject.new
|
103
|
+
end
|
104
|
+
rescue ActionController::UnpermittedParameters => e
|
105
|
+
raise JSONAPI::Exceptions::PageParametersNotAllowed.new(e.params)
|
106
|
+
end
|
107
|
+
|
108
|
+
def verify_pagination_params
|
109
|
+
if @limit < 1
|
110
|
+
fail JSONAPI::Exceptions::InvalidPageValue.new(:limit, @limit)
|
111
|
+
elsif @limit > JSONAPI.configuration.maximum_page_size
|
112
|
+
fail JSONAPI::Exceptions::InvalidPageValue.new(:limit, @limit,
|
113
|
+
detail: "Limit exceeds maximum page size of #{JSONAPI.configuration.maximum_page_size}.")
|
114
|
+
end
|
115
|
+
|
116
|
+
if @offset < 0
|
117
|
+
fail JSONAPI::Exceptions::InvalidPageValue.new(:offset, @offset)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class PagedPaginator < JSONAPI::Paginator
|
123
|
+
attr_reader :size, :number
|
124
|
+
|
125
|
+
def initialize(params)
|
126
|
+
parse_pagination_params(params)
|
127
|
+
verify_pagination_params
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.requires_record_count
|
131
|
+
true
|
132
|
+
end
|
133
|
+
|
134
|
+
def calculate_page_count(record_count)
|
135
|
+
(record_count / @size.to_f).ceil
|
136
|
+
end
|
137
|
+
|
138
|
+
def apply(relation, _order_options)
|
139
|
+
offset = (@number - 1) * @size
|
140
|
+
relation.offset(offset).limit(@size)
|
141
|
+
end
|
142
|
+
|
143
|
+
def links_page_params(options = {})
|
144
|
+
record_count = options[:record_count]
|
145
|
+
page_count = calculate_page_count(record_count)
|
146
|
+
|
147
|
+
links_page_params = {}
|
148
|
+
|
149
|
+
links_page_params['first'] = {
|
150
|
+
'number' => 1,
|
151
|
+
'size' => @size
|
152
|
+
}
|
153
|
+
|
154
|
+
if @number > 1
|
155
|
+
links_page_params['prev'] = {
|
156
|
+
'number' => @number - 1,
|
157
|
+
'size' => @size
|
158
|
+
}
|
159
|
+
end
|
160
|
+
|
161
|
+
unless @number >= page_count
|
162
|
+
links_page_params['next'] = {
|
163
|
+
'number' => @number + 1,
|
164
|
+
'size' => @size
|
165
|
+
}
|
166
|
+
end
|
167
|
+
|
168
|
+
if record_count
|
169
|
+
links_page_params['last'] = {
|
170
|
+
'number' => page_count == 0 ? 1 : page_count,
|
171
|
+
'size' => @size
|
172
|
+
}
|
173
|
+
end
|
174
|
+
|
175
|
+
links_page_params
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
def parse_pagination_params(params)
|
181
|
+
if params.nil?
|
182
|
+
@number = 1
|
183
|
+
@size = JSONAPI.configuration.default_page_size
|
184
|
+
elsif params.is_a?(ActionController::Parameters)
|
185
|
+
validparams = params.permit(:number, :size)
|
186
|
+
|
187
|
+
@size = validparams[:size] ? validparams[:size].to_i : JSONAPI.configuration.default_page_size
|
188
|
+
@number = validparams[:number] ? validparams[:number].to_i : 1
|
189
|
+
else
|
190
|
+
@size = JSONAPI.configuration.default_page_size
|
191
|
+
@number = params.to_i
|
192
|
+
end
|
193
|
+
rescue ActionController::UnpermittedParameters => e
|
194
|
+
raise JSONAPI::Exceptions::PageParametersNotAllowed.new(e.params)
|
195
|
+
end
|
196
|
+
|
197
|
+
def verify_pagination_params
|
198
|
+
if @size < 1
|
199
|
+
fail JSONAPI::Exceptions::InvalidPageValue.new(:size, @size)
|
200
|
+
elsif @size > JSONAPI.configuration.maximum_page_size
|
201
|
+
fail JSONAPI::Exceptions::InvalidPageValue.new(:size, @size,
|
202
|
+
detail: "size exceeds maximum page size of #{JSONAPI.configuration.maximum_page_size}.")
|
203
|
+
end
|
204
|
+
|
205
|
+
if @number < 1
|
206
|
+
fail JSONAPI::Exceptions::InvalidPageValue.new(:number, @number)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
@@ -0,0 +1,328 @@
|
|
1
|
+
module JSONAPI
|
2
|
+
class Processor
|
3
|
+
include Callbacks
|
4
|
+
define_jsonapi_resources_callbacks :find,
|
5
|
+
:show,
|
6
|
+
:show_relationship,
|
7
|
+
:show_related_resource,
|
8
|
+
:show_related_resources,
|
9
|
+
:create_resource,
|
10
|
+
:remove_resource,
|
11
|
+
:replace_fields,
|
12
|
+
:replace_to_one_relationship,
|
13
|
+
:replace_polymorphic_to_one_relationship,
|
14
|
+
:create_to_many_relationships,
|
15
|
+
:replace_to_many_relationships,
|
16
|
+
:remove_to_many_relationships,
|
17
|
+
:remove_to_one_relationship,
|
18
|
+
:operation
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def processor_instance_for(resource_klass, operation_type, params)
|
22
|
+
_processor_from_resource_type(resource_klass).new(resource_klass, operation_type, params)
|
23
|
+
end
|
24
|
+
|
25
|
+
def _processor_from_resource_type(resource_klass)
|
26
|
+
processor = resource_klass.name.gsub(/Resource$/,'Processor').safe_constantize
|
27
|
+
if processor.nil?
|
28
|
+
processor = JSONAPI.configuration.default_processor_klass
|
29
|
+
end
|
30
|
+
|
31
|
+
return processor
|
32
|
+
end
|
33
|
+
|
34
|
+
def transactional_operation_type?(operation_type)
|
35
|
+
case operation_type
|
36
|
+
when :find, :show, :show_related_resource, :show_related_resources
|
37
|
+
return false
|
38
|
+
else
|
39
|
+
return true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_reader :resource_klass, :operation_type, :params, :context, :result, :result_options
|
45
|
+
|
46
|
+
def initialize(resource_klass, operation_type, params)
|
47
|
+
@resource_klass = resource_klass
|
48
|
+
@operation_type = operation_type
|
49
|
+
@params = params
|
50
|
+
@context = params[:context]
|
51
|
+
@result = nil
|
52
|
+
@result_options = {}
|
53
|
+
end
|
54
|
+
|
55
|
+
def process
|
56
|
+
run_callbacks :operation do
|
57
|
+
run_callbacks operation_type do
|
58
|
+
@result = send(operation_type)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
rescue JSONAPI::Exceptions::Error => e
|
63
|
+
@result = JSONAPI::ErrorsOperationResult.new(e.errors[0].code, e.errors)
|
64
|
+
end
|
65
|
+
|
66
|
+
def find
|
67
|
+
filters = params[:filters]
|
68
|
+
include_directives = params[:include_directives]
|
69
|
+
sort_criteria = params.fetch(:sort_criteria, [])
|
70
|
+
paginator = params[:paginator]
|
71
|
+
fields = params[:fields]
|
72
|
+
|
73
|
+
verified_filters = resource_klass.verify_filters(filters, context)
|
74
|
+
find_options = {
|
75
|
+
context: context,
|
76
|
+
include_directives: include_directives,
|
77
|
+
sort_criteria: sort_criteria,
|
78
|
+
paginator: paginator,
|
79
|
+
fields: fields
|
80
|
+
}
|
81
|
+
|
82
|
+
resource_records = if params[:cache_serializer]
|
83
|
+
resource_klass.find_serialized_with_caching(verified_filters,
|
84
|
+
params[:cache_serializer],
|
85
|
+
find_options)
|
86
|
+
else
|
87
|
+
resource_klass.find(verified_filters, find_options)
|
88
|
+
end
|
89
|
+
|
90
|
+
page_options = {}
|
91
|
+
if (JSONAPI.configuration.top_level_meta_include_record_count ||
|
92
|
+
(paginator && paginator.class.requires_record_count))
|
93
|
+
page_options[:record_count] = resource_klass.find_count(verified_filters,
|
94
|
+
context: context,
|
95
|
+
include_directives: include_directives)
|
96
|
+
end
|
97
|
+
|
98
|
+
if (JSONAPI.configuration.top_level_meta_include_page_count && page_options[:record_count])
|
99
|
+
page_options[:page_count] = paginator ? paginator.calculate_page_count(page_options[:record_count]) : 1
|
100
|
+
end
|
101
|
+
|
102
|
+
if JSONAPI.configuration.top_level_links_include_pagination && paginator
|
103
|
+
page_options[:pagination_params] = paginator.links_page_params(page_options)
|
104
|
+
end
|
105
|
+
|
106
|
+
return JSONAPI::ResourcesOperationResult.new(:ok, resource_records, page_options)
|
107
|
+
end
|
108
|
+
|
109
|
+
def show
|
110
|
+
include_directives = params[:include_directives]
|
111
|
+
fields = params[:fields]
|
112
|
+
id = params[:id]
|
113
|
+
|
114
|
+
key = resource_klass.verify_key(id, context)
|
115
|
+
|
116
|
+
find_options = {
|
117
|
+
context: context,
|
118
|
+
include_directives: include_directives,
|
119
|
+
fields: fields
|
120
|
+
}
|
121
|
+
|
122
|
+
resource_record = if params[:cache_serializer]
|
123
|
+
resource_klass.find_by_key_serialized_with_caching(key,
|
124
|
+
params[:cache_serializer],
|
125
|
+
find_options)
|
126
|
+
else
|
127
|
+
resource_klass.find_by_key(key, find_options)
|
128
|
+
end
|
129
|
+
|
130
|
+
return JSONAPI::ResourceOperationResult.new(:ok, resource_record)
|
131
|
+
end
|
132
|
+
|
133
|
+
def show_relationship
|
134
|
+
parent_key = params[:parent_key]
|
135
|
+
relationship_type = params[:relationship_type].to_sym
|
136
|
+
|
137
|
+
parent_resource = resource_klass.find_by_key(parent_key, context: context)
|
138
|
+
|
139
|
+
return JSONAPI::LinksObjectOperationResult.new(:ok,
|
140
|
+
parent_resource,
|
141
|
+
resource_klass._relationship(relationship_type))
|
142
|
+
end
|
143
|
+
|
144
|
+
def show_related_resource
|
145
|
+
source_klass = params[:source_klass]
|
146
|
+
source_id = params[:source_id]
|
147
|
+
relationship_type = params[:relationship_type].to_sym
|
148
|
+
fields = params[:fields]
|
149
|
+
|
150
|
+
# TODO Should fetch related_resource from cache if caching enabled
|
151
|
+
source_resource = source_klass.find_by_key(source_id, context: context, fields: fields)
|
152
|
+
|
153
|
+
related_resource = source_resource.public_send(relationship_type)
|
154
|
+
|
155
|
+
return JSONAPI::ResourceOperationResult.new(:ok, related_resource)
|
156
|
+
end
|
157
|
+
|
158
|
+
def show_related_resources
|
159
|
+
source_klass = params[:source_klass]
|
160
|
+
source_id = params[:source_id]
|
161
|
+
relationship_type = params[:relationship_type]
|
162
|
+
filters = params[:filters]
|
163
|
+
sort_criteria = params[:sort_criteria]
|
164
|
+
paginator = params[:paginator]
|
165
|
+
fields = params[:fields]
|
166
|
+
include_directives = params[:include_directives]
|
167
|
+
|
168
|
+
source_resource ||= source_klass.find_by_key(source_id, context: context, fields: fields)
|
169
|
+
|
170
|
+
rel_opts = {
|
171
|
+
filters: filters,
|
172
|
+
sort_criteria: sort_criteria,
|
173
|
+
paginator: paginator,
|
174
|
+
fields: fields,
|
175
|
+
context: context,
|
176
|
+
include_directives: include_directives
|
177
|
+
}
|
178
|
+
|
179
|
+
related_resources = nil
|
180
|
+
if params[:cache_serializer]
|
181
|
+
# TODO Could also avoid instantiating source_resource as actual Resource by
|
182
|
+
# allowing LinkBuilder to accept CachedResourceFragment as source in
|
183
|
+
# relationships_related_link
|
184
|
+
scope = source_resource.public_send(:"records_for_#{relationship_type}", rel_opts)
|
185
|
+
relationship = source_klass._relationship(relationship_type)
|
186
|
+
related_resources = relationship.resource_klass.find_serialized_with_caching(
|
187
|
+
scope,
|
188
|
+
params[:cache_serializer],
|
189
|
+
rel_opts
|
190
|
+
)
|
191
|
+
else
|
192
|
+
related_resources = source_resource.public_send(relationship_type, rel_opts)
|
193
|
+
end
|
194
|
+
|
195
|
+
if ((JSONAPI.configuration.top_level_meta_include_record_count) ||
|
196
|
+
(paginator && paginator.class.requires_record_count) ||
|
197
|
+
(JSONAPI.configuration.top_level_meta_include_page_count))
|
198
|
+
related_resource_records = source_resource.public_send("records_for_" + relationship_type)
|
199
|
+
records = resource_klass.filter_records(filters, {},
|
200
|
+
related_resource_records)
|
201
|
+
|
202
|
+
record_count = resource_klass.count_records(records)
|
203
|
+
end
|
204
|
+
|
205
|
+
if (JSONAPI.configuration.top_level_meta_include_page_count && record_count)
|
206
|
+
page_count = paginator.calculate_page_count(record_count)
|
207
|
+
end
|
208
|
+
|
209
|
+
pagination_params = if paginator && JSONAPI.configuration.top_level_links_include_pagination
|
210
|
+
page_options = {}
|
211
|
+
page_options[:record_count] = record_count if paginator.class.requires_record_count
|
212
|
+
paginator.links_page_params(page_options)
|
213
|
+
else
|
214
|
+
{}
|
215
|
+
end
|
216
|
+
|
217
|
+
opts = {}
|
218
|
+
opts.merge!(pagination_params: pagination_params) if JSONAPI.configuration.top_level_links_include_pagination
|
219
|
+
opts.merge!(record_count: record_count) if JSONAPI.configuration.top_level_meta_include_record_count
|
220
|
+
opts.merge!(page_count: page_count) if JSONAPI.configuration.top_level_meta_include_page_count
|
221
|
+
|
222
|
+
return JSONAPI::RelatedResourcesOperationResult.new(:ok,
|
223
|
+
source_resource,
|
224
|
+
relationship_type,
|
225
|
+
related_resources,
|
226
|
+
opts)
|
227
|
+
end
|
228
|
+
|
229
|
+
def create_resource
|
230
|
+
data = params[:data]
|
231
|
+
resource = resource_klass.create(context)
|
232
|
+
result = resource.replace_fields(data)
|
233
|
+
|
234
|
+
return JSONAPI::ResourceOperationResult.new((result == :completed ? :created : :accepted), resource)
|
235
|
+
end
|
236
|
+
|
237
|
+
def remove_resource
|
238
|
+
resource_id = params[:resource_id]
|
239
|
+
|
240
|
+
resource = resource_klass.find_by_key(resource_id, context: context)
|
241
|
+
result = resource.remove
|
242
|
+
|
243
|
+
return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
|
244
|
+
end
|
245
|
+
|
246
|
+
def replace_fields
|
247
|
+
resource_id = params[:resource_id]
|
248
|
+
data = params[:data]
|
249
|
+
|
250
|
+
resource = resource_klass.find_by_key(resource_id, context: context)
|
251
|
+
result = resource.replace_fields(data)
|
252
|
+
|
253
|
+
return JSONAPI::ResourceOperationResult.new(result == :completed ? :ok : :accepted, resource)
|
254
|
+
end
|
255
|
+
|
256
|
+
def replace_to_one_relationship
|
257
|
+
resource_id = params[:resource_id]
|
258
|
+
relationship_type = params[:relationship_type].to_sym
|
259
|
+
key_value = params[:key_value]
|
260
|
+
|
261
|
+
resource = resource_klass.find_by_key(resource_id, context: context)
|
262
|
+
result = resource.replace_to_one_link(relationship_type, key_value)
|
263
|
+
|
264
|
+
return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
|
265
|
+
end
|
266
|
+
|
267
|
+
def replace_polymorphic_to_one_relationship
|
268
|
+
resource_id = params[:resource_id]
|
269
|
+
relationship_type = params[:relationship_type].to_sym
|
270
|
+
key_value = params[:key_value]
|
271
|
+
key_type = params[:key_type]
|
272
|
+
|
273
|
+
resource = resource_klass.find_by_key(resource_id, context: context)
|
274
|
+
result = resource.replace_polymorphic_to_one_link(relationship_type, key_value, key_type)
|
275
|
+
|
276
|
+
return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
|
277
|
+
end
|
278
|
+
|
279
|
+
def create_to_many_relationships
|
280
|
+
resource_id = params[:resource_id]
|
281
|
+
relationship_type = params[:relationship_type].to_sym
|
282
|
+
data = params[:data]
|
283
|
+
|
284
|
+
resource = resource_klass.find_by_key(resource_id, context: context)
|
285
|
+
result = resource.create_to_many_links(relationship_type, data)
|
286
|
+
|
287
|
+
return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
|
288
|
+
end
|
289
|
+
|
290
|
+
def replace_to_many_relationships
|
291
|
+
resource_id = params[:resource_id]
|
292
|
+
relationship_type = params[:relationship_type].to_sym
|
293
|
+
data = params.fetch(:data)
|
294
|
+
|
295
|
+
resource = resource_klass.find_by_key(resource_id, context: context)
|
296
|
+
result = resource.replace_to_many_links(relationship_type, data)
|
297
|
+
|
298
|
+
return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
|
299
|
+
end
|
300
|
+
|
301
|
+
def remove_to_many_relationships
|
302
|
+
resource_id = params[:resource_id]
|
303
|
+
relationship_type = params[:relationship_type].to_sym
|
304
|
+
associated_keys = params[:associated_keys]
|
305
|
+
|
306
|
+
resource = resource_klass.find_by_key(resource_id, context: context)
|
307
|
+
|
308
|
+
complete = true
|
309
|
+
associated_keys.each do |key|
|
310
|
+
result = resource.remove_to_many_link(relationship_type, key)
|
311
|
+
if complete && result != :completed
|
312
|
+
complete = false
|
313
|
+
end
|
314
|
+
end
|
315
|
+
return JSONAPI::OperationResult.new(complete ? :no_content : :accepted)
|
316
|
+
end
|
317
|
+
|
318
|
+
def remove_to_one_relationship
|
319
|
+
resource_id = params[:resource_id]
|
320
|
+
relationship_type = params[:relationship_type].to_sym
|
321
|
+
|
322
|
+
resource = resource_klass.find_by_key(resource_id, context: context)
|
323
|
+
result = resource.remove_to_one_link(relationship_type)
|
324
|
+
|
325
|
+
return JSONAPI::OperationResult.new(result == :completed ? :no_content : :accepted)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module JSONAPI
|
2
|
+
class Relationship
|
3
|
+
attr_reader :acts_as_set, :foreign_key, :options, :name,
|
4
|
+
:class_name, :polymorphic, :always_include_linkage_data,
|
5
|
+
:parent_resource, :eager_load_on_include
|
6
|
+
|
7
|
+
def initialize(name, options = {})
|
8
|
+
@name = name.to_s
|
9
|
+
@options = options
|
10
|
+
@acts_as_set = options.fetch(:acts_as_set, false) == true
|
11
|
+
@foreign_key = options[:foreign_key] ? options[:foreign_key].to_sym : nil
|
12
|
+
@parent_resource = options[:parent_resource]
|
13
|
+
@relation_name = options.fetch(:relation_name, @name)
|
14
|
+
@polymorphic = options.fetch(:polymorphic, false) == true
|
15
|
+
@always_include_linkage_data = options.fetch(:always_include_linkage_data, false) == true
|
16
|
+
@eager_load_on_include = options.fetch(:eager_load_on_include, true) == true
|
17
|
+
end
|
18
|
+
|
19
|
+
alias_method :polymorphic?, :polymorphic
|
20
|
+
|
21
|
+
def primary_key
|
22
|
+
@primary_key ||= resource_klass._primary_key
|
23
|
+
end
|
24
|
+
|
25
|
+
def resource_klass
|
26
|
+
@resource_klass ||= @parent_resource.resource_for(@class_name)
|
27
|
+
end
|
28
|
+
|
29
|
+
def table_name
|
30
|
+
@table_name ||= resource_klass._table_name
|
31
|
+
end
|
32
|
+
|
33
|
+
def type
|
34
|
+
@type ||= resource_klass._type.to_sym
|
35
|
+
end
|
36
|
+
|
37
|
+
def relation_name(options)
|
38
|
+
case @relation_name
|
39
|
+
when Symbol
|
40
|
+
# :nocov:
|
41
|
+
@relation_name
|
42
|
+
# :nocov:
|
43
|
+
when String
|
44
|
+
@relation_name.to_sym
|
45
|
+
when Proc
|
46
|
+
@relation_name.call(options)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def type_for_source(source)
|
51
|
+
if polymorphic?
|
52
|
+
resource = source.public_send(name)
|
53
|
+
resource.class._type if resource
|
54
|
+
else
|
55
|
+
type
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def belongs_to?
|
60
|
+
false
|
61
|
+
end
|
62
|
+
|
63
|
+
class ToOne < Relationship
|
64
|
+
attr_reader :foreign_key_on
|
65
|
+
|
66
|
+
def initialize(name, options = {})
|
67
|
+
super
|
68
|
+
@class_name = options.fetch(:class_name, name.to_s.camelize)
|
69
|
+
@foreign_key ||= "#{name}_id".to_sym
|
70
|
+
@foreign_key_on = options.fetch(:foreign_key_on, :self)
|
71
|
+
end
|
72
|
+
|
73
|
+
def belongs_to?
|
74
|
+
foreign_key_on == :self
|
75
|
+
end
|
76
|
+
|
77
|
+
def polymorphic_type
|
78
|
+
"#{name}_type" if polymorphic?
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class ToMany < Relationship
|
83
|
+
attr_reader :reflect, :inverse_relationship
|
84
|
+
|
85
|
+
def initialize(name, options = {})
|
86
|
+
super
|
87
|
+
@class_name = options.fetch(:class_name, name.to_s.camelize.singularize)
|
88
|
+
@foreign_key ||= "#{name.to_s.singularize}_ids".to_sym
|
89
|
+
@reflect = options.fetch(:reflect, true) == true
|
90
|
+
@inverse_relationship = options.fetch(:inverse_relationship, parent_resource._type.to_s.singularize.to_sym) if parent_resource
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|