jpie 1.0.0 → 1.0.1

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.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/.cursor/rules/release.mdc +62 -0
  3. data/.gitignore +5 -0
  4. data/.rubocop.yml +82 -38
  5. data/Gemfile +12 -10
  6. data/Gemfile.lock +10 -1
  7. data/README.md +675 -1235
  8. data/jpie.gemspec +15 -15
  9. data/kiln/app/resources/user_message_resource.rb +2 -0
  10. data/lib/jpie.rb +0 -1
  11. data/lib/json_api/active_storage/deserialization.rb +32 -22
  12. data/lib/json_api/active_storage/detection.rb +36 -41
  13. data/lib/json_api/active_storage/serialization.rb +13 -11
  14. data/lib/json_api/configuration.rb +4 -5
  15. data/lib/json_api/controllers/base_controller.rb +3 -3
  16. data/lib/json_api/controllers/concerns/controller_helpers/authorization.rb +30 -0
  17. data/lib/json_api/controllers/concerns/controller_helpers/document_meta.rb +20 -0
  18. data/lib/json_api/controllers/concerns/controller_helpers/error_rendering.rb +64 -0
  19. data/lib/json_api/controllers/concerns/controller_helpers/parsing.rb +127 -0
  20. data/lib/json_api/controllers/concerns/controller_helpers/resource_setup.rb +38 -0
  21. data/lib/json_api/controllers/concerns/controller_helpers.rb +11 -215
  22. data/lib/json_api/controllers/concerns/relationships/active_storage_removal.rb +65 -0
  23. data/lib/json_api/controllers/concerns/relationships/events.rb +44 -0
  24. data/lib/json_api/controllers/concerns/relationships/removal.rb +92 -0
  25. data/lib/json_api/controllers/concerns/relationships/response_helpers.rb +55 -0
  26. data/lib/json_api/controllers/concerns/relationships/serialization.rb +72 -0
  27. data/lib/json_api/controllers/concerns/relationships/sorting.rb +114 -0
  28. data/lib/json_api/controllers/concerns/relationships/updating.rb +73 -0
  29. data/lib/json_api/controllers/concerns/relationships_controller/active_storage_removal.rb +67 -0
  30. data/lib/json_api/controllers/concerns/relationships_controller/events.rb +44 -0
  31. data/lib/json_api/controllers/concerns/relationships_controller/removal.rb +92 -0
  32. data/lib/json_api/controllers/concerns/relationships_controller/response_helpers.rb +55 -0
  33. data/lib/json_api/controllers/concerns/relationships_controller/serialization.rb +72 -0
  34. data/lib/json_api/controllers/concerns/relationships_controller/sorting.rb +114 -0
  35. data/lib/json_api/controllers/concerns/relationships_controller/updating.rb +73 -0
  36. data/lib/json_api/controllers/concerns/resource_actions/crud_helpers.rb +93 -0
  37. data/lib/json_api/controllers/concerns/resource_actions/field_validation.rb +114 -0
  38. data/lib/json_api/controllers/concerns/resource_actions/filter_validation.rb +91 -0
  39. data/lib/json_api/controllers/concerns/resource_actions/pagination.rb +51 -0
  40. data/lib/json_api/controllers/concerns/resource_actions/preloading.rb +64 -0
  41. data/lib/json_api/controllers/concerns/resource_actions/resource_loading.rb +71 -0
  42. data/lib/json_api/controllers/concerns/resource_actions/serialization.rb +63 -0
  43. data/lib/json_api/controllers/concerns/resource_actions/type_validation.rb +75 -0
  44. data/lib/json_api/controllers/concerns/resource_actions.rb +51 -602
  45. data/lib/json_api/controllers/relationships_controller.rb +26 -422
  46. data/lib/json_api/errors/parameter_not_allowed.rb +1 -1
  47. data/lib/json_api/railtie.rb +46 -9
  48. data/lib/json_api/resources/active_storage_blob_resource.rb +9 -1
  49. data/lib/json_api/resources/concerns/attributes_dsl.rb +69 -0
  50. data/lib/json_api/resources/concerns/filters_dsl.rb +32 -0
  51. data/lib/json_api/resources/concerns/meta_dsl.rb +23 -0
  52. data/lib/json_api/resources/concerns/model_class_helpers.rb +37 -0
  53. data/lib/json_api/resources/concerns/relationships_dsl.rb +71 -0
  54. data/lib/json_api/resources/concerns/sortable_fields_dsl.rb +36 -0
  55. data/lib/json_api/resources/resource.rb +13 -219
  56. data/lib/json_api/routing.rb +56 -47
  57. data/lib/json_api/serialization/concerns/attributes_deserialization.rb +27 -0
  58. data/lib/json_api/serialization/concerns/attributes_serialization.rb +50 -0
  59. data/lib/json_api/serialization/concerns/deserialization_helpers.rb +115 -0
  60. data/lib/json_api/serialization/concerns/includes_serialization.rb +82 -0
  61. data/lib/json_api/serialization/concerns/links_serialization.rb +33 -0
  62. data/lib/json_api/serialization/concerns/meta_serialization.rb +60 -0
  63. data/lib/json_api/serialization/concerns/model_attributes_transformation.rb +69 -0
  64. data/lib/json_api/serialization/concerns/relationship_processing.rb +119 -0
  65. data/lib/json_api/serialization/concerns/relationships_deserialization.rb +47 -0
  66. data/lib/json_api/serialization/concerns/relationships_serialization.rb +81 -0
  67. data/lib/json_api/serialization/deserializer.rb +10 -346
  68. data/lib/json_api/serialization/serializer.rb +17 -260
  69. data/lib/json_api/support/active_storage_support.rb +10 -13
  70. data/lib/json_api/support/collection_query.rb +14 -370
  71. data/lib/json_api/support/concerns/condition_building.rb +57 -0
  72. data/lib/json_api/support/concerns/nested_filters.rb +130 -0
  73. data/lib/json_api/support/concerns/pagination.rb +30 -0
  74. data/lib/json_api/support/concerns/polymorphic_filters.rb +75 -0
  75. data/lib/json_api/support/concerns/regular_filters.rb +81 -0
  76. data/lib/json_api/support/concerns/sorting.rb +88 -0
  77. data/lib/json_api/support/instrumentation.rb +13 -12
  78. data/lib/json_api/support/param_helpers.rb +9 -6
  79. data/lib/json_api/support/relationship_helpers.rb +4 -2
  80. data/lib/json_api/support/resource_identifier.rb +29 -29
  81. data/lib/json_api/support/responders.rb +5 -5
  82. data/lib/json_api/version.rb +1 -1
  83. metadata +51 -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
- resource_type:,
28
- resource_id:,
29
- relationship_name:
30
- }
31
- payload[:related_type] = related_type if related_type
32
- payload[:related_ids] = Array(related_ids) if related_ids
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
- if value.is_a?(Hash)
43
- value.map { |k, v| "#{CGI.escape(key.to_s)}[#{CGI.escape(k.to_s)}]=#{CGI.escape(v.to_s)}" }
44
- elsif value.is_a?(Array)
45
- value.map { |v| "#{CGI.escape(key.to_s)}=#{CGI.escape(v.to_s)}" }
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
- unless is_polymorphic
33
- expected_type = TypeConversion.model_type_name(association.klass)
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
- begin
40
- related_model_class = if is_polymorphic
41
- TypeConversion.type_to_class_name(type).constantize
42
- else
43
- association.klass
44
- end
45
-
46
- related_model_class.find(id)
47
- rescue ActiveRecord::RecordNotFound
48
- raise ArgumentError, "Related resource not found: #{type} with id #{id}"
49
- rescue NameError
50
- raise ArgumentError, "Invalid relationship type: #{type} does not correspond to a valid model class"
51
- end
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 && (polymorphic_association_for_association?(association, definition) ||
71
- (use_instance_class && sti_subclass?(record.class, association.klass)))
72
- record.class
73
- elsif association
74
- association.klass
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSONAPI
4
- VERSION = "1.0.0"
4
+ VERSION = "1.0.1"
5
5
  end
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.0
4
+ version: 1.0.1
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,68 @@ 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
93
+ - lib/json_api/controllers/concerns/relationships_controller/active_storage_removal.rb
94
+ - lib/json_api/controllers/concerns/relationships_controller/events.rb
95
+ - lib/json_api/controllers/concerns/relationships_controller/removal.rb
96
+ - lib/json_api/controllers/concerns/relationships_controller/response_helpers.rb
97
+ - lib/json_api/controllers/concerns/relationships_controller/serialization.rb
98
+ - lib/json_api/controllers/concerns/relationships_controller/sorting.rb
99
+ - lib/json_api/controllers/concerns/relationships_controller/updating.rb
80
100
  - lib/json_api/controllers/concerns/resource_actions.rb
101
+ - lib/json_api/controllers/concerns/resource_actions/crud_helpers.rb
102
+ - lib/json_api/controllers/concerns/resource_actions/field_validation.rb
103
+ - lib/json_api/controllers/concerns/resource_actions/filter_validation.rb
104
+ - lib/json_api/controllers/concerns/resource_actions/pagination.rb
105
+ - lib/json_api/controllers/concerns/resource_actions/preloading.rb
106
+ - lib/json_api/controllers/concerns/resource_actions/resource_loading.rb
107
+ - lib/json_api/controllers/concerns/resource_actions/serialization.rb
108
+ - lib/json_api/controllers/concerns/resource_actions/type_validation.rb
81
109
  - lib/json_api/controllers/relationships_controller.rb
82
110
  - lib/json_api/controllers/resources_controller.rb
83
111
  - lib/json_api/errors/parameter_not_allowed.rb
84
112
  - lib/json_api/railtie.rb
85
113
  - lib/json_api/resources/active_storage_blob_resource.rb
114
+ - lib/json_api/resources/concerns/attributes_dsl.rb
115
+ - lib/json_api/resources/concerns/filters_dsl.rb
116
+ - lib/json_api/resources/concerns/meta_dsl.rb
117
+ - lib/json_api/resources/concerns/model_class_helpers.rb
118
+ - lib/json_api/resources/concerns/relationships_dsl.rb
119
+ - lib/json_api/resources/concerns/sortable_fields_dsl.rb
86
120
  - lib/json_api/resources/resource.rb
87
121
  - lib/json_api/resources/resource_loader.rb
88
122
  - lib/json_api/routing.rb
123
+ - lib/json_api/serialization/concerns/attributes_deserialization.rb
124
+ - lib/json_api/serialization/concerns/attributes_serialization.rb
125
+ - lib/json_api/serialization/concerns/deserialization_helpers.rb
126
+ - lib/json_api/serialization/concerns/includes_serialization.rb
127
+ - lib/json_api/serialization/concerns/links_serialization.rb
128
+ - lib/json_api/serialization/concerns/meta_serialization.rb
129
+ - lib/json_api/serialization/concerns/model_attributes_transformation.rb
130
+ - lib/json_api/serialization/concerns/relationship_processing.rb
131
+ - lib/json_api/serialization/concerns/relationships_deserialization.rb
132
+ - lib/json_api/serialization/concerns/relationships_serialization.rb
89
133
  - lib/json_api/serialization/deserializer.rb
90
134
  - lib/json_api/serialization/serializer.rb
91
135
  - lib/json_api/support/active_storage_support.rb
92
136
  - lib/json_api/support/collection_query.rb
137
+ - lib/json_api/support/concerns/condition_building.rb
138
+ - lib/json_api/support/concerns/nested_filters.rb
139
+ - lib/json_api/support/concerns/pagination.rb
140
+ - lib/json_api/support/concerns/polymorphic_filters.rb
141
+ - lib/json_api/support/concerns/regular_filters.rb
142
+ - lib/json_api/support/concerns/sorting.rb
93
143
  - lib/json_api/support/instrumentation.rb
94
144
  - lib/json_api/support/param_helpers.rb
95
145
  - lib/json_api/support/relationship_guard.rb