jpie 1.3.1 → 1.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dea557b52cade83077a80c1371aef0a85dc756bbe871e24c345cf9c8caa67fdf
4
- data.tar.gz: 2981d0c427c7b3e7b4ab7647614823ed324185812dc92c5daf9124c06c5c55b8
3
+ metadata.gz: d6718c74febd247abaa1dd7294fd729209a3e905363e02d8bb88f20e6a8ee92e
4
+ data.tar.gz: 388c8ee9564f191f14e87d0095d15657dd4ccb55c841d26755d8d7916811d0f0
5
5
  SHA512:
6
- metadata.gz: c58a23284f8d71094e492a75ef5e7e814b796fe97955ca67f916fea1b27442f73f6d6879c899f6cf488738ecd4f701374b1de9a82a4727623ec80168c7cab511
7
- data.tar.gz: 1922cb663461ba5e2c3e146489d8fbffb9a5449642cc4a6245ce643829cd108cb58fa85d11bb21fd6f84a76b4c5f102103f8ea27c6c11ed1916427b4658ff1fb
6
+ metadata.gz: d4188461bf266a55290b2728691e8b052b8f8eeb2ba319949aa0fbb23109dc9175e2c7d29aa53e3768fe794738cd7f5b70e83f624ec0ff72af13051159eb4800
7
+ data.tar.gz: dd193aec85fc8fb12ef21200eca3341411d7c12c0e32f5d2d50562d4fcbefd76ddfc4d08ea36b2c69204c2b579fa2dd9cac18cd2aa4540898f4a9f9543422218
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- jpie (1.3.1)
4
+ jpie (1.5.0)
5
5
  actionpack (~> 8.0, >= 8.0.0)
6
6
  rails (~> 8.0, >= 8.0.0)
7
7
 
data/README.md CHANGED
@@ -238,6 +238,8 @@ Nested includes use dot notation and support arbitrary depth. Related resources
238
238
  }
239
239
  ```
240
240
 
241
+ Only relationships that appear in the `include` path are serialized (and thus loaded). For example, `include=messages` adds messages to `included` but does not serialize each message's `user`, `message_action`, or other relationships, so those associations are not queried. Use `include=messages,messages.user,messages.message_action` (etc.) to include and load nested relationships.
242
+
241
243
  ## Polymorphic Relationships
242
244
 
243
245
  Polymorphic relationships are accessed through the parent resource's relationship endpoints and includes. Route only the parent resource; the gem automatically handles polymorphic types through the relationship endpoints.
@@ -1392,7 +1394,7 @@ api.define(
1392
1394
  },
1393
1395
  {
1394
1396
  collectionPath: "users",
1395
- }
1397
+ },
1396
1398
  );
1397
1399
 
1398
1400
  // Model with relationships
@@ -1412,7 +1414,7 @@ api.define(
1412
1414
  },
1413
1415
  {
1414
1416
  collectionPath: "posts",
1415
- }
1417
+ },
1416
1418
  );
1417
1419
  ```
1418
1420
 
@@ -1622,7 +1624,7 @@ api.define(
1622
1624
  },
1623
1625
  {
1624
1626
  collectionPath: "workstreams",
1625
- }
1627
+ },
1626
1628
  );
1627
1629
 
1628
1630
  // Create with polymorphic relationship
@@ -65,7 +65,14 @@ module JSONAPI
65
65
  end
66
66
 
67
67
  def define_explicit_sti_routes(sti_resources, defaults)
68
- sti_resources.each { |sub_resource_name| jsonapi_resources(sub_resource_name, defaults:) }
68
+ sti_resources.each do |entry|
69
+ case entry
70
+ in Hash
71
+ entry.each { |sub_resource_name, options| jsonapi_resources(sub_resource_name, defaults:, **options) }
72
+ else
73
+ jsonapi_resources(entry, defaults:)
74
+ end
75
+ end
69
76
  end
70
77
 
71
78
  def define_auto_sti_routes(resource, resource_name, defaults, namespace = nil)
@@ -4,13 +4,14 @@ module JSONAPI
4
4
  module Serialization
5
5
  module IncludesSerialization
6
6
  def serialize_included(includes, fields = {})
7
- return [] if includes.empty?
7
+ all_includes = normalize_include_paths(includes)
8
+ return [] if all_includes.empty?
8
9
 
9
10
  included_records = []
10
11
  processed = Set.new
11
12
 
12
- includes.each do |include_path|
13
- serialize_include_path(record, include_path, fields, included_records, processed)
13
+ all_includes.each do |include_path|
14
+ serialize_include_path(record, include_path, fields, included_records, processed, all_includes:)
14
15
  end
15
16
 
16
17
  included_records
@@ -18,7 +19,7 @@ module JSONAPI
18
19
 
19
20
  private
20
21
 
21
- def serialize_include_path(current_record, include_path, fields, included_records, processed)
22
+ def serialize_include_path(current_record, include_path, fields, included_records, processed, all_includes:)
22
23
  path_parts = include_path.split(".")
23
24
  association_name = path_parts.first.to_sym
24
25
 
@@ -26,8 +27,9 @@ module JSONAPI
26
27
 
27
28
  related_array = get_related_records(current_record, association_name)
28
29
  related_array.each do |related_record|
29
- serialize_and_process_record(related_record, fields, included_records, processed)
30
- serialize_nested_path(related_record, path_parts, fields, included_records, processed)
30
+ parent_context = self.class.active_storage_attachment?(association_name, current_record.class) ? { parent_record: current_record, association_name: } : {}
31
+ serialize_and_process_record(related_record, include_path, all_includes, fields, included_records, processed, **parent_context)
32
+ serialize_nested_path(related_record, path_parts, fields, included_records, processed, all_includes:)
31
33
  end
32
34
  end
33
35
 
@@ -54,7 +56,16 @@ module JSONAPI
54
56
  related_klass = association.klass
55
57
  return [] if related_klass.blank?
56
58
 
57
- build_scoped_relation(related_klass, association).to_a
59
+ scope = build_scoped_relation(related_klass, association)
60
+ association.loaded? ? scope_loaded_to_records_scope(association, scope) : scope.to_a
61
+ end
62
+
63
+ def scope_loaded_to_records_scope(association, scope)
64
+ loaded_array = association.target.respond_to?(:to_a) ? association.target.to_a : Array(association.target)
65
+ loaded_ids = loaded_array.filter_map(&:id)
66
+ return [] if loaded_ids.empty?
67
+
68
+ scope.where(id: loaded_ids).to_a
58
69
  end
59
70
 
60
71
  def build_scoped_relation(related_klass, association)
@@ -70,12 +81,13 @@ module JSONAPI
70
81
  [attachment.blob].compact
71
82
  end
72
83
 
73
- def serialize_and_process_record(related_record, fields, included_records, processed)
84
+ def serialize_and_process_record(related_record, include_path, all_includes, fields, included_records, processed, parent_record: nil, association_name: nil) # rubocop:disable Metrics/ParameterLists
74
85
  record_key = build_record_key(related_record)
75
86
  return if processed.include?(record_key)
76
87
 
77
- serializer = self.class.new(related_record)
78
- included_records << serializer.serialize_record(fields)
88
+ requested_relationships = include_paths_to_relationship_names(all_includes, include_path)
89
+ serializer = self.class.new(related_record, parent_record:, association_name:)
90
+ included_records << serializer.serialize_record(fields, requested_relationships: requested_relationships)
79
91
  processed.add(record_key)
80
92
  end
81
93
 
@@ -83,11 +95,39 @@ module JSONAPI
83
95
  "#{related_record.class.name}-#{related_record.id}"
84
96
  end
85
97
 
86
- def serialize_nested_path(related_record, path_parts, fields, included_records, processed)
98
+ def serialize_nested_path(related_record, path_parts, fields, included_records, processed, all_includes:)
87
99
  return unless path_parts.length > 1
88
100
 
89
101
  nested_path = path_parts[1..].join(".")
90
- serialize_include_path(related_record, nested_path, fields, included_records, processed)
102
+ serialize_include_path(related_record, nested_path, fields, included_records, processed,
103
+ all_includes: all_includes,)
104
+ end
105
+
106
+ def normalize_include_paths(include_param)
107
+ case include_param
108
+ when Array
109
+ include_param.flat_map { |s| s.to_s.split(",").map(&:strip) }.uniq
110
+ when String
111
+ include_param.split(",").map(&:strip)
112
+ else
113
+ []
114
+ end
115
+ end
116
+
117
+ def include_paths_to_relationship_names(include_paths, path_prefix)
118
+ return [] if include_paths.blank?
119
+
120
+ prefix = path_prefix.present? ? "#{path_prefix}." : ""
121
+ include_paths.filter_map do |p|
122
+ path = p.to_s
123
+ next unless path == path_prefix || path.start_with?(prefix)
124
+
125
+ segments = path.split(".")
126
+ prefix_segments = path_prefix.split(".")
127
+ next if segments.length <= prefix_segments.length
128
+
129
+ segments[prefix_segments.length].to_sym
130
+ end.uniq
91
131
  end
92
132
  end
93
133
  end
@@ -16,11 +16,23 @@ module JSONAPI
16
16
  end
17
17
 
18
18
  def add_active_storage_download_link(links)
19
- links[:download] = rails_blob_path || fallback_blob_path
19
+ links[:download] = variant_or_blob_path || fallback_blob_path
20
20
  rescue StandardError
21
21
  links[:download] = fallback_blob_path
22
22
  end
23
23
 
24
+ def variant_or_blob_path
25
+ return rails_blob_path unless parent_record && association_name && record.content_type&.start_with?("image/")
26
+
27
+ parent_definition = ResourceLoader.find_for_model(parent_record.class)
28
+ rel_def = RelationshipHelpers.find_relationship_definition(parent_definition, association_name)
29
+ variant_opts = rel_def&.dig(:options, :variant)
30
+ return rails_blob_path unless variant_opts.present?
31
+
32
+ representation = record.representation(**variant_opts.symbolize_keys)
33
+ Rails.application.routes.url_helpers.rails_representation_path(representation, only_path: true)
34
+ end
35
+
24
36
  def rails_blob_path
25
37
  Rails.application.routes.url_helpers.rails_blob_path(record, only_path: true)
26
38
  end
@@ -3,9 +3,13 @@
3
3
  module JSONAPI
4
4
  module Serialization
5
5
  module RelationshipsSerialization
6
- def serialize_relationships
6
+ def serialize_relationships(requested_relationship_names = nil)
7
7
  relationships = {}
8
8
  relationship_definitions = definition.relationship_definitions
9
+ if requested_relationship_names
10
+ requested_set = requested_relationship_names.map(&:to_sym)
11
+ relationship_definitions = relationship_definitions.select { |r| requested_set.include?(r[:name]) }
12
+ end
9
13
 
10
14
  relationship_definitions.each do |rel_def|
11
15
  serialize_relationship(rel_def, relationships)
@@ -33,28 +33,32 @@ module JSONAPI
33
33
  @jsonapi_object = nil
34
34
  end
35
35
 
36
- def initialize(record, definition: nil, base_definition: nil)
36
+ def initialize(record, definition: nil, base_definition: nil, parent_record: nil, association_name: nil)
37
37
  @record = record
38
38
  @definition = definition || ResourceLoader.find_for_model(record.class)
39
39
  @base_definition = base_definition
40
+ @parent_record = parent_record
41
+ @association_name = association_name
40
42
  @sti_subclass = nil
41
43
  end
42
44
 
43
45
  def to_hash(include: [], fields: {}, document_meta: nil)
46
+ include_paths = normalize_include_paths(include)
47
+ top_level_relationships = include_paths_to_relationship_names(include_paths, "")
44
48
  {
45
49
  jsonapi: jsonapi_object,
46
- data: serialize_record(fields),
47
- included: serialize_included(include, fields),
50
+ data: serialize_record(fields, requested_relationships: top_level_relationships),
51
+ included: serialize_included(include_paths, fields),
48
52
  meta: document_meta,
49
53
  }.compact
50
54
  end
51
55
 
52
- def serialize_record(fields = {})
56
+ def serialize_record(fields = {}, requested_relationships: nil)
53
57
  {
54
58
  type: record_type,
55
59
  id: record_id,
56
60
  attributes: serialize_attributes(fields),
57
- relationships: serialize_relationships,
61
+ relationships: serialize_relationships(requested_relationships),
58
62
  links: serialize_links,
59
63
  meta: record_meta,
60
64
  }.compact
@@ -62,7 +66,7 @@ module JSONAPI
62
66
 
63
67
  private
64
68
 
65
- attr_reader :record, :definition
69
+ attr_reader :record, :definition, :parent_record, :association_name
66
70
 
67
71
  def base_definition
68
72
  @base_definition ||= definition
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module JSONAPI
4
- VERSION = "1.3.1"
4
+ VERSION = "1.5.0"
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.3.1
4
+ version: 1.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emil Kampp
@@ -70,7 +70,6 @@ files:
70
70
  - bin/console
71
71
  - bin/setup
72
72
  - jpie.gemspec
73
- - kiln/app/resources/user_message_resource.rb
74
73
  - lib/jpie.rb
75
74
  - lib/json_api.rb
76
75
  - lib/json_api/active_storage/deserialization.rb
@@ -1,4 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class UserMessageResource < MessageResource
4
- end