simple_jsonapi 1.0.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/.gitignore +10 -0
- data/.rubocop.yml +131 -0
- data/CHANGELOG.md +2 -0
- data/Gemfile +5 -0
- data/Jenkinsfile +92 -0
- data/LICENSE.txt +22 -0
- data/README.md +532 -0
- data/Rakefile +10 -0
- data/lib/simple_jsonapi.rb +112 -0
- data/lib/simple_jsonapi/definition/attribute.rb +45 -0
- data/lib/simple_jsonapi/definition/base.rb +50 -0
- data/lib/simple_jsonapi/definition/concerns/has_links_object.rb +36 -0
- data/lib/simple_jsonapi/definition/concerns/has_meta_object.rb +36 -0
- data/lib/simple_jsonapi/definition/error.rb +70 -0
- data/lib/simple_jsonapi/definition/error_source.rb +29 -0
- data/lib/simple_jsonapi/definition/link.rb +27 -0
- data/lib/simple_jsonapi/definition/meta.rb +27 -0
- data/lib/simple_jsonapi/definition/relationship.rb +60 -0
- data/lib/simple_jsonapi/definition/resource.rb +104 -0
- data/lib/simple_jsonapi/error_serializer.rb +76 -0
- data/lib/simple_jsonapi/errors/bad_request.rb +11 -0
- data/lib/simple_jsonapi/errors/exception_serializer.rb +6 -0
- data/lib/simple_jsonapi/errors/wrapped_error.rb +35 -0
- data/lib/simple_jsonapi/errors/wrapped_error_serializer.rb +35 -0
- data/lib/simple_jsonapi/helpers/exceptions.rb +39 -0
- data/lib/simple_jsonapi/helpers/serializer_inferrer.rb +136 -0
- data/lib/simple_jsonapi/helpers/serializer_methods.rb +36 -0
- data/lib/simple_jsonapi/node/attributes.rb +51 -0
- data/lib/simple_jsonapi/node/base.rb +91 -0
- data/lib/simple_jsonapi/node/data/collection.rb +25 -0
- data/lib/simple_jsonapi/node/data/singular.rb +26 -0
- data/lib/simple_jsonapi/node/document/base.rb +62 -0
- data/lib/simple_jsonapi/node/document/collection.rb +17 -0
- data/lib/simple_jsonapi/node/document/errors.rb +17 -0
- data/lib/simple_jsonapi/node/document/singular.rb +17 -0
- data/lib/simple_jsonapi/node/error.rb +55 -0
- data/lib/simple_jsonapi/node/error_source.rb +40 -0
- data/lib/simple_jsonapi/node/errors.rb +28 -0
- data/lib/simple_jsonapi/node/included.rb +45 -0
- data/lib/simple_jsonapi/node/object_links.rb +40 -0
- data/lib/simple_jsonapi/node/object_meta.rb +40 -0
- data/lib/simple_jsonapi/node/relationship.rb +79 -0
- data/lib/simple_jsonapi/node/relationship_data/base.rb +53 -0
- data/lib/simple_jsonapi/node/relationship_data/collection.rb +32 -0
- data/lib/simple_jsonapi/node/relationship_data/singular.rb +33 -0
- data/lib/simple_jsonapi/node/relationships.rb +60 -0
- data/lib/simple_jsonapi/node/resource/base.rb +21 -0
- data/lib/simple_jsonapi/node/resource/full.rb +49 -0
- data/lib/simple_jsonapi/node/resource/linkage.rb +25 -0
- data/lib/simple_jsonapi/parameters/fields_spec.rb +45 -0
- data/lib/simple_jsonapi/parameters/include_spec.rb +57 -0
- data/lib/simple_jsonapi/parameters/sort_spec.rb +107 -0
- data/lib/simple_jsonapi/serializer.rb +89 -0
- data/lib/simple_jsonapi/version.rb +3 -0
- data/simple_jsonapi.gemspec +29 -0
- data/test/errors/bad_request_test.rb +34 -0
- data/test/errors/error_serializer_test.rb +229 -0
- data/test/errors/exception_serializer_test.rb +25 -0
- data/test/errors/wrapped_error_serializer_test.rb +91 -0
- data/test/errors/wrapped_error_test.rb +44 -0
- data/test/parameters/fields_spec_test.rb +56 -0
- data/test/parameters/include_spec_test.rb +58 -0
- data/test/parameters/sort_spec_test.rb +65 -0
- data/test/resources/attributes_test.rb +109 -0
- data/test/resources/extras_test.rb +70 -0
- data/test/resources/id_and_type_test.rb +76 -0
- data/test/resources/inclusion_test.rb +134 -0
- data/test/resources/links_test.rb +63 -0
- data/test/resources/meta_test.rb +49 -0
- data/test/resources/relationships_test.rb +262 -0
- data/test/resources/sorting_test.rb +79 -0
- data/test/resources/sparse_fieldset_test.rb +160 -0
- data/test/root_objects_test.rb +165 -0
- data/test/test_helper.rb +31 -0
- metadata +235 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class IncludeSpecTest < Minitest::Spec
|
4
|
+
describe SimpleJsonapi::Parameters::IncludeSpec do
|
5
|
+
describe "parsing" do
|
6
|
+
it "accepts a comma-delimited list" do
|
7
|
+
spec = SimpleJsonapi::Parameters::IncludeSpec.new("order,product")
|
8
|
+
assert_equal({ order: {}, product: {} }, spec.to_h)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "accepts a list of strings" do
|
12
|
+
spec = SimpleJsonapi::Parameters::IncludeSpec.new("order", "product")
|
13
|
+
assert_equal({ order: {}, product: {} }, spec.to_h)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "accepts an array of strings" do
|
17
|
+
spec = SimpleJsonapi::Parameters::IncludeSpec.new(["order", "product"])
|
18
|
+
assert_equal({ order: {}, product: {} }, spec.to_h)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "nested includes" do
|
23
|
+
let(:nested_spec) do
|
24
|
+
SimpleJsonapi::Parameters::IncludeSpec.new(
|
25
|
+
"rel_1",
|
26
|
+
"rel_1.rel_1_a",
|
27
|
+
"rel_1.rel_1_a.rel_1_a_i",
|
28
|
+
"rel_1.rel_1_a.rel_1_a_ii",
|
29
|
+
"rel_1.rel_1_b",
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "parses nested includes" do
|
34
|
+
expected_value = {
|
35
|
+
rel_1: {
|
36
|
+
rel_1_a: {
|
37
|
+
rel_1_a_i: {},
|
38
|
+
rel_1_a_ii: {},
|
39
|
+
},
|
40
|
+
rel_1_b: {},
|
41
|
+
},
|
42
|
+
}
|
43
|
+
assert_equal(expected_value, nested_spec.to_h)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "extracts a nested include spec" do
|
47
|
+
expected_value = {
|
48
|
+
rel_1_a: {
|
49
|
+
rel_1_a_i: {},
|
50
|
+
rel_1_a_ii: {},
|
51
|
+
},
|
52
|
+
rel_1_b: {},
|
53
|
+
}
|
54
|
+
assert_equal(expected_value, nested_spec[:rel_1].to_h)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class SortSpecTest < Minitest::Spec
|
4
|
+
describe SimpleJsonapi::Parameters::SortSpec do
|
5
|
+
it "interprets a field name as ascending" do
|
6
|
+
spec = SimpleJsonapi::Parameters::SortSpec.new("orders" => "customer_name")
|
7
|
+
|
8
|
+
assert_equal [:customer_name], spec[:orders].map(&:field)
|
9
|
+
assert_equal [:asc], spec[:orders].map(&:dir)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "interprets a leading hyphen as descending" do
|
13
|
+
spec = SimpleJsonapi::Parameters::SortSpec.new("orders" => "-customer_name")
|
14
|
+
|
15
|
+
assert_equal [:customer_name], spec[:orders].map(&:field)
|
16
|
+
assert_equal [:desc], spec[:orders].map(&:dir)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "accepts comma-delimited strings" do
|
20
|
+
spec = SimpleJsonapi::Parameters::SortSpec.new(
|
21
|
+
"orders" => "customer_name,-description",
|
22
|
+
"line_items" => "-order_id,product_id",
|
23
|
+
)
|
24
|
+
|
25
|
+
assert_equal [:customer_name, :description], spec[:orders].map(&:field)
|
26
|
+
assert_equal [:asc, :desc], spec[:orders].map(&:dir)
|
27
|
+
|
28
|
+
assert_equal [:order_id, :product_id], spec[:line_items].map(&:field)
|
29
|
+
assert_equal [:desc, :asc], spec[:line_items].map(&:dir)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "accepts arrays of strings" do
|
33
|
+
spec = SimpleJsonapi::Parameters::SortSpec.new(
|
34
|
+
"orders" => ["customer_name", "-description"],
|
35
|
+
"line_items" => ["-order_id", "product_id"],
|
36
|
+
)
|
37
|
+
|
38
|
+
assert_equal [:customer_name, :description], spec[:orders].map(&:field)
|
39
|
+
assert_equal [:asc, :desc], spec[:orders].map(&:dir)
|
40
|
+
|
41
|
+
assert_equal [:order_id, :product_id], spec[:line_items].map(&:field)
|
42
|
+
assert_equal [:desc, :asc], spec[:line_items].map(&:dir)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe SimpleJsonapi::Parameters::SortFieldSpec do
|
47
|
+
it "interprets a field name as ascending" do
|
48
|
+
field_spec = SimpleJsonapi::Parameters::SortFieldSpec.new("customer_name")
|
49
|
+
|
50
|
+
assert_equal :customer_name, field_spec.field
|
51
|
+
assert_equal :asc, field_spec.dir
|
52
|
+
assert_equal true, field_spec.asc?
|
53
|
+
assert_equal false, field_spec.desc?
|
54
|
+
end
|
55
|
+
|
56
|
+
it "interprets a leading hyphen as descending" do
|
57
|
+
field_spec = SimpleJsonapi::Parameters::SortFieldSpec.new("-customer_name")
|
58
|
+
|
59
|
+
assert_equal :customer_name, field_spec.field
|
60
|
+
assert_equal :desc, field_spec.dir
|
61
|
+
assert_equal false, field_spec.asc?
|
62
|
+
assert_equal true, field_spec.desc?
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class AttributesTest < Minitest::Spec
|
4
|
+
class Thing < TestModel
|
5
|
+
attr_accessor :name, :number, :timestamp
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "Resource attributes" do
|
9
|
+
describe "rendering" do
|
10
|
+
class BasicSerializer < SimpleJsonapi::Serializer
|
11
|
+
attribute :name
|
12
|
+
attribute(:number) { |res| res.number&.to_i }
|
13
|
+
attribute(:date) { |res| res.timestamp&.to_date&.iso8601 }
|
14
|
+
end
|
15
|
+
|
16
|
+
let(:thing) do
|
17
|
+
Thing.new(
|
18
|
+
id: 1,
|
19
|
+
name: "Thing 1",
|
20
|
+
number: 2.718,
|
21
|
+
timestamp: Time.new(2017, 7, 15, 12, 34, 56),
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:serialized) { SimpleJsonapi.render_resource(thing, serializer: BasicSerializer) }
|
26
|
+
let(:attributes) { serialized.dig(:data, :attributes) }
|
27
|
+
|
28
|
+
it "calls the matching method by default" do
|
29
|
+
assert_equal "Thing 1", attributes[:name]
|
30
|
+
end
|
31
|
+
|
32
|
+
it "executes a proc" do
|
33
|
+
assert_equal 2, attributes[:number]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "conditional rendering" do
|
38
|
+
class ConditionalSerializer < SimpleJsonapi::Serializer
|
39
|
+
attribute :name, if: ->(res) { res.name.starts_with?("A") }
|
40
|
+
attribute :number, unless: ->(res) { res.name.starts_with?("Z") }
|
41
|
+
attribute(:date) { |res| res.timestamp&.to_date&.iso8601 }
|
42
|
+
end
|
43
|
+
|
44
|
+
let(:thing_a) { Thing.new(id: 1, name: "Abigail", number: 123) }
|
45
|
+
let(:thing_z) { Thing.new(id: 2, name: "Zachary", number: 456) }
|
46
|
+
|
47
|
+
def attributes_for(thing)
|
48
|
+
SimpleJsonapi.render_resource(thing, serializer: ConditionalSerializer).dig(:data, :attributes)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "renders the attribute when the 'if' is truthy" do
|
52
|
+
assert_equal "Abigail", attributes_for(thing_a)[:name]
|
53
|
+
end
|
54
|
+
|
55
|
+
it "renders the attribute when the 'unless' is falsey" do
|
56
|
+
assert_equal 123, attributes_for(thing_a)[:number]
|
57
|
+
end
|
58
|
+
|
59
|
+
it "omits the attribute when the 'if' is falsey" do
|
60
|
+
refute_includes attributes_for(thing_z).keys, :name
|
61
|
+
end
|
62
|
+
|
63
|
+
it "omits the attribute when the 'unless' is truthy" do
|
64
|
+
refute_includes attributes_for(thing_z).keys, :number
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "validation" do
|
69
|
+
it "cannot be named id" do
|
70
|
+
assert_raises(ArgumentError) do
|
71
|
+
Class.new(SimpleJsonapi::Serializer) { attribute :id }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
it "cannot be named type" do
|
76
|
+
assert_raises(ArgumentError) do
|
77
|
+
Class.new(SimpleJsonapi::Serializer) { attribute :type }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "documentation" do
|
83
|
+
class DocSerializer < SimpleJsonapi::Serializer
|
84
|
+
attribute :name
|
85
|
+
attribute :number, type: :numeric, description: "a number"
|
86
|
+
attribute :colors, type: :string, array: true
|
87
|
+
end
|
88
|
+
|
89
|
+
it "builds an attribute without documentation" do
|
90
|
+
attr_defn = DocSerializer.definition.attribute_definitions[:name]
|
91
|
+
assert_nil attr_defn.data_type
|
92
|
+
assert_equal false, attr_defn.array
|
93
|
+
assert_nil attr_defn.description
|
94
|
+
end
|
95
|
+
|
96
|
+
it "stores the type and description" do
|
97
|
+
attr_defn = DocSerializer.definition.attribute_definitions[:number]
|
98
|
+
assert_equal :numeric, attr_defn.data_type
|
99
|
+
assert_equal "a number", attr_defn.description
|
100
|
+
end
|
101
|
+
|
102
|
+
it "stores the type and array-ness" do
|
103
|
+
attr_defn = DocSerializer.definition.attribute_definitions[:colors]
|
104
|
+
assert_equal :string, attr_defn.data_type
|
105
|
+
assert_equal true, attr_defn.array
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ExtrasTest < Minitest::Spec
|
4
|
+
class Order < TestModel
|
5
|
+
attr_accessor :order_number, :total_price, :customer
|
6
|
+
end
|
7
|
+
|
8
|
+
class Customer < TestModel
|
9
|
+
attr_accessor :first_name, :last_name
|
10
|
+
end
|
11
|
+
|
12
|
+
class OrderSerializer < SimpleJsonapi::Serializer
|
13
|
+
attribute(:order_number) { |_order| @override_order_number }
|
14
|
+
attribute(:total_price) { |_order| @override_total_price }
|
15
|
+
has_one :customer, if: ->(_order) { @has_a_customer }
|
16
|
+
end
|
17
|
+
|
18
|
+
class CustomerSerializer < SimpleJsonapi::Serializer
|
19
|
+
attribute(:first_name) { |_customer| @override_first_name }
|
20
|
+
attribute(:last_name) { |_customer| @override_last_name }
|
21
|
+
end
|
22
|
+
|
23
|
+
let(:order) { Order.new(id: 1, order_number: "123", total_price: "5.99", customer: customer) }
|
24
|
+
let(:customer) { Customer.new(id: 2, first_name: "Andrei", last_name: "Romanov") }
|
25
|
+
|
26
|
+
describe "Extra contextual data" do
|
27
|
+
describe "on the root resource" do
|
28
|
+
it "runs when no extras are provided" do
|
29
|
+
serialized = SimpleJsonapi.render_resource(order)
|
30
|
+
assert_nil serialized.dig(:data, :attributes, :order_number)
|
31
|
+
end
|
32
|
+
|
33
|
+
it "makes the extras hash available to the attributes" do
|
34
|
+
serialized = SimpleJsonapi.render_resource(
|
35
|
+
order,
|
36
|
+
extras: {
|
37
|
+
override_order_number: "999",
|
38
|
+
override_total_price: 1.00,
|
39
|
+
},
|
40
|
+
)
|
41
|
+
assert_equal "999", serialized.dig(:data, :attributes, :order_number)
|
42
|
+
assert_equal 1.00, serialized.dig(:data, :attributes, :total_price)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "makes the extras hash available to an if predicate" do
|
46
|
+
serialized = SimpleJsonapi.render_resource(order, extras: { has_a_customer: false })
|
47
|
+
assert_nil serialized.dig(:data, :relationships, :customer, :data)
|
48
|
+
|
49
|
+
serialized = SimpleJsonapi.render_resource(order, extras: { has_a_customer: true })
|
50
|
+
assert_equal "2", serialized.dig(:data, :relationships, :customer, :data, :id)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "on a related resource" do
|
55
|
+
it "makes the extras hash available to the attributes" do
|
56
|
+
serialized = SimpleJsonapi.render_resource(
|
57
|
+
order,
|
58
|
+
include: "customer",
|
59
|
+
extras: {
|
60
|
+
has_a_customer: true,
|
61
|
+
override_first_name: "Jane",
|
62
|
+
override_last_name: "Doe",
|
63
|
+
},
|
64
|
+
)
|
65
|
+
assert_equal "Jane", serialized.dig(:included, 0, :attributes, :first_name)
|
66
|
+
assert_equal "Doe", serialized.dig(:included, 0, :attributes, :last_name)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class IdAndTypeTest < Minitest::Spec
|
4
|
+
class Thing < TestModel
|
5
|
+
end
|
6
|
+
|
7
|
+
let(:thing) { Thing.new(id: 4) }
|
8
|
+
|
9
|
+
describe "Resource id and type" do
|
10
|
+
describe "default implementation" do
|
11
|
+
class DefaultsSerializer < SimpleJsonapi::Serializer
|
12
|
+
end
|
13
|
+
|
14
|
+
it "calls the id method" do
|
15
|
+
serialized = SimpleJsonapi.render_resource(thing, serializer: DefaultsSerializer)
|
16
|
+
assert_equal "4", serialized.dig(:data, :id)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "uses the objects type" do
|
20
|
+
serialized = SimpleJsonapi.render_resource(thing, serializer: DefaultsSerializer)
|
21
|
+
assert_equal "things", serialized.dig(:data, :type)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "defined with blocks" do
|
26
|
+
class BlocksSerializer < SimpleJsonapi::Serializer
|
27
|
+
id { |thing| "#{thing.id}-the-thing" }
|
28
|
+
type { |thing| thing.class.name }
|
29
|
+
end
|
30
|
+
|
31
|
+
it "calls the block" do
|
32
|
+
serialized = SimpleJsonapi.render_resource(thing, serializer: BlocksSerializer)
|
33
|
+
assert_equal "4-the-thing", serialized.dig(:data, :id)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "calls the block" do
|
37
|
+
serialized = SimpleJsonapi.render_resource(thing, serializer: BlocksSerializer)
|
38
|
+
assert_equal "IdAndTypeTest::Thing", serialized.dig(:data, :type)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "defined with procs" do
|
43
|
+
class ProcsSerializer < SimpleJsonapi::Serializer
|
44
|
+
id ->(thing) { "#{thing.id}-the-thing" }
|
45
|
+
type ->(thing) { thing.class.name }
|
46
|
+
end
|
47
|
+
|
48
|
+
it "calls the proc" do
|
49
|
+
serialized = SimpleJsonapi.render_resource(thing, serializer: ProcsSerializer)
|
50
|
+
assert_equal "4-the-thing", serialized.dig(:data, :id)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "calls the proc" do
|
54
|
+
serialized = SimpleJsonapi.render_resource(thing, serializer: ProcsSerializer)
|
55
|
+
assert_equal "IdAndTypeTest::Thing", serialized.dig(:data, :type)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe "defined with constant values" do
|
60
|
+
class ValuesSerializer < SimpleJsonapi::Serializer
|
61
|
+
id 42
|
62
|
+
type "values"
|
63
|
+
end
|
64
|
+
|
65
|
+
it "renders the value" do
|
66
|
+
serialized = SimpleJsonapi.render_resource(thing, serializer: ValuesSerializer)
|
67
|
+
assert_equal "42", serialized.dig(:data, :id)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "renders the value" do
|
71
|
+
serialized = SimpleJsonapi.render_resource(thing, serializer: ValuesSerializer)
|
72
|
+
assert_equal "values", serialized.dig(:data, :type)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class InclusionTest < Minitest::Spec
|
4
|
+
class Order < TestModel
|
5
|
+
attr_writer :line_items
|
6
|
+
|
7
|
+
def line_items
|
8
|
+
@line_items ||= []
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class LineItem < TestModel
|
13
|
+
attr_accessor :product
|
14
|
+
end
|
15
|
+
|
16
|
+
class Product < TestModel
|
17
|
+
end
|
18
|
+
|
19
|
+
class OrderSerializer < SimpleJsonapi::Serializer
|
20
|
+
has_many :line_items do
|
21
|
+
data(&:line_items)
|
22
|
+
end
|
23
|
+
has_many :products do
|
24
|
+
data { |o| o.line_items.flat_map(&:product).compact.uniq }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class LineItemSerializer < SimpleJsonapi::Serializer
|
29
|
+
has_one :product do
|
30
|
+
data(&:product)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class ProductSerializer < SimpleJsonapi::Serializer
|
35
|
+
end
|
36
|
+
|
37
|
+
let(:order) { Order.new(id: 10, line_items: [line_item]) }
|
38
|
+
let(:line_item) { LineItem.new(id: 20, product: product) }
|
39
|
+
let(:product) { Product.new(id: 30) }
|
40
|
+
|
41
|
+
let(:included_line_item) do
|
42
|
+
serialized[:included]&.find { |res| res[:type] == "line_items" }
|
43
|
+
end
|
44
|
+
let(:included_product) do
|
45
|
+
serialized[:included]&.find { |res| res[:type] == "products" }
|
46
|
+
end
|
47
|
+
|
48
|
+
# def meta_for_resource(resource, relationship_name)
|
49
|
+
# resource.dig(:relationships, relationship_name, :data, :meta)
|
50
|
+
# end
|
51
|
+
|
52
|
+
describe "Including related resources" do
|
53
|
+
describe "without an include parameter" do
|
54
|
+
let(:serialized) { SimpleJsonapi.render_resource(order) }
|
55
|
+
|
56
|
+
it "doesn't include any related resources" do
|
57
|
+
refute serialized.key?(:included)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "adds meta information to the root resource" do
|
61
|
+
assert_equal(
|
62
|
+
{ included: false },
|
63
|
+
serialized.dig(:data, :relationships, :line_items, :data, 0, :meta) # has_many
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "with one level of inclusion" do
|
69
|
+
let(:serialized) do
|
70
|
+
SimpleJsonapi.render_resource(order, include: "line_items")
|
71
|
+
end
|
72
|
+
|
73
|
+
it "includes the related resources" do
|
74
|
+
refute_nil included_line_item
|
75
|
+
assert_nil included_product
|
76
|
+
end
|
77
|
+
|
78
|
+
it "adds meta information to the root resource" do
|
79
|
+
assert_equal(
|
80
|
+
{ included: true },
|
81
|
+
serialized.dig(:data, :relationships, :line_items, :data, 0, :meta) # has_many
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "adds meta information to the included resource" do
|
86
|
+
assert_equal(
|
87
|
+
{ included: false },
|
88
|
+
included_line_item.dig(:relationships, :product, :data, :meta) # has_one
|
89
|
+
)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
describe "with two levels of inclusion" do
|
94
|
+
let(:serialized) do
|
95
|
+
SimpleJsonapi.render_resource(order, include: "line_items,line_items.product")
|
96
|
+
end
|
97
|
+
|
98
|
+
it "includes the related resources" do
|
99
|
+
refute_nil included_line_item
|
100
|
+
refute_nil included_product
|
101
|
+
end
|
102
|
+
|
103
|
+
it "adds meta information to the base resource" do
|
104
|
+
assert_equal(
|
105
|
+
{ included: true },
|
106
|
+
serialized.dig(:data, :relationships, :line_items, :data, 0, :meta) # has_many
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "adds meta information to the included resource" do
|
111
|
+
assert_equal(
|
112
|
+
{ included: true },
|
113
|
+
included_line_item.dig(:relationships, :product, :data, :meta) # has_one
|
114
|
+
)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "duplicate resources" do
|
119
|
+
let(:serialized) do
|
120
|
+
SimpleJsonapi.render_resource(order, include: "line_items,line_items.product,products")
|
121
|
+
end
|
122
|
+
|
123
|
+
it "includes the related object only once" do
|
124
|
+
order_to_products_linkage = serialized.dig(:data, :relationships, :products, :data, 0) # has_many
|
125
|
+
line_item_to_product_linkage = included_line_item.dig(:relationships, :product, :data) # has_one
|
126
|
+
|
127
|
+
assert_equal product.id.to_s, order_to_products_linkage[:id]
|
128
|
+
assert_equal product.id.to_s, line_item_to_product_linkage[:id]
|
129
|
+
|
130
|
+
assert_equal(1, serialized[:included].count { |res| res[:type] == "products" })
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|