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
|