Skip to content

Commit 6243176

Browse files
committed
Rename PathPart to PathSegment
raises an error if segment specifies a type that the relationship does not support the resource type
1 parent dd9ab5e commit 6243176

File tree

7 files changed

+102
-81
lines changed

7 files changed

+102
-81
lines changed

lib/jsonapi-resources.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,4 @@
3131
require 'jsonapi/resource_id_tree'
3232
require 'jsonapi/resource_set'
3333
require 'jsonapi/path'
34-
require 'jsonapi/path_part'
34+
require 'jsonapi/path_segment'

lib/jsonapi/active_relation_resource_finder.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,7 @@ def find_related_polymorphic_fragments(source_rids, relationship, options, conne
508508
path_string: "#{relationship.name}#{linkage_relationship_path}",
509509
ensure_default_field: false)
510510

511-
linkage_relationship = path.parts[-1].relationship
511+
linkage_relationship = path.segments[-1].relationship
512512

513513
if linkage_relationship.polymorphic? && linkage_relationship.belongs_to?
514514
linkage_relationship.resource_types.each do |resource_type|
@@ -761,8 +761,8 @@ def apply_filters(records, filters, options = {})
761761
def get_aliased_field(path_with_field, joins)
762762
path = JSONAPI::Path.new(resource_klass: self, path_string: path_with_field)
763763

764-
relationship = path.parts[-2]
765-
field = path.parts[-1]
764+
relationship = path.segments[-2]
765+
field = path.segments[-1]
766766
relationship_path = path.relationship_path_string
767767

768768
if relationship

lib/jsonapi/active_relation_resource_finder/join_tree.rb

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def initialize(resource_klass:,
4040
def add_join(path, default_type = :inner, default_polymorphic_join_type = :left)
4141
if source_relationship
4242
if source_relationship.polymorphic?
43-
# Polymorphic paths will come it with the resource_type as the first part (for example `#documents.comments`)
43+
# Polymorphic paths will come it with the resource_type as the first segment (for example `#documents.comments`)
4444
# We just need to prepend the relationship portion the
4545
sourced_path = "#{source_relationship.name}#{path}"
4646
else
@@ -63,7 +63,7 @@ def add_join(path, default_type = :inner, default_polymorphic_join_type = :left)
6363
}
6464
end
6565

66-
def process_path_to_tree(path_parts, resource_klass, default_join_type, default_polymorphic_join_type)
66+
def process_path_to_tree(path_segments, resource_klass, default_join_type, default_polymorphic_join_type)
6767
node = {
6868
resource_klasses: {
6969
resource_klass => {
@@ -72,20 +72,20 @@ def process_path_to_tree(path_parts, resource_klass, default_join_type, default_
7272
}
7373
}
7474

75-
part = path_parts.shift
75+
segment = path_segments.shift
7676

77-
if part.is_a?(PathPart::Relationship)
78-
node[:resource_klasses][resource_klass][:relationships][part.relationship] ||= {}
77+
if segment.is_a?(PathSegment::Relationship)
78+
node[:resource_klasses][resource_klass][:relationships][segment.relationship] ||= {}
7979

8080
# join polymorphic as left joins
81-
node[:resource_klasses][resource_klass][:relationships][part.relationship][:join_type] ||=
82-
part.relationship.polymorphic? ? default_polymorphic_join_type : default_join_type
81+
node[:resource_klasses][resource_klass][:relationships][segment.relationship][:join_type] ||=
82+
segment.relationship.polymorphic? ? default_polymorphic_join_type : default_join_type
8383

84-
part.relationship.resource_types.each do |related_resource_type|
84+
segment.relationship.resource_types.each do |related_resource_type|
8585
related_resource_klass = resource_klass.resource_klass_for(related_resource_type)
86-
if !part.path_specified_resource_klass? || related_resource_klass == part.resource_klass
87-
related_resource_tree = process_path_to_tree(path_parts.dup, related_resource_klass, default_join_type, default_polymorphic_join_type)
88-
node[:resource_klasses][resource_klass][:relationships][part.relationship].deep_merge!(related_resource_tree)
86+
if !segment.path_specified_resource_klass? || related_resource_klass == segment.resource_klass
87+
related_resource_tree = process_path_to_tree(path_segments.dup, related_resource_klass, default_join_type, default_polymorphic_join_type)
88+
node[:resource_klasses][resource_klass][:relationships][segment.relationship].deep_merge!(related_resource_tree)
8989
end
9090
end
9191
end
@@ -94,8 +94,8 @@ def process_path_to_tree(path_parts, resource_klass, default_join_type, default_
9494

9595
def parse_path_to_tree(path_string, resource_klass, default_join_type = :inner, default_polymorphic_join_type = :left)
9696
path = JSONAPI::Path.new(resource_klass: resource_klass, path_string: path_string)
97-
field = path.parts[-1]
98-
return process_path_to_tree(path.parts, resource_klass, default_join_type, default_polymorphic_join_type), field
97+
field = path.segments[-1]
98+
return process_path_to_tree(path.segments, resource_klass, default_join_type, default_polymorphic_join_type), field
9999
end
100100

101101
def add_source_relationship(source_relationship)

lib/jsonapi/include_directives.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ def parse_include(include)
4141

4242
current = @include_directives_hash
4343

44-
path.parts.each do |part|
45-
relationship_name = part.relationship.name.to_sym
44+
path.segments.each do |segment|
45+
relationship_name = segment.relationship.name.to_sym
4646

4747
current[:include_related][relationship_name] ||= { include: true, include_related: {} }
4848
current = current[:include_related][relationship_name]

lib/jsonapi/path.rb

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,39 @@
11
module JSONAPI
22
class Path
3-
attr_reader :parts, :resource_klass
3+
attr_reader :segments, :resource_klass
44
def initialize(resource_klass:,
55
path_string:,
66
ensure_default_field: true,
77
parse_fields: true)
88
@resource_klass = resource_klass
99

1010
current_resource_klass = resource_klass
11-
@parts = path_string.to_s.split('.').collect do |part_string|
12-
part = PathPart.parse(source_resource_klass: current_resource_klass,
13-
part_string: part_string,
14-
parse_fields: parse_fields)
11+
@segments = path_string.to_s.split('.').collect do |segment_string|
12+
segment = PathSegment.parse(source_resource_klass: current_resource_klass,
13+
segment_string: segment_string,
14+
parse_fields: parse_fields)
1515

16-
current_resource_klass = part.resource_klass
17-
part
16+
current_resource_klass = segment.resource_klass
17+
segment
1818
end
1919

20-
if ensure_default_field && parse_fields && @parts.last.is_a?(PathPart::Relationship)
21-
last = @parts.last
22-
@parts << PathPart::Field.new(resource_klass: last.resource_klass,
23-
field_name: last.resource_klass._primary_key)
20+
if ensure_default_field && parse_fields && @segments.last.is_a?(PathSegment::Relationship)
21+
last = @segments.last
22+
@segments << PathSegment::Field.new(resource_klass: last.resource_klass,
23+
field_name: last.resource_klass._primary_key)
2424
end
2525
end
2626

27-
def relationship_parts
27+
def relationship_segments
2828
relationships = []
29-
@parts.each do |part|
30-
relationships << part if part.is_a?(PathPart::Relationship)
29+
@segments.each do |segment|
30+
relationships << segment if segment.is_a?(PathSegment::Relationship)
3131
end
3232
relationships
3333
end
3434

3535
def relationship_path_string
36-
relationship_parts.collect do |part|
37-
part.to_s
38-
end.join('.')
36+
relationship_segments.collect(&:to_s).join('.')
3937
end
4038
end
41-
end
39+
end

lib/jsonapi/path_part.rb renamed to lib/jsonapi/path_segment.rb

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
module JSONAPI
2-
class PathPart
3-
def self.parse(source_resource_klass:, part_string:, parse_fields: true)
4-
first_part, last_part = part_string.split('#', 2)
2+
class PathSegment
3+
def self.parse(source_resource_klass:, segment_string:, parse_fields: true)
4+
first_part, last_part = segment_string.split('#', 2)
55
relationship = source_resource_klass._relationship(first_part)
66

77
if relationship
88
if last_part
9+
unless relationship.resource_types.include?(last_part)
10+
raise JSONAPI::Exceptions::InvalidRelationship.new(source_resource_klass._type, segment_string)
11+
end
912
resource_klass = source_resource_klass.resource_klass_for(last_part)
10-
# ToDo: compare to relationship and raise error if not a match?
1113
end
12-
return PathPart::Relationship.new(relationship: relationship, resource_klass: resource_klass)
14+
return PathSegment::Relationship.new(relationship: relationship, resource_klass: resource_klass)
1315
else
1416
if last_part.blank? && parse_fields
15-
return PathPart::Field.new(resource_klass: source_resource_klass, field_name: first_part)
17+
return PathSegment::Field.new(resource_klass: source_resource_klass, field_name: first_part)
1618
else
17-
raise JSONAPI::Exceptions::InvalidRelationship.new(source_resource_klass._type, part_string)
19+
raise JSONAPI::Exceptions::InvalidRelationship.new(source_resource_klass._type, segment_string)
1820
end
1921
end
2022
end
@@ -44,10 +46,6 @@ class Field
4446
attr_reader :resource_klass, :field_name
4547

4648
def initialize(resource_klass:, field_name:)
47-
# ToDo: Should we enforce the resource has the field?
48-
# unless resource_klass._has_attribute?(field_name)
49-
# raise JSONAPI::Exceptions::InvalidField.new(resource_klass._type, field_name)
50-
# end
5149
@resource_klass = resource_klass
5250
@field_name = field_name
5351
end

test/unit/paths/path_test.rb

Lines changed: 58 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,40 +6,40 @@ class PathTest < ActiveSupport::TestCase
66
def test_one_relationship
77
path = JSONAPI::Path.new(resource_klass: Api::V1::PostResource, path_string: 'comments')
88

9-
assert path.parts.is_a?(Array)
10-
assert path.parts[0].is_a?(JSONAPI::PathPart::Relationship), "should be a PathPart::Relationship"
11-
assert_equal Api::V1::PostResource._relationship(:comments), path.parts[0].relationship
9+
assert path.segments.is_a?(Array)
10+
assert path.segments[0].is_a?(JSONAPI::PathSegment::Relationship), "should be a PathSegment::Relationship"
11+
assert_equal Api::V1::PostResource._relationship(:comments), path.segments[0].relationship
1212
end
1313

1414
def test_one_field
1515
path = JSONAPI::Path.new(resource_klass: Api::V1::PostResource, path_string: 'title')
1616

17-
assert path.parts.is_a?(Array)
18-
assert path.parts[0].is_a?(JSONAPI::PathPart::Field), "should be a PathPart::Field"
19-
assert_equal 'title', path.parts[0].field_name
17+
assert path.segments.is_a?(Array)
18+
assert path.segments[0].is_a?(JSONAPI::PathSegment::Field), "should be a PathSegment::Field"
19+
assert_equal 'title', path.segments[0].field_name
2020
end
2121

2222
def test_two_relationships
2323
path = JSONAPI::Path.new(resource_klass: Api::V1::PostResource, path_string: 'comments.author')
2424

25-
assert path.parts.is_a?(Array)
26-
assert path.parts[0].is_a?(JSONAPI::PathPart::Relationship), "should be a PathPart::Relationship"
27-
assert path.parts[1].is_a?(JSONAPI::PathPart::Relationship), "should be a PathPart::Relationship"
28-
assert_equal Api::V1::PostResource._relationship(:comments), path.parts[0].relationship
29-
assert_equal Api::V1::CommentResource._relationship(:author), path.parts[1].relationship
25+
assert path.segments.is_a?(Array)
26+
assert path.segments[0].is_a?(JSONAPI::PathSegment::Relationship), "should be a PathSegment::Relationship"
27+
assert path.segments[1].is_a?(JSONAPI::PathSegment::Relationship), "should be a PathSegment::Relationship"
28+
assert_equal Api::V1::PostResource._relationship(:comments), path.segments[0].relationship
29+
assert_equal Api::V1::CommentResource._relationship(:author), path.segments[1].relationship
3030
end
3131

3232
def test_two_relationships_and_field
3333
path = JSONAPI::Path.new(resource_klass: Api::V1::PostResource, path_string: 'comments.author.name')
3434

35-
assert path.parts.is_a?(Array)
36-
assert path.parts[0].is_a?(JSONAPI::PathPart::Relationship), "should be a PathPart::Relationship"
37-
assert path.parts[1].is_a?(JSONAPI::PathPart::Relationship), "should be a PathPart::Relationship"
38-
assert path.parts[2].is_a?(JSONAPI::PathPart::Field), "should be a PathPart::Field"
35+
assert path.segments.is_a?(Array)
36+
assert path.segments[0].is_a?(JSONAPI::PathSegment::Relationship), "should be a PathSegment::Relationship"
37+
assert path.segments[1].is_a?(JSONAPI::PathSegment::Relationship), "should be a PathSegment::Relationship"
38+
assert path.segments[2].is_a?(JSONAPI::PathSegment::Field), "should be a PathSegment::Field"
3939

40-
assert_equal Api::V1::PostResource._relationship(:comments), path.parts[0].relationship
41-
assert_equal Api::V1::CommentResource._relationship(:author), path.parts[1].relationship
42-
assert_equal 'name', path.parts[2].field_name
40+
assert_equal Api::V1::PostResource._relationship(:comments), path.segments[0].relationship
41+
assert_equal Api::V1::CommentResource._relationship(:author), path.segments[1].relationship
42+
assert_equal 'name', path.segments[2].field_name
4343
end
4444

4545
def test_two_relationships_and_parse_fields_false_raises_with_field
@@ -54,32 +54,57 @@ def test_two_relationships_and_parse_fields_false_raises_with_field
5454
def test_ensure_default_field_false
5555
path = JSONAPI::Path.new(resource_klass: Api::V1::PostResource, path_string: 'comments.author', ensure_default_field: false)
5656

57-
assert path.parts.is_a?(Array)
58-
assert_equal 2, path.parts.length
59-
assert path.parts[0].is_a?(JSONAPI::PathPart::Relationship), "should be a PathPart::Relationship"
60-
assert path.parts[1].is_a?(JSONAPI::PathPart::Relationship), "should be a PathPart::Relationship"
57+
assert path.segments.is_a?(Array)
58+
assert_equal 2, path.segments.length
59+
assert path.segments[0].is_a?(JSONAPI::PathSegment::Relationship), "should be a PathSegment::Relationship"
60+
assert path.segments[1].is_a?(JSONAPI::PathSegment::Relationship), "should be a PathSegment::Relationship"
6161

62-
assert_equal Api::V1::PostResource._relationship(:comments), path.parts[0].relationship
63-
assert_equal Api::V1::CommentResource._relationship(:author), path.parts[1].relationship
62+
assert_equal Api::V1::PostResource._relationship(:comments), path.segments[0].relationship
63+
assert_equal Api::V1::CommentResource._relationship(:author), path.segments[1].relationship
6464
end
6565

6666
def test_ensure_default_field_true
6767
path = JSONAPI::Path.new(resource_klass: Api::V1::PostResource, path_string: 'comments.author', ensure_default_field: true)
6868

69-
assert path.parts.is_a?(Array)
70-
assert_equal 3, path.parts.length
71-
assert path.parts[0].is_a?(JSONAPI::PathPart::Relationship), "should be a PathPart::Relationship"
72-
assert path.parts[1].is_a?(JSONAPI::PathPart::Relationship), "should be a PathPart::Relationship"
69+
assert path.segments.is_a?(Array)
70+
assert_equal 3, path.segments.length
71+
assert path.segments[0].is_a?(JSONAPI::PathSegment::Relationship), "should be a PathSegment::Relationship"
72+
assert path.segments[1].is_a?(JSONAPI::PathSegment::Relationship), "should be a PathSegment::Relationship"
7373

74-
assert_equal Api::V1::PostResource._relationship(:comments), path.parts[0].relationship
75-
assert_equal Api::V1::CommentResource._relationship(:author), path.parts[1].relationship
74+
assert_equal Api::V1::PostResource._relationship(:comments), path.segments[0].relationship
75+
assert_equal Api::V1::CommentResource._relationship(:author), path.segments[1].relationship
7676
end
7777

7878
def test_polymorphic_path
7979
path = JSONAPI::Path.new(resource_klass: PictureResource, path_string: :imageable)
8080

81-
assert path.parts.is_a?(Array)
82-
assert path.parts[0].is_a?(JSONAPI::PathPart::Relationship), "should be a PathPart::Relationship"
83-
assert_equal PictureResource._relationship(:imageable), path.parts[0].relationship
81+
assert path.segments.is_a?(Array)
82+
assert path.segments[0].is_a?(JSONAPI::PathSegment::Relationship), "should be a PathSegment::Relationship"
83+
assert_equal PictureResource._relationship(:imageable), path.segments[0].relationship
84+
refute path.segments[0].path_specified_resource_klass?, "should note that the resource klass was not specified"
85+
end
86+
87+
def test_polymorphic_path_with_resource_type
88+
path = JSONAPI::Path.new(resource_klass: PictureResource, path_string: 'imageable#documents')
89+
90+
assert path.segments.is_a?(Array)
91+
assert path.segments[0].is_a?(JSONAPI::PathSegment::Relationship), "should be a PathSegment::Relationship"
92+
assert_equal PictureResource._relationship(:imageable), path.segments[0].relationship
93+
assert_equal DocumentResource, path.segments[0].resource_klass, "should return the specified resource klass"
94+
assert path.segments[0].path_specified_resource_klass?, "should note that the resource klass was specified"
95+
end
96+
97+
def test_polymorphic_path_with_wrong_resource_type
98+
assert_raises JSONAPI::Exceptions::InvalidRelationship do
99+
JSONAPI::Path.new(resource_klass: PictureResource, path_string: 'imageable#docs')
100+
end
101+
end
102+
103+
def test_raises_when_field_is_specified_if_not_expected
104+
assert JSONAPI::Path.new(resource_klass: PictureResource, path_string: 'comments.author.name', parse_fields: true)
105+
106+
assert_raises JSONAPI::Exceptions::InvalidRelationship do
107+
JSONAPI::Path.new(resource_klass: PictureResource, path_string: 'comments.author.name', parse_fields: false)
108+
end
84109
end
85110
end

0 commit comments

Comments
 (0)