jpie 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.cursor/rules/release.mdc +62 -0
- data/.gitignore +5 -0
- data/.rubocop.yml +82 -38
- data/Gemfile +13 -10
- data/Gemfile.lock +18 -1
- data/README.md +675 -1235
- data/Rakefile +22 -0
- data/jpie.gemspec +15 -15
- data/kiln/app/resources/user_message_resource.rb +2 -0
- data/lib/jpie.rb +0 -1
- data/lib/json_api/active_storage/deserialization.rb +32 -22
- data/lib/json_api/active_storage/detection.rb +36 -41
- data/lib/json_api/active_storage/serialization.rb +13 -11
- data/lib/json_api/configuration.rb +4 -5
- data/lib/json_api/controllers/base_controller.rb +3 -3
- data/lib/json_api/controllers/concerns/controller_helpers/authorization.rb +30 -0
- data/lib/json_api/controllers/concerns/controller_helpers/document_meta.rb +20 -0
- data/lib/json_api/controllers/concerns/controller_helpers/error_rendering.rb +64 -0
- data/lib/json_api/controllers/concerns/controller_helpers/parsing.rb +127 -0
- data/lib/json_api/controllers/concerns/controller_helpers/resource_setup.rb +38 -0
- data/lib/json_api/controllers/concerns/controller_helpers.rb +11 -215
- data/lib/json_api/controllers/concerns/relationships/active_storage_removal.rb +65 -0
- data/lib/json_api/controllers/concerns/relationships/events.rb +44 -0
- data/lib/json_api/controllers/concerns/relationships/removal.rb +92 -0
- data/lib/json_api/controllers/concerns/relationships/response_helpers.rb +55 -0
- data/lib/json_api/controllers/concerns/relationships/serialization.rb +72 -0
- data/lib/json_api/controllers/concerns/relationships/sorting.rb +114 -0
- data/lib/json_api/controllers/concerns/relationships/updating.rb +73 -0
- data/lib/json_api/controllers/concerns/resource_actions/crud_helpers.rb +93 -0
- data/lib/json_api/controllers/concerns/resource_actions/field_validation.rb +114 -0
- data/lib/json_api/controllers/concerns/resource_actions/filter_validation.rb +91 -0
- data/lib/json_api/controllers/concerns/resource_actions/pagination.rb +51 -0
- data/lib/json_api/controllers/concerns/resource_actions/preloading.rb +64 -0
- data/lib/json_api/controllers/concerns/resource_actions/resource_loading.rb +71 -0
- data/lib/json_api/controllers/concerns/resource_actions/serialization.rb +63 -0
- data/lib/json_api/controllers/concerns/resource_actions/type_validation.rb +75 -0
- data/lib/json_api/controllers/concerns/resource_actions.rb +51 -602
- data/lib/json_api/controllers/relationships_controller.rb +26 -422
- data/lib/json_api/errors/parameter_not_allowed.rb +1 -1
- data/lib/json_api/railtie.rb +46 -9
- data/lib/json_api/resources/concerns/attributes_dsl.rb +69 -0
- data/lib/json_api/resources/concerns/filters_dsl.rb +32 -0
- data/lib/json_api/resources/concerns/meta_dsl.rb +23 -0
- data/lib/json_api/resources/concerns/model_class_helpers.rb +37 -0
- data/lib/json_api/resources/concerns/relationships_dsl.rb +71 -0
- data/lib/json_api/resources/concerns/sortable_fields_dsl.rb +36 -0
- data/lib/json_api/resources/resource.rb +13 -219
- data/lib/json_api/routing.rb +56 -47
- data/lib/json_api/serialization/concerns/attributes_deserialization.rb +27 -0
- data/lib/json_api/serialization/concerns/attributes_serialization.rb +50 -0
- data/lib/json_api/serialization/concerns/deserialization_helpers.rb +115 -0
- data/lib/json_api/serialization/concerns/includes_serialization.rb +82 -0
- data/lib/json_api/serialization/concerns/links_serialization.rb +33 -0
- data/lib/json_api/serialization/concerns/meta_serialization.rb +60 -0
- data/lib/json_api/serialization/concerns/model_attributes_transformation.rb +69 -0
- data/lib/json_api/serialization/concerns/relationship_processing.rb +119 -0
- data/lib/json_api/serialization/concerns/relationships_deserialization.rb +47 -0
- data/lib/json_api/serialization/concerns/relationships_serialization.rb +81 -0
- data/lib/json_api/serialization/deserializer.rb +10 -346
- data/lib/json_api/serialization/serializer.rb +17 -260
- data/lib/json_api/support/active_storage_support.rb +10 -13
- data/lib/json_api/support/collection_query.rb +14 -370
- data/lib/json_api/support/concerns/condition_building.rb +57 -0
- data/lib/json_api/support/concerns/nested_filters.rb +130 -0
- data/lib/json_api/support/concerns/pagination.rb +30 -0
- data/lib/json_api/support/concerns/polymorphic_filters.rb +75 -0
- data/lib/json_api/support/concerns/regular_filters.rb +81 -0
- data/lib/json_api/support/concerns/sorting.rb +88 -0
- data/lib/json_api/support/instrumentation.rb +13 -12
- data/lib/json_api/support/param_helpers.rb +9 -6
- data/lib/json_api/support/relationship_helpers.rb +4 -2
- data/lib/json_api/support/resource_identifier.rb +29 -29
- data/lib/json_api/support/responders.rb +5 -5
- data/lib/json_api/version.rb +1 -1
- metadata +44 -1
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSONAPI
|
|
4
|
+
module Support
|
|
5
|
+
module PolymorphicFilters
|
|
6
|
+
def apply_polymorphic_nested_filters(scope, association, _relationship_name, attributes)
|
|
7
|
+
context = build_polymorphic_filter_context(association, attributes)
|
|
8
|
+
|
|
9
|
+
attributes.each do |attr_name, attr_value|
|
|
10
|
+
scope = apply_polymorphic_attribute_filter(scope, attr_name, attr_value, context)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
apply_final_type_filter(scope, context)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def build_polymorphic_filter_context(association, attributes)
|
|
19
|
+
{
|
|
20
|
+
foreign_key: association.foreign_key,
|
|
21
|
+
foreign_type: association.foreign_type,
|
|
22
|
+
fk_column: model_class.column_for_attribute(association.foreign_key),
|
|
23
|
+
type_value: attributes["type"] || attributes["type_eq"],
|
|
24
|
+
class_name: association.options[:class_name],
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def apply_polymorphic_attribute_filter(scope, attr_name, attr_value, context)
|
|
29
|
+
return scope if empty_filter_value?(attr_value)
|
|
30
|
+
return scope if attr_name == "type"
|
|
31
|
+
|
|
32
|
+
scope = apply_polymorphic_id_filter(scope, attr_name, attr_value, context)
|
|
33
|
+
apply_polymorphic_type_filter(scope, context)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def apply_polymorphic_id_filter(scope, attr_name, attr_value, context)
|
|
37
|
+
column_filter = parse_column_filter(attr_name)
|
|
38
|
+
return apply_polymorphic_fk_filter(scope, attr_value, column_filter, context) if polymorphic_fk_filter?(
|
|
39
|
+
column_filter, context,
|
|
40
|
+
)
|
|
41
|
+
return scope.where(context[:foreign_key] => attr_value) if attr_name == "id"
|
|
42
|
+
|
|
43
|
+
scope
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def polymorphic_fk_filter?(column_filter, context)
|
|
47
|
+
column_filter && column_filter[:column] == "id" && context[:fk_column]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def apply_polymorphic_fk_filter(scope, attr_value, column_filter, context)
|
|
51
|
+
value = normalize_filter_value_for_model(model_class, context[:fk_column], attr_value)
|
|
52
|
+
return scope unless value
|
|
53
|
+
|
|
54
|
+
condition = build_condition_for_model(model_class, context[:fk_column], value, column_filter[:operator])
|
|
55
|
+
condition ? apply_condition(scope, condition) : scope
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def apply_polymorphic_type_filter(scope, context)
|
|
59
|
+
return scope unless context[:foreign_type]
|
|
60
|
+
return scope.where(context[:foreign_type] => context[:class_name]) if context[:class_name]
|
|
61
|
+
return scope.where(context[:foreign_type] => context[:type_value]) if context[:type_value]
|
|
62
|
+
|
|
63
|
+
scope
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def apply_final_type_filter(scope, context)
|
|
67
|
+
return scope unless context[:foreign_type]
|
|
68
|
+
return scope if context[:class_name]
|
|
69
|
+
return scope unless context[:type_value]
|
|
70
|
+
|
|
71
|
+
scope.where(context[:foreign_type] => context[:type_value])
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSONAPI
|
|
4
|
+
module Support
|
|
5
|
+
module RegularFilters
|
|
6
|
+
def apply_regular_filters(scope)
|
|
7
|
+
return scope if filter_params.empty?
|
|
8
|
+
|
|
9
|
+
regular_filters = filter_params.reject { |k, _v| k.to_s.include?(".") }
|
|
10
|
+
return scope if regular_filters.empty?
|
|
11
|
+
|
|
12
|
+
regular_filters.reduce(scope) do |current_scope, (filter_name, filter_value)|
|
|
13
|
+
apply_regular_filter(current_scope, filter_name.to_s, filter_value)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def apply_regular_filter(scope, filter_name, filter_value)
|
|
20
|
+
return scope if filter_value.respond_to?(:empty?) ? filter_value.empty? : filter_value.nil?
|
|
21
|
+
|
|
22
|
+
column_filter = parse_column_filter(filter_name)
|
|
23
|
+
if column_filter
|
|
24
|
+
apply_column_filter(scope, column_filter, filter_value)
|
|
25
|
+
else
|
|
26
|
+
apply_scope_fallback(scope, filter_name, filter_value)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def parse_column_filter(filter_name)
|
|
31
|
+
match = filter_name.match(/\A(.+)_(eq|match|lt|lte|gt|gte)\z/)
|
|
32
|
+
return nil unless match
|
|
33
|
+
|
|
34
|
+
column_name = match[1]
|
|
35
|
+
operator = match[2].to_sym
|
|
36
|
+
|
|
37
|
+
{ column: column_name, operator: }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def apply_column_filter(scope, column_filter, raw_value)
|
|
41
|
+
condition = build_column_condition(column_filter, raw_value)
|
|
42
|
+
condition ? apply_condition(scope, condition) : scope
|
|
43
|
+
rescue StandardError => e
|
|
44
|
+
log_filter_error(column_filter, column_filter[:operator], e)
|
|
45
|
+
scope
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def build_column_condition(column_filter, raw_value)
|
|
49
|
+
column = model_class.column_for_attribute(column_filter[:column])
|
|
50
|
+
return nil unless column
|
|
51
|
+
|
|
52
|
+
value = normalize_filter_value(column, raw_value)
|
|
53
|
+
return nil if value.nil?
|
|
54
|
+
|
|
55
|
+
build_condition(column, value, column_filter[:operator])
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def log_filter_error(column_filter, operator, error)
|
|
59
|
+
return unless defined?(Rails.logger)
|
|
60
|
+
|
|
61
|
+
Rails.logger.warn("Filter error for #{column_filter[:column]}_#{operator}: #{error.class} - #{error.message}")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def normalize_filter_value(column, raw_value)
|
|
65
|
+
value = raw_value.is_a?(Array) ? raw_value.first : raw_value
|
|
66
|
+
return nil if value.nil?
|
|
67
|
+
|
|
68
|
+
type = model_class.type_for_attribute(column.name)
|
|
69
|
+
type.cast(value)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def apply_scope_fallback(scope, filter_name, filter_value)
|
|
73
|
+
return scope unless model_class.respond_to?(filter_name.to_sym)
|
|
74
|
+
|
|
75
|
+
scope.public_send(filter_name.to_sym, filter_value)
|
|
76
|
+
rescue ArgumentError, NoMethodError
|
|
77
|
+
scope
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSONAPI
|
|
4
|
+
module Support
|
|
5
|
+
module Sorting
|
|
6
|
+
def apply_sorting(scope)
|
|
7
|
+
return scope if sort_params.empty?
|
|
8
|
+
|
|
9
|
+
has_virtual_sorts = sort_params.any? { |sort_field| virtual_attribute_sort?(sort_field) }
|
|
10
|
+
has_virtual_sorts ? apply_virtual_sorting(scope) : apply_db_sorting(scope)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def apply_virtual_sorting(scope)
|
|
16
|
+
records = scope.to_a
|
|
17
|
+
apply_mixed_sorting(records, sort_params)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def apply_db_sorting(scope)
|
|
21
|
+
sort_params.each do |sort_field|
|
|
22
|
+
direction = RelationshipHelpers.extract_sort_direction(sort_field)
|
|
23
|
+
field = RelationshipHelpers.extract_sort_field_name(sort_field)
|
|
24
|
+
scope = scope.order(field => direction)
|
|
25
|
+
end
|
|
26
|
+
scope
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def virtual_attribute_sort?(sort_field)
|
|
30
|
+
field = RelationshipHelpers.extract_sort_field_name(sort_field)
|
|
31
|
+
!model_class.column_names.include?(field.to_s)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def apply_mixed_sorting(records, all_sorts)
|
|
35
|
+
records.sort do |a, b|
|
|
36
|
+
compare_by_sort_criteria(a, b, all_sorts)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def compare_by_sort_criteria(record_a, record_b, all_sorts)
|
|
41
|
+
all_sorts.each do |sort_field|
|
|
42
|
+
direction = RelationshipHelpers.extract_sort_direction(sort_field)
|
|
43
|
+
field = RelationshipHelpers.extract_sort_field_name(sort_field)
|
|
44
|
+
value_a = fetch_sort_value(record_a, field)
|
|
45
|
+
value_b = fetch_sort_value(record_b, field)
|
|
46
|
+
comparison = compare_values(value_a, value_b)
|
|
47
|
+
next if comparison.zero?
|
|
48
|
+
|
|
49
|
+
return direction == :desc ? -comparison : comparison
|
|
50
|
+
end
|
|
51
|
+
0
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def fetch_sort_value(record, field)
|
|
55
|
+
return fetch_column_value(record, field) if model_class.column_names.include?(field.to_s)
|
|
56
|
+
|
|
57
|
+
fetch_virtual_attribute_value(record, field)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def fetch_column_value(record, field)
|
|
61
|
+
return record.public_send(field.to_sym) if record.respond_to?(field.to_sym)
|
|
62
|
+
return nil unless record.respond_to?(:attributes) && record.attributes.is_a?(Hash)
|
|
63
|
+
|
|
64
|
+
record.attributes[field.to_s] || record.attributes[field.to_sym]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def fetch_virtual_attribute_value(record, field)
|
|
68
|
+
definition_instance = definition.new(record, {})
|
|
69
|
+
fetch_virtual_value(definition_instance, field)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def fetch_virtual_value(definition_instance, field)
|
|
73
|
+
field_sym = field.to_sym
|
|
74
|
+
return definition_instance.public_send(field_sym) if definition_instance.respond_to?(field_sym, false)
|
|
75
|
+
|
|
76
|
+
nil
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def compare_values(value_a, value_b)
|
|
80
|
+
return 0 if value_a.nil? && value_b.nil?
|
|
81
|
+
return -1 if value_a.nil?
|
|
82
|
+
return 1 if value_b.nil?
|
|
83
|
+
|
|
84
|
+
value_a <=> value_b
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -14,7 +14,7 @@ module JSONAPI
|
|
|
14
14
|
"jsonapi.#{resource_type}.#{action}",
|
|
15
15
|
resource_type:,
|
|
16
16
|
resource_id:,
|
|
17
|
-
changes: changes.compact
|
|
17
|
+
changes: changes.compact,
|
|
18
18
|
)
|
|
19
19
|
end
|
|
20
20
|
end
|
|
@@ -23,19 +23,20 @@ module JSONAPI
|
|
|
23
23
|
related_type: nil)
|
|
24
24
|
return unless enabled?
|
|
25
25
|
|
|
26
|
-
payload =
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
payload = build_relationship_payload(resource_type, resource_id, relationship_name, related_type, related_ids)
|
|
27
|
+
notify_relationship(action, resource_type, payload)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.build_relationship_payload(resource_type, resource_id, relationship_name, related_type, related_ids)
|
|
31
|
+
{ resource_type:, resource_id:, relationship_name: }.tap do |p|
|
|
32
|
+
p[:related_type] = related_type if related_type
|
|
33
|
+
p[:related_ids] = Array(related_ids) if related_ids
|
|
34
|
+
end
|
|
35
|
+
end
|
|
33
36
|
|
|
37
|
+
def self.notify_relationship(action, resource_type, payload)
|
|
34
38
|
Rails.event.tagged("jsonapi", "relationship") do
|
|
35
|
-
Rails.event.notify(
|
|
36
|
-
"jsonapi.#{resource_type}.relationship.#{action}",
|
|
37
|
-
payload
|
|
38
|
-
)
|
|
39
|
+
Rails.event.notify("jsonapi.#{resource_type}.relationship.#{action}", payload)
|
|
39
40
|
end
|
|
40
41
|
end
|
|
41
42
|
end
|
|
@@ -39,13 +39,16 @@ module JSONAPI
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def build_query_parts_for_param(key, value)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
else
|
|
47
|
-
["#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"]
|
|
42
|
+
encoded_key = CGI.escape(key.to_s)
|
|
43
|
+
case value
|
|
44
|
+
when Hash then value.map { |k, v| "#{encoded_key}[#{esc(k)}]=#{esc(v)}" }
|
|
45
|
+
when Array then value.map { |v| "#{encoded_key}=#{esc(v)}" }
|
|
46
|
+
else ["#{encoded_key}=#{esc(value)}"]
|
|
48
47
|
end
|
|
49
48
|
end
|
|
49
|
+
|
|
50
|
+
def esc(val)
|
|
51
|
+
CGI.escape(val.to_s)
|
|
52
|
+
end
|
|
50
53
|
end
|
|
51
54
|
end
|
|
@@ -27,14 +27,16 @@ module JSONAPI
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
# Delegate to ResourceIdentifier
|
|
30
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
|
30
31
|
def serialize_resource_identifier(
|
|
31
32
|
record, association: nil, resource_class: nil, use_instance_class: false, base_resource_class: nil
|
|
32
33
|
)
|
|
34
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
|
33
35
|
ResourceIdentifier.serialize_identifier(
|
|
34
36
|
record,
|
|
35
37
|
association:,
|
|
36
38
|
definition: resource_class,
|
|
37
|
-
use_instance_class
|
|
39
|
+
use_instance_class:,
|
|
38
40
|
)
|
|
39
41
|
end
|
|
40
42
|
|
|
@@ -51,7 +53,7 @@ module JSONAPI
|
|
|
51
53
|
identifier,
|
|
52
54
|
association:,
|
|
53
55
|
definition: resource_class,
|
|
54
|
-
relationship_name
|
|
56
|
+
relationship_name:,
|
|
55
57
|
)
|
|
56
58
|
end
|
|
57
59
|
|
|
@@ -6,7 +6,7 @@ module JSONAPI
|
|
|
6
6
|
|
|
7
7
|
def serialize_identifier(record, association:, definition:, use_instance_class: false)
|
|
8
8
|
model_class = determine_model_class(record, association:, definition:,
|
|
9
|
-
use_instance_class
|
|
9
|
+
use_instance_class:,)
|
|
10
10
|
related_definition = JSONAPI::ResourceLoader.find_for_model(model_class)
|
|
11
11
|
related_type = TypeConversion.resource_type_name(related_definition)
|
|
12
12
|
|
|
@@ -24,31 +24,34 @@ module JSONAPI
|
|
|
24
24
|
def resolve_and_find_related_record(identifier, association:, definition:, relationship_name:)
|
|
25
25
|
type = extract_type(identifier)
|
|
26
26
|
id = extract_id(identifier)
|
|
27
|
-
|
|
28
27
|
raise ArgumentError, "Missing type or id in relationship data" unless type && id
|
|
29
28
|
|
|
30
29
|
is_polymorphic = polymorphic_association?(definition, relationship_name)
|
|
30
|
+
validate_relationship_type!(type, association) unless is_polymorphic
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
if type != expected_type
|
|
35
|
-
raise ArgumentError, "Invalid relationship type: expected #{expected_type}, got #{type}"
|
|
36
|
-
end
|
|
37
|
-
end
|
|
32
|
+
find_related_record(type, id, association, is_polymorphic)
|
|
33
|
+
end
|
|
38
34
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
35
|
+
def validate_relationship_type!(type, association)
|
|
36
|
+
expected_type = TypeConversion.model_type_name(association.klass)
|
|
37
|
+
return if type == expected_type
|
|
38
|
+
|
|
39
|
+
raise ArgumentError, "Invalid relationship type: expected #{expected_type}, got #{type}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def find_related_record(type, id, association, is_polymorphic)
|
|
43
|
+
related_model_class = resolve_related_model_class(type, association, is_polymorphic)
|
|
44
|
+
related_model_class.find(id)
|
|
45
|
+
rescue ActiveRecord::RecordNotFound
|
|
46
|
+
raise ArgumentError, "Related resource not found: #{type} with id #{id}"
|
|
47
|
+
rescue NameError
|
|
48
|
+
raise ArgumentError, "Invalid relationship type: #{type} does not correspond to a valid model class"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def resolve_related_model_class(type, association, is_polymorphic)
|
|
52
|
+
return TypeConversion.type_to_class_name(type).constantize if is_polymorphic
|
|
53
|
+
|
|
54
|
+
association.klass
|
|
52
55
|
end
|
|
53
56
|
|
|
54
57
|
def polymorphic_association?(definition, relationship_name)
|
|
@@ -67,14 +70,11 @@ module JSONAPI
|
|
|
67
70
|
end
|
|
68
71
|
|
|
69
72
|
def determine_model_class(record, association:, definition:, use_instance_class:)
|
|
70
|
-
if association
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
else
|
|
76
|
-
record.class
|
|
77
|
-
end
|
|
73
|
+
return record.class if association.nil?
|
|
74
|
+
return record.class if polymorphic_association_for_association?(association, definition)
|
|
75
|
+
return record.class if use_instance_class && sti_subclass?(record.class, association.klass)
|
|
76
|
+
|
|
77
|
+
association.klass
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
def polymorphic_association_for_association?(association, definition)
|
|
@@ -22,9 +22,9 @@ module JSONAPI
|
|
|
22
22
|
{
|
|
23
23
|
status: "415",
|
|
24
24
|
title: "Unsupported Media Type",
|
|
25
|
-
detail: "Content-Type must be application/vnd.api+json"
|
|
26
|
-
}
|
|
27
|
-
]
|
|
25
|
+
detail: "Content-Type must be application/vnd.api+json",
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
28
|
}, status: :unsupported_media_type
|
|
29
29
|
end
|
|
30
30
|
|
|
@@ -59,7 +59,7 @@ module JSONAPI
|
|
|
59
59
|
[nil],
|
|
60
60
|
title: "Not Acceptable",
|
|
61
61
|
detail_proc: ->(_) { "Accept header must include application/vnd.api+json or be omitted" },
|
|
62
|
-
status: :not_acceptable
|
|
62
|
+
status: :not_acceptable,
|
|
63
63
|
)
|
|
64
64
|
end
|
|
65
65
|
|
|
@@ -82,7 +82,7 @@ module JSONAPI
|
|
|
82
82
|
errors = Array(values).map do |value|
|
|
83
83
|
error = {
|
|
84
84
|
title:,
|
|
85
|
-
detail: detail_proc.call(value)
|
|
85
|
+
detail: detail_proc.call(value),
|
|
86
86
|
}
|
|
87
87
|
error[:source] = source_proc.call(value) if source_proc
|
|
88
88
|
error
|
data/lib/json_api/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jpie
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Emil Kampp
|
|
@@ -57,6 +57,7 @@ executables: []
|
|
|
57
57
|
extensions: []
|
|
58
58
|
extra_rdoc_files: []
|
|
59
59
|
files:
|
|
60
|
+
- ".cursor/rules/release.mdc"
|
|
60
61
|
- ".gitignore"
|
|
61
62
|
- ".rspec"
|
|
62
63
|
- ".rubocop.yml"
|
|
@@ -77,19 +78,61 @@ files:
|
|
|
77
78
|
- lib/json_api/configuration.rb
|
|
78
79
|
- lib/json_api/controllers/base_controller.rb
|
|
79
80
|
- lib/json_api/controllers/concerns/controller_helpers.rb
|
|
81
|
+
- lib/json_api/controllers/concerns/controller_helpers/authorization.rb
|
|
82
|
+
- lib/json_api/controllers/concerns/controller_helpers/document_meta.rb
|
|
83
|
+
- lib/json_api/controllers/concerns/controller_helpers/error_rendering.rb
|
|
84
|
+
- lib/json_api/controllers/concerns/controller_helpers/parsing.rb
|
|
85
|
+
- lib/json_api/controllers/concerns/controller_helpers/resource_setup.rb
|
|
86
|
+
- lib/json_api/controllers/concerns/relationships/active_storage_removal.rb
|
|
87
|
+
- lib/json_api/controllers/concerns/relationships/events.rb
|
|
88
|
+
- lib/json_api/controllers/concerns/relationships/removal.rb
|
|
89
|
+
- lib/json_api/controllers/concerns/relationships/response_helpers.rb
|
|
90
|
+
- lib/json_api/controllers/concerns/relationships/serialization.rb
|
|
91
|
+
- lib/json_api/controllers/concerns/relationships/sorting.rb
|
|
92
|
+
- lib/json_api/controllers/concerns/relationships/updating.rb
|
|
80
93
|
- lib/json_api/controllers/concerns/resource_actions.rb
|
|
94
|
+
- lib/json_api/controllers/concerns/resource_actions/crud_helpers.rb
|
|
95
|
+
- lib/json_api/controllers/concerns/resource_actions/field_validation.rb
|
|
96
|
+
- lib/json_api/controllers/concerns/resource_actions/filter_validation.rb
|
|
97
|
+
- lib/json_api/controllers/concerns/resource_actions/pagination.rb
|
|
98
|
+
- lib/json_api/controllers/concerns/resource_actions/preloading.rb
|
|
99
|
+
- lib/json_api/controllers/concerns/resource_actions/resource_loading.rb
|
|
100
|
+
- lib/json_api/controllers/concerns/resource_actions/serialization.rb
|
|
101
|
+
- lib/json_api/controllers/concerns/resource_actions/type_validation.rb
|
|
81
102
|
- lib/json_api/controllers/relationships_controller.rb
|
|
82
103
|
- lib/json_api/controllers/resources_controller.rb
|
|
83
104
|
- lib/json_api/errors/parameter_not_allowed.rb
|
|
84
105
|
- lib/json_api/railtie.rb
|
|
85
106
|
- lib/json_api/resources/active_storage_blob_resource.rb
|
|
107
|
+
- lib/json_api/resources/concerns/attributes_dsl.rb
|
|
108
|
+
- lib/json_api/resources/concerns/filters_dsl.rb
|
|
109
|
+
- lib/json_api/resources/concerns/meta_dsl.rb
|
|
110
|
+
- lib/json_api/resources/concerns/model_class_helpers.rb
|
|
111
|
+
- lib/json_api/resources/concerns/relationships_dsl.rb
|
|
112
|
+
- lib/json_api/resources/concerns/sortable_fields_dsl.rb
|
|
86
113
|
- lib/json_api/resources/resource.rb
|
|
87
114
|
- lib/json_api/resources/resource_loader.rb
|
|
88
115
|
- lib/json_api/routing.rb
|
|
116
|
+
- lib/json_api/serialization/concerns/attributes_deserialization.rb
|
|
117
|
+
- lib/json_api/serialization/concerns/attributes_serialization.rb
|
|
118
|
+
- lib/json_api/serialization/concerns/deserialization_helpers.rb
|
|
119
|
+
- lib/json_api/serialization/concerns/includes_serialization.rb
|
|
120
|
+
- lib/json_api/serialization/concerns/links_serialization.rb
|
|
121
|
+
- lib/json_api/serialization/concerns/meta_serialization.rb
|
|
122
|
+
- lib/json_api/serialization/concerns/model_attributes_transformation.rb
|
|
123
|
+
- lib/json_api/serialization/concerns/relationship_processing.rb
|
|
124
|
+
- lib/json_api/serialization/concerns/relationships_deserialization.rb
|
|
125
|
+
- lib/json_api/serialization/concerns/relationships_serialization.rb
|
|
89
126
|
- lib/json_api/serialization/deserializer.rb
|
|
90
127
|
- lib/json_api/serialization/serializer.rb
|
|
91
128
|
- lib/json_api/support/active_storage_support.rb
|
|
92
129
|
- lib/json_api/support/collection_query.rb
|
|
130
|
+
- lib/json_api/support/concerns/condition_building.rb
|
|
131
|
+
- lib/json_api/support/concerns/nested_filters.rb
|
|
132
|
+
- lib/json_api/support/concerns/pagination.rb
|
|
133
|
+
- lib/json_api/support/concerns/polymorphic_filters.rb
|
|
134
|
+
- lib/json_api/support/concerns/regular_filters.rb
|
|
135
|
+
- lib/json_api/support/concerns/sorting.rb
|
|
93
136
|
- lib/json_api/support/instrumentation.rb
|
|
94
137
|
- lib/json_api/support/param_helpers.rb
|
|
95
138
|
- lib/json_api/support/relationship_guard.rb
|