jsonapi-realizer 6.0.0.rc3 → 6.1.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 +4 -4
- data/LICENSE +440 -0
- data/README.md +15 -24
- data/Rakefile +1 -0
- data/lib/jsonapi/realizer/action.rb +80 -74
- data/lib/jsonapi/realizer/adapter/active_record.rb +11 -9
- data/lib/jsonapi/realizer/adapter.rb +7 -7
- data/lib/jsonapi/realizer/adapter_spec.rb +2 -4
- data/lib/jsonapi/realizer/configuration.rb +2 -0
- data/lib/jsonapi/realizer/context.rb +2 -0
- data/lib/jsonapi/realizer/controller.rb +22 -5
- data/lib/jsonapi/realizer/error/include_without_data_property.rb +2 -1
- data/lib/jsonapi/realizer/error/invalid_content_type_header.rb +2 -0
- data/lib/jsonapi/realizer/error/invalid_data_type_property.rb +2 -0
- data/lib/jsonapi/realizer/error/invalid_root_property.rb +2 -0
- data/lib/jsonapi/realizer/error/missing_content_type_header.rb +2 -1
- data/lib/jsonapi/realizer/error/missing_data_type_property.rb +2 -1
- data/lib/jsonapi/realizer/error/missing_root_property.rb +2 -1
- data/lib/jsonapi/realizer/error/resource_attribute_not_found.rb +2 -0
- data/lib/jsonapi/realizer/error/resource_relationship_not_found.rb +2 -0
- data/lib/jsonapi/realizer/error.rb +2 -0
- data/lib/jsonapi/realizer/resource/attribute.rb +2 -0
- data/lib/jsonapi/realizer/resource/configuration.rb +2 -0
- data/lib/jsonapi/realizer/resource/relation.rb +2 -0
- data/lib/jsonapi/realizer/resource.rb +181 -182
- data/lib/jsonapi/realizer/resource_spec.rb +50 -9
- data/lib/jsonapi/realizer/version.rb +3 -1
- data/lib/jsonapi/realizer.rb +7 -5
- data/lib/jsonapi/realizer_spec.rb +2 -28
- data/lib/jsonapi-realizer.rb +2 -0
- metadata +24 -204
- data/lib/jsonapi/realizer/version_spec.rb +0 -7
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module JSONAPI
|
2
4
|
module Realizer
|
3
5
|
class Action
|
@@ -18,21 +20,81 @@ module JSONAPI
|
|
18
20
|
raise Error::MissingAcceptHeader unless @headers.key?("Accept")
|
19
21
|
raise Error::InvalidAcceptHeader, given: @headers.fetch("Accept"), wanted: JSONAPI::MEDIA_TYPE unless @headers.fetch("Accept") == JSONAPI::MEDIA_TYPE
|
20
22
|
raise Error::IncludeWithoutDataProperty if @payload.key?("include") && !@payload.key?("data")
|
21
|
-
raise Error::MalformedDataRootProperty, given: data if @payload.key?("data") && !(data.
|
23
|
+
raise Error::MalformedDataRootProperty, given: data if @payload.key?("data") && !(data.is_a?(Array) || data.is_a?(Hash) || data.nil?)
|
22
24
|
end
|
23
25
|
|
24
26
|
def call; end
|
25
27
|
|
28
|
+
def relation
|
29
|
+
relation_after_fields(
|
30
|
+
relation_after_inclusion(
|
31
|
+
@scope || model_class
|
32
|
+
)
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
def includes
|
37
|
+
return [] if payload.blank?
|
38
|
+
return [] unless payload.key?("include")
|
39
|
+
|
40
|
+
payload
|
41
|
+
.fetch("include").
|
42
|
+
# "carts.cart-items,carts.cart-items.product,carts.billing-information,payments"
|
43
|
+
split(/\s*,\s*/).
|
44
|
+
# ["carts.cart-items", "carts.cart-items.product", "carts.billing-information", "payments"]
|
45
|
+
map { |path| path.tr("-", "_") }.
|
46
|
+
# ["carts.cart_items", "carts.cart_items.product", "carts.billing_information", "payments"]
|
47
|
+
map { |path| path.split(".") }.
|
48
|
+
# [["carts", "cart_items"], ["carts", "cart_items", "product"], ["carts", "billing_information"], ["payments"]]
|
49
|
+
select do |chain|
|
50
|
+
# ["carts", "cart_items"]
|
51
|
+
chain.reduce(resource_class) do |last_resource_class, key|
|
52
|
+
break unless last_resource_class
|
53
|
+
|
54
|
+
JSONAPI::Realizer::Resource.type_mapping.fetch(last_resource_class.relationship(key).as).resource_class if last_resource_class.valid_includes?(key)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
# [["carts", "cart_items", "product"], ["payments"]]
|
58
|
+
end
|
59
|
+
|
60
|
+
def fields
|
61
|
+
return [] if payload.blank?
|
62
|
+
return [] unless payload.key?("fields")
|
63
|
+
|
64
|
+
payload
|
65
|
+
.fetch("fields").
|
66
|
+
# "title,active-photographer.email,active-photographer.posts.title"
|
67
|
+
split(/\s*,\s*/).
|
68
|
+
# ["title", "active-photographer.email", "active-photographer.posts.title"]
|
69
|
+
map { |path| path.tr("-", "_") }.
|
70
|
+
# ["title", "active_photographer.email", "active_photographer.posts.title"]
|
71
|
+
map { |path| path.split(".") }.
|
72
|
+
# [["title"], ["active_photographer", "email"], ["active_photographer", "posts", "title"]]
|
73
|
+
select do |chain|
|
74
|
+
# ["active_photographer", "email"]
|
75
|
+
chain.reduce(resource_class) do |last_resource_class, key|
|
76
|
+
break unless last_resource_class
|
77
|
+
|
78
|
+
if last_resource_class.valid_includes?(key)
|
79
|
+
JSONAPI::Realizer::Resource.type_mapping.fetch(last_resource_class.relationship(key).as).resource_class
|
80
|
+
elsif last_resource_class.valid_sparse_field?(key)
|
81
|
+
last_resource_class
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
# [["title"], ["active_photographer", "email"]]
|
86
|
+
end
|
87
|
+
|
26
88
|
private def model_class
|
27
|
-
resource_class
|
89
|
+
resource_class&.model_class
|
28
90
|
end
|
29
91
|
|
30
92
|
private def resource_class
|
31
|
-
configuration
|
93
|
+
configuration&.resource_class
|
32
94
|
end
|
33
95
|
|
34
96
|
private def adapter
|
35
|
-
configuration
|
97
|
+
configuration&.adapter
|
36
98
|
end
|
37
99
|
|
38
100
|
private def relation_after_inclusion(subrelation)
|
@@ -51,14 +113,6 @@ module JSONAPI
|
|
51
113
|
end
|
52
114
|
end
|
53
115
|
|
54
|
-
def relation
|
55
|
-
relation_after_fields(
|
56
|
-
relation_after_inclusion(
|
57
|
-
@scope || model_class
|
58
|
-
)
|
59
|
-
)
|
60
|
-
end
|
61
|
-
|
62
116
|
private def data
|
63
117
|
payload.fetch("data", nil)
|
64
118
|
end
|
@@ -68,22 +122,26 @@ module JSONAPI
|
|
68
122
|
end
|
69
123
|
|
70
124
|
private def attributes
|
71
|
-
data
|
72
|
-
|
73
|
-
|
74
|
-
|
125
|
+
return unless data
|
126
|
+
|
127
|
+
data
|
128
|
+
.fetch("attributes", {})
|
129
|
+
.transform_keys(&:underscore)
|
130
|
+
.select(&resource_class.method(:valid_attribute?))
|
75
131
|
end
|
76
132
|
|
77
133
|
private def relationships
|
78
|
-
data
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
134
|
+
return unless data
|
135
|
+
|
136
|
+
data
|
137
|
+
.fetch("relationships", {})
|
138
|
+
.transform_keys(&:underscore)
|
139
|
+
.select(&resource_class.method(:valid_relationship?))
|
140
|
+
.transform_values(&method(:as_relationship))
|
83
141
|
end
|
84
142
|
|
85
143
|
private def as_relationship(value)
|
86
|
-
if value.
|
144
|
+
if value.is_a?(Array)
|
87
145
|
value.map do |member|
|
88
146
|
data = member.fetch("data")
|
89
147
|
mapping = JSONAPI::Realizer.type_mapping.fetch(data.fetch("type"))
|
@@ -102,58 +160,6 @@ module JSONAPI
|
|
102
160
|
end
|
103
161
|
end
|
104
162
|
|
105
|
-
def includes
|
106
|
-
return [] unless payload.present?
|
107
|
-
return [] unless payload.key?("include")
|
108
|
-
|
109
|
-
payload.
|
110
|
-
fetch("include").
|
111
|
-
# "carts.cart-items,carts.cart-items.product,carts.billing-information,payments"
|
112
|
-
split(/\s*,\s*/).
|
113
|
-
# ["carts.cart-items", "carts.cart-items.product", "carts.billing-information", "payments"]
|
114
|
-
map { |path| path.gsub("-", "_") }.
|
115
|
-
# ["carts.cart_items", "carts.cart_items.product", "carts.billing_information", "payments"]
|
116
|
-
map { |path| path.split(".") }.
|
117
|
-
# [["carts", "cart_items"], ["carts", "cart_items", "product"], ["carts", "billing_information"], ["payments"]]
|
118
|
-
select do |chain|
|
119
|
-
# ["carts", "cart_items"]
|
120
|
-
chain.reduce(resource_class) do |last_resource_class, key|
|
121
|
-
break unless last_resource_class
|
122
|
-
|
123
|
-
JSONAPI::Realizer::Resource.type_mapping.fetch(last_resource_class.relationship(key).as).resource_class if last_resource_class.valid_includes?(key)
|
124
|
-
end
|
125
|
-
end
|
126
|
-
# [["carts", "cart_items", "product"], ["payments"]]
|
127
|
-
end
|
128
|
-
|
129
|
-
def fields
|
130
|
-
return [] unless payload.present?
|
131
|
-
return [] unless payload.key?("fields")
|
132
|
-
|
133
|
-
payload.
|
134
|
-
fetch("fields").
|
135
|
-
# "title,active-photographer.email,active-photographer.posts.title"
|
136
|
-
split(/\s*,\s*/).
|
137
|
-
# ["title", "active-photographer.email", "active-photographer.posts.title"]
|
138
|
-
map { |path| path.gsub("-", "_") }.
|
139
|
-
# ["title", "active_photographer.email", "active_photographer.posts.title"]
|
140
|
-
map { |path| path.split(".") }.
|
141
|
-
# [["title"], ["active_photographer", "email"], ["active_photographer", "posts", "title"]]
|
142
|
-
select do |chain|
|
143
|
-
# ["active_photographer", "email"]
|
144
|
-
chain.reduce(resource_class) do |last_resource_class, key|
|
145
|
-
break unless last_resource_class
|
146
|
-
|
147
|
-
if last_resource_class.valid_includes?(key)
|
148
|
-
JSONAPI::Realizer::Resource.type_mapping.fetch(last_resource_class.relationship(key).as).resource_class
|
149
|
-
elsif last_resource_class.valid_sparse_field?(key)
|
150
|
-
last_resource_class
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|
154
|
-
# [["title"], ["active_photographer", "email"]]
|
155
|
-
end
|
156
|
-
|
157
163
|
private def configuration
|
158
164
|
JSONAPI::Realizer::Resource.type_mapping.fetch(type) if type
|
159
165
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module JSONAPI
|
2
4
|
module Realizer
|
3
5
|
class Adapter
|
@@ -16,14 +18,14 @@ module JSONAPI
|
|
16
18
|
|
17
19
|
def sorting(scope, sorts)
|
18
20
|
scope.order(
|
19
|
-
*sorts
|
20
|
-
map do |(keychain, direction)|
|
21
|
-
[keychain,
|
22
|
-
end
|
23
|
-
map do |(keychain, direction)|
|
24
|
-
[keychain.map
|
25
|
-
end
|
26
|
-
map do |pair|
|
21
|
+
*sorts
|
22
|
+
.map do |(keychain, direction)|
|
23
|
+
[keychain, direction == "-" ? :DESC : :ASC]
|
24
|
+
end
|
25
|
+
.map do |(keychain, direction)|
|
26
|
+
[keychain.map(&:inspect).join("."), direction]
|
27
|
+
end
|
28
|
+
.map do |pair|
|
27
29
|
Arel.sql(pair.join(" "))
|
28
30
|
end
|
29
31
|
)
|
@@ -49,7 +51,7 @@ module JSONAPI
|
|
49
51
|
if chains.size == 1
|
50
52
|
chains.first
|
51
53
|
else
|
52
|
-
{chains.first => arel_chain(chains.drop(1))}
|
54
|
+
{ chains.first => arel_chain(chains.drop(1)) }
|
53
55
|
end
|
54
56
|
end
|
55
57
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module JSONAPI
|
2
4
|
module Realizer
|
3
5
|
class Adapter
|
@@ -6,8 +8,8 @@ module JSONAPI
|
|
6
8
|
require_relative("adapter/active_record")
|
7
9
|
|
8
10
|
MAPPINGS = {
|
9
|
-
:
|
10
|
-
}
|
11
|
+
active_record: JSONAPI::Realizer::Adapter::ActiveRecord
|
12
|
+
}.freeze
|
11
13
|
private_constant :MAPPINGS
|
12
14
|
|
13
15
|
attr_accessor :interface
|
@@ -15,17 +17,15 @@ module JSONAPI
|
|
15
17
|
validates_presence_of(:interface)
|
16
18
|
|
17
19
|
def initialize(interface:)
|
18
|
-
super(interface:
|
20
|
+
super(interface:)
|
19
21
|
|
20
22
|
validate!
|
21
23
|
|
22
24
|
mappings = MAPPINGS.merge(JSONAPI::Realizer.configuration.adapter_mappings).with_indifferent_access
|
23
25
|
|
24
|
-
unless mappings.key?(interface)
|
25
|
-
raise(ArgumentError, "you've given an invalid adapter alias: #{interface}, we support #{mappings.keys.to_sentence}")
|
26
|
-
end
|
26
|
+
raise(ArgumentError, "you've given an invalid adapter alias: #{interface}, we support #{mappings.keys.to_sentence}") unless mappings.key?(interface)
|
27
27
|
|
28
|
-
|
28
|
+
singleton_class.prepend(mappings.fetch(interface))
|
29
29
|
|
30
30
|
raise(ArgumentError, "need to provide a Adapter#find_one interface") unless respond_to?(:find_one)
|
31
31
|
raise(ArgumentError, "need to provide a Adapter#find_many interface") unless respond_to?(:find_many)
|
@@ -1,9 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module JSONAPI
|
2
4
|
module Realizer
|
3
5
|
module Controller
|
6
|
+
private def reject_missing_accept_type_header
|
7
|
+
return if request.body.empty?
|
8
|
+
return if request.headers.key?("HTTP_ACCEPT")
|
9
|
+
|
10
|
+
raise(JSONAPI::Realizer.configuration.default_missing_accept_type_exception)
|
11
|
+
end
|
12
|
+
|
13
|
+
private def reject_invalid_accept_type_header
|
14
|
+
reject_missing_accept_type_header
|
15
|
+
|
16
|
+
return if request.headers.fetch("HTTP_ACCEPT").include?(JSONAPI::MEDIA_TYPE)
|
17
|
+
|
18
|
+
raise(JSONAPI::Realizer.configuration.default_invalid_accept_type_exception)
|
19
|
+
end
|
20
|
+
|
4
21
|
private def reject_missing_content_type_header
|
5
|
-
return if request.body.
|
6
|
-
return if request.headers.
|
22
|
+
return if request.body.empty?
|
23
|
+
return if request.headers.key?("Content-Type")
|
7
24
|
|
8
25
|
raise(JSONAPI::Realizer.configuration.default_missing_content_type_exception)
|
9
26
|
end
|
@@ -18,8 +35,8 @@ module JSONAPI
|
|
18
35
|
|
19
36
|
private def reject_missing_root_property
|
20
37
|
return if request.parameters.key?("body")
|
21
|
-
return if request.
|
22
|
-
return if request.
|
38
|
+
return if request.parameters.key?("errors")
|
39
|
+
return if request.parameters.key?("meta")
|
23
40
|
|
24
41
|
raise(Error::MissingRootProperty)
|
25
42
|
end
|
@@ -46,7 +63,7 @@ module JSONAPI
|
|
46
63
|
reject_missing_type_property
|
47
64
|
|
48
65
|
return if request.parameters.fetch("data").is_a?(Hash) && request.parameters.fetch("data").fetch("type").is_a?(String) && request.parameters.fetch("data").fetch("type").present?
|
49
|
-
return if request.parameters.fetch("data").is_a?(Array) && request.parameters.fetch("data").map {|data| data.fetch("type")}.all? {|type| type.is_a?(String) && type.present? }
|
66
|
+
return if request.parameters.fetch("data").is_a?(Array) && request.parameters.fetch("data").map { |data| data.fetch("type") }.all? { |type| type.is_a?(String) && type.present? }
|
50
67
|
|
51
68
|
raise(Error::InvalidDataTypeProperty)
|
52
69
|
end
|