jsonapi-realizer 6.0.0.rc3 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
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