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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +440 -0
  3. data/README.md +15 -24
  4. data/Rakefile +1 -0
  5. data/lib/jsonapi/realizer/action.rb +80 -74
  6. data/lib/jsonapi/realizer/adapter/active_record.rb +11 -9
  7. data/lib/jsonapi/realizer/adapter.rb +7 -7
  8. data/lib/jsonapi/realizer/adapter_spec.rb +2 -4
  9. data/lib/jsonapi/realizer/configuration.rb +2 -0
  10. data/lib/jsonapi/realizer/context.rb +2 -0
  11. data/lib/jsonapi/realizer/controller.rb +22 -5
  12. data/lib/jsonapi/realizer/error/include_without_data_property.rb +2 -1
  13. data/lib/jsonapi/realizer/error/invalid_content_type_header.rb +2 -0
  14. data/lib/jsonapi/realizer/error/invalid_data_type_property.rb +2 -0
  15. data/lib/jsonapi/realizer/error/invalid_root_property.rb +2 -0
  16. data/lib/jsonapi/realizer/error/missing_content_type_header.rb +2 -1
  17. data/lib/jsonapi/realizer/error/missing_data_type_property.rb +2 -1
  18. data/lib/jsonapi/realizer/error/missing_root_property.rb +2 -1
  19. data/lib/jsonapi/realizer/error/resource_attribute_not_found.rb +2 -0
  20. data/lib/jsonapi/realizer/error/resource_relationship_not_found.rb +2 -0
  21. data/lib/jsonapi/realizer/error.rb +2 -0
  22. data/lib/jsonapi/realizer/resource/attribute.rb +2 -0
  23. data/lib/jsonapi/realizer/resource/configuration.rb +2 -0
  24. data/lib/jsonapi/realizer/resource/relation.rb +2 -0
  25. data/lib/jsonapi/realizer/resource.rb +181 -182
  26. data/lib/jsonapi/realizer/resource_spec.rb +50 -9
  27. data/lib/jsonapi/realizer/version.rb +3 -1
  28. data/lib/jsonapi/realizer.rb +7 -5
  29. data/lib/jsonapi/realizer_spec.rb +2 -28
  30. data/lib/jsonapi-realizer.rb +2 -0
  31. metadata +24 -204
  32. 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.kind_of?(Array) || data.kind_of?(Hash) || data.nil?)
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.model_class if resource_class
89
+ resource_class&.model_class
28
90
  end
29
91
 
30
92
  private def resource_class
31
- configuration.resource_class if configuration
93
+ configuration&.resource_class
32
94
  end
33
95
 
34
96
  private def adapter
35
- configuration.adapter if 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
- fetch("attributes", {}).
73
- transform_keys(&:underscore).
74
- select(&resource_class.method(:valid_attribute?)) if data
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
- fetch("relationships", {}).
80
- transform_keys(&:underscore).
81
- select(&resource_class.method(:valid_relationship?)).
82
- transform_values(&method(:as_relationship)) if data
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.kind_of?(Array)
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, if direction == "-" then :DESC else :ASC end]
22
- end.
23
- map do |(keychain, direction)|
24
- [keychain.map {|key| key.inspect}.join("."), direction]
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
- :active_record => JSONAPI::Realizer::Adapter::ActiveRecord
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: 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
- self.singleton_class.prepend(mappings.fetch(interface))
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,5 +1,3 @@
1
- require("spec_helper")
2
-
3
- RSpec.describe(JSONAPI::Realizer::Adapter) do
1
+ # frozen_string_literal: true
4
2
 
5
- end
3
+ require("spec_helper")
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Realizer
3
5
  class Configuration
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Realizer
3
5
  module Context
@@ -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.size.zero?
6
- return if request.headers.property?("Content-Type")
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.paremters.key?("errors")
22
- return if request.paremters.key?("meta")
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
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Realizer
3
5
  class Error
4
6
  class IncludeWithoutDataProperty < Error
5
-
6
7
  end
7
8
  end
8
9
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Realizer
3
5
  class Error
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Realizer
3
5
  class Error
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Realizer
3
5
  class Error
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Realizer
3
5
  class Error
4
6
  class MissingContentTypeHeader < Error
5
-
6
7
  end
7
8
  end
8
9
  end
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Realizer
3
5
  class Error
4
6
  class MissingDataTypeProperty < Error
5
-
6
7
  end
7
8
  end
8
9
  end
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Realizer
3
5
  class Error
4
6
  class MissingRootProperty < Error
5
-
6
7
  end
7
8
  end
8
9
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Realizer
3
5
  class Error
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Realizer
3
5
  class Error
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Realizer
3
5
  class Error < StandardError
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Realizer
3
5
  module Resource
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Realizer
3
5
  module Resource
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module JSONAPI
2
4
  module Realizer
3
5
  module Resource