graphiti 1.2.34 → 1.2.39

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3c877600c2b854433fd506185d43e64fce4e9f6c3a7cd37846fe60f724acd8f4
4
- data.tar.gz: d1e30c54a384eda5a2b2f5224da346808c47a54aadf57610b1ccd7339d780234
3
+ metadata.gz: 6d2917a607d95ef6d4701b8960ed183fd39923662b034d74fda9f8080f65d534
4
+ data.tar.gz: 7fc8ec33363e0112c541d05c676c7315b1934876f15875ac0b153bf2fc6cebdd
5
5
  SHA512:
6
- metadata.gz: 68b7f4d1d96c67d6d28976ed9ba8071e5d8376835cfbc61a2744e9792467e93dee4b4338fbeb4d7d3492f8a03aa935657726c0b66d0af69f4d061b5cdc29f1b7
7
- data.tar.gz: 62a8b811497bfc1ffc06e4bb85f86ac573be0f7eb7b0a41cf915f6c0ee9fdf85460612c17708956c89472a6b7dad0d43c0087212db2779e0320211d9f99a93c9
6
+ metadata.gz: 6f9867cbf49483cb09a887db26af222e2d74962c07aaf6ece7552a73cf74b625ca49e19ed730c1f383f7706ae10546551d54792b2b09832709aa14829ddd8180
7
+ data.tar.gz: 746d7900a12437a7a610dc8d4e8737ada319867578349898cc95067d7295c1b04b659dec00e33aec0a9977c33ecafd0a58f9f07da61ec04def6ad8131656351f
@@ -74,19 +74,22 @@ module Graphiti
74
74
  name_chain << k unless name_chain.last == k
75
75
 
76
76
  unless remote_resource? && serializers.nil?
77
- attrs[name.to_sym] = if serializers.is_a?(Array)
78
- serializers.map do |rr|
77
+ payload = if serializers.is_a?(Array)
78
+ data = serializers.map { |rr|
79
79
  rr.to_hash(fields: fields, include: nested_include, graphql: graphql, name_chain: name_chain)
80
- end
80
+ }
81
+ graphql ? {nodes: data} : data
81
82
  elsif serializers.nil?
82
83
  if @resource.class.respond_to?(:sideload)
83
84
  if @resource.class.sideload(k).type.to_s.include?("_many")
84
- []
85
+ graphql ? {nodes: []} : []
85
86
  end
86
87
  end
87
88
  else
88
89
  serializers.to_hash(fields: fields, include: nested_include, graphql: graphql, name_chain: name_chain)
89
90
  end
91
+
92
+ attrs[name.to_sym] = payload
90
93
  end
91
94
  end
92
95
 
@@ -133,29 +136,58 @@ module Graphiti
133
136
  serializers = options[:data]
134
137
  opts = options.slice(:fields, :include)
135
138
  opts[:graphql] = @graphql
136
- to_hash(serializers, opts).tap do |hash|
137
- hash.merge!(options.slice(:meta)) unless options[:meta].empty?
138
- end
139
+ top_level_key = get_top_level_key(@resource, serializers.is_a?(Array))
140
+
141
+ hash = {top_level_key => {}}
142
+ nodes = get_nodes(serializers, opts)
143
+ add_nodes(hash, top_level_key, options, nodes, @graphql)
144
+ add_stats(hash, top_level_key, options, @graphql)
145
+ hash
139
146
  end
140
147
 
141
148
  private
142
149
 
143
- def to_hash(serializers, opts)
144
- {}.tap do |hash|
145
- top_level_key = :data
146
- if @graphql
147
- top_level_key = @resource.graphql_entrypoint
148
- unless serializers.is_a?(Array)
149
- top_level_key = top_level_key.to_s.singularize.to_sym
150
- end
150
+ def get_top_level_key(resource, is_many)
151
+ key = :data
152
+
153
+ if @graphql
154
+ key = @resource.graphql_entrypoint
155
+ key = key.to_s.singularize.to_sym unless is_many
156
+ end
157
+
158
+ key
159
+ end
160
+
161
+ def get_nodes(serializers, opts)
162
+ if serializers.is_a?(Array)
163
+ serializers.map do |s|
164
+ s.to_hash(**opts)
151
165
  end
166
+ else
167
+ serializers.to_hash(**opts)
168
+ end
169
+ end
170
+
171
+ def add_nodes(hash, top_level_key, opts, nodes, graphql)
172
+ payload = nodes
173
+ if graphql && nodes.is_a?(Array)
174
+ payload = {nodes: nodes}
175
+ end
152
176
 
153
- hash[top_level_key] = if serializers.is_a?(Array)
154
- serializers.map do |s|
155
- s.to_hash(**opts)
177
+ # Don't render nodes if we only requested stats
178
+ unless graphql && opts[:fields].values == [[:stats]]
179
+ hash[top_level_key] = payload
180
+ end
181
+ end
182
+
183
+ def add_stats(hash, top_level_key, options, graphql)
184
+ if options[:meta] && !options[:meta].empty?
185
+ if @graphql
186
+ if (stats = options[:meta][:stats])
187
+ hash[top_level_key][:stats] = stats
156
188
  end
157
189
  else
158
- serializers.to_hash(**opts)
190
+ hash.merge!(options.slice(:meta))
159
191
  end
160
192
  end
161
193
  end
@@ -26,17 +26,17 @@ module Graphiti
26
26
  [:data, :type],
27
27
  [:data, :id]
28
28
  ].each do |required_attr|
29
- attribute_mismatch(required_attr) unless @raw_params.dig(*required_attr)
29
+ attribute_mismatch(required_attr) unless @params.dig(*required_attr)
30
30
  end
31
31
  errors.blank?
32
32
  end
33
33
 
34
34
  def payload_matches_endpoint?
35
- unless @raw_params.dig(:data, :id) == @raw_params.dig(:filter, :id)
35
+ unless @params.dig(:data, :id) == @params.dig(:filter, :id)
36
36
  attribute_mismatch([:data, :id])
37
37
  end
38
38
 
39
- meta_type = @raw_params.dig(:data, :type)
39
+ meta_type = @params.dig(:data, :type)
40
40
 
41
41
  # NOTE: calling #to_s and comparing 2 strings is slower than
42
42
  # calling #to_sym and comparing 2 symbols. But pre ruby-2.2
@@ -5,21 +5,31 @@ module Graphiti
5
5
 
6
6
  def initialize(root_resource, raw_params, action)
7
7
  @root_resource = root_resource
8
- @raw_params = raw_params
8
+ @params = normalized_params(raw_params)
9
9
  @errors = Graphiti::Util::SimpleErrors.new(raw_params)
10
10
  @action = action
11
11
  end
12
12
 
13
13
  def validate
14
+ # Right now, all requests - even reads - go through the validator
15
+ # In the future these should have their own validation logic, but
16
+ # for now we can just bypass
17
+ return true unless @params.has_key?(:data)
18
+
14
19
  resource = @root_resource
15
- if (meta_type = deserialized_payload.meta[:type].try(:to_sym))
16
- if @root_resource.type != meta_type && @root_resource.polymorphic?
17
- resource = @root_resource.class.resource_for_type(meta_type).new
20
+
21
+ if @params[:data].has_key?(:type)
22
+ if (meta_type = deserialized_payload.meta[:type].try(:to_sym))
23
+ if @root_resource.type != meta_type && @root_resource.polymorphic?
24
+ resource = @root_resource.class.resource_for_type(meta_type).new
25
+ end
18
26
  end
19
- end
20
27
 
21
- typecast_attributes(resource, deserialized_payload.attributes, deserialized_payload.meta[:payload_path])
22
- process_relationships(resource, deserialized_payload.relationships, deserialized_payload.meta[:payload_path])
28
+ typecast_attributes(resource, deserialized_payload.attributes, @action, deserialized_payload.meta[:payload_path])
29
+ process_relationships(resource, deserialized_payload.relationships, deserialized_payload.meta[:payload_path])
30
+ else
31
+ errors.add(:"data.type", :missing)
32
+ end
23
33
 
24
34
  errors.blank?
25
35
  end
@@ -33,14 +43,7 @@ module Graphiti
33
43
  end
34
44
 
35
45
  def deserialized_payload
36
- @deserialized_payload ||= begin
37
- payload = normalized_params
38
- if payload[:data] && payload[:data][:type]
39
- Graphiti::Deserializer.new(payload)
40
- else
41
- Graphiti::Deserializer.new({})
42
- end
43
- end
46
+ @deserialized_payload ||= Graphiti::Deserializer.new(@params)
44
47
  end
45
48
 
46
49
  private
@@ -62,15 +65,20 @@ module Graphiti
62
65
  next
63
66
  end
64
67
 
65
- typecast_attributes(x[:resource], x[:attributes], x[:meta][:payload_path])
66
- process_relationships(x[:resource], x[:relationships], x[:meta][:payload_path])
68
+ resource = x[:resource]
69
+ attributes = x[:attributes]
70
+ relationships = x[:relationships]
71
+ payload_path = x[:meta][:payload_path]
72
+ action = x[:meta][:method]
73
+ typecast_attributes(resource, attributes, action, payload_path)
74
+ process_relationships(resource, relationships, payload_path)
67
75
  end
68
76
  end
69
77
 
70
- def typecast_attributes(resource, attributes, payload_path)
78
+ def typecast_attributes(resource, attributes, action, payload_path)
71
79
  attributes.each_pair do |key, value|
72
80
  # Only validate id if create action, otherwise it's only used for lookup
73
- next if @action != :create &&
81
+ next if action != :create &&
74
82
  key == :id &&
75
83
  resource.class.config[:attributes][:id][:writable] == false
76
84
 
@@ -86,8 +94,8 @@ module Graphiti
86
94
  end
87
95
  end
88
96
 
89
- def normalized_params
90
- normalized = @raw_params
97
+ def normalized_params(raw_params)
98
+ normalized = raw_params
91
99
  if normalized.respond_to?(:to_unsafe_h)
92
100
  normalized = normalized.to_unsafe_h.deep_symbolize_keys
93
101
  end
@@ -96,9 +96,14 @@ module Graphiti
96
96
  extra_attributes: extra_attributes(r),
97
97
  sorts: sorts(r),
98
98
  filters: filters(r),
99
- relationships: relationships(r)
99
+ relationships: relationships(r),
100
+ stats: stats(r)
100
101
  }
101
102
 
103
+ if r.grouped_filters.any?
104
+ config[:filter_group] = r.grouped_filters
105
+ end
106
+
102
107
  if r.default_sort
103
108
  default_sort = r.default_sort.map { |s|
104
109
  {s.keys.first.to_s => s.values.first.to_s}
@@ -165,6 +170,14 @@ module Graphiti
165
170
  end
166
171
  end
167
172
 
173
+ def stats(resource)
174
+ {}.tap do |stats|
175
+ resource.stats.each_pair do |name, config|
176
+ stats[name] = config.calculations.keys
177
+ end
178
+ end
179
+ end
180
+
168
181
  def sorts(resource)
169
182
  {}.tap do |s|
170
183
  resource.sorts.each_pair do |name, sort|
@@ -212,6 +225,10 @@ module Graphiti
212
225
  end
213
226
  end
214
227
 
228
+ def filter_group(resource)
229
+ resource.config[:grouped_filters]
230
+ end
231
+
215
232
  def relationships(resource)
216
233
  {}.tap do |r|
217
234
  resource.sideloads.each_pair do |name, config|
@@ -1,8 +1,8 @@
1
1
  module Graphiti
2
2
  class SchemaDiff
3
3
  def initialize(old, new)
4
- @old = old.deep_symbolize_keys
5
- @new = new.deep_symbolize_keys
4
+ @old = JSON.parse(old.to_json).deep_symbolize_keys
5
+ @new = JSON.parse(new.to_json).deep_symbolize_keys
6
6
  @errors = []
7
7
  end
8
8
 
@@ -30,6 +30,8 @@ module Graphiti
30
30
  compare_extra_attributes(r, new_resource)
31
31
  compare_sorts(r, new_resource)
32
32
  compare_filters(r, new_resource)
33
+ compare_filter_group(r, new_resource)
34
+ compare_stats(r, new_resource)
33
35
  compare_relationships(r, new_resource)
34
36
  end
35
37
  end
@@ -133,12 +135,12 @@ module Graphiti
133
135
  end
134
136
 
135
137
  if new_sort[:only] && !old_sort[:only]
136
- @errors << "#{old_resource[:name]}: sort #{name.inspect} now limited to only #{new_sort[:only].inspect}."
138
+ @errors << "#{old_resource[:name]}: sort #{name.inspect} now limited to only #{new_sort[:only].to_sym.inspect}."
137
139
  end
138
140
 
139
141
  if new_sort[:only] && old_sort[:only]
140
142
  if new_sort[:only] != old_sort[:only]
141
- @errors << "#{old_resource[:name]}: sort #{name.inspect} was limited to only #{old_sort[:only].inspect}, now limited to only #{new_sort[:only].inspect}."
143
+ @errors << "#{old_resource[:name]}: sort #{name.inspect} was limited to only #{old_sort[:only].to_sym.inspect}, now limited to only #{new_sort[:only].to_sym.inspect}."
142
144
  end
143
145
  end
144
146
  end
@@ -204,6 +206,44 @@ module Graphiti
204
206
  end
205
207
  end
206
208
 
209
+ def compare_filter_group(old_resource, new_resource)
210
+ if new_resource[:filter_group]
211
+ if old_resource[:filter_group]
212
+ new_names = new_resource[:filter_group][:names]
213
+ old_names = old_resource[:filter_group][:names]
214
+ diff = new_names - old_names
215
+ if !diff.empty? && new_resource[:filter_group][:required] == "all"
216
+ @errors << "#{old_resource[:name]}: all required filter group #{old_names.map(&:to_sym).inspect} added #{"member".pluralize(diff.length)} #{diff.map(&:to_sym).inspect}."
217
+ end
218
+
219
+ old_required = old_resource[:filter_group][:required]
220
+ new_required = new_resource[:filter_group][:required]
221
+ if old_required == "any" && new_required == "all"
222
+ @errors << "#{old_resource[:name]}: filter group #{old_names.map(&:to_sym).inspect} moved from required: :any to required: :all"
223
+ end
224
+ else
225
+ @errors << "#{old_resource[:name]}: filter group #{new_resource[:filter_group][:names].map(&:to_sym).inspect} was added."
226
+ end
227
+ end
228
+ end
229
+
230
+ def compare_stats(old_resource, new_resource)
231
+ return unless old_resource.key?(:stats)
232
+
233
+ old_resource[:stats].each_pair do |name, old_calculations|
234
+ new_calculations = new_resource[:stats][name]
235
+ if new_calculations
236
+ old_calculations.each do |calc|
237
+ unless new_calculations.include?(calc)
238
+ @errors << "#{old_resource[:name]}: calculation #{calc.to_sym.inspect} was removed from stat #{name.inspect}."
239
+ end
240
+ end
241
+ else
242
+ @errors << "#{old_resource[:name]}: stat #{name.inspect} was removed."
243
+ end
244
+ end
245
+ end
246
+
207
247
  def compare_endpoints
208
248
  @old[:endpoints].each_pair do |path, old_endpoint|
209
249
  unless (new_endpoint = @new[:endpoints][path])
@@ -3,10 +3,12 @@ module Graphiti
3
3
  include Scoping::Filterable
4
4
 
5
5
  def apply
6
- Graphiti::Scoping::FilterGroupValidator.new(
7
- resource,
8
- query_hash
9
- ).raise_unless_filter_group_requirements_met!
6
+ unless @opts[:bypass_required_filters]
7
+ Graphiti::Scoping::FilterGroupValidator.new(
8
+ resource,
9
+ query_hash
10
+ ).raise_unless_filter_group_requirements_met!
11
+ end
10
12
 
11
13
  if missing_required_filters.any? && !@opts[:bypass_required_filters]
12
14
  raise Errors::RequiredFilter.new(resource, missing_required_filters)
@@ -16,13 +16,11 @@ module Graphiti
16
16
 
17
17
  if @name == :id
18
18
  @serializer.id(&proc)
19
- elsif @attr[:proc]
19
+ elsif @attr[:proc] ||
20
+ !previously_applied? ||
21
+ previously_applied_via_resource?
20
22
  @serializer.send(_method, @name, serializer_options, &proc)
21
- elsif @serializer.attribute_blocks[@name].nil?
22
- @serializer.send(_method, @name, serializer_options, &proc)
23
- elsif @serializer.send(applied_method).include?(@name)
24
- @serializer.field_condition_blocks[@name] = guard if guard?
25
- else
23
+ else # Previously applied via explicit serializer, so wrap it
26
24
  inner = @serializer.attribute_blocks.delete(@name)
27
25
  wrapped = wrap_proc(inner)
28
26
  @serializer.send(_method, @name, serializer_options, &wrapped)
@@ -34,6 +32,14 @@ module Graphiti
34
32
 
35
33
  private
36
34
 
35
+ def previously_applied?
36
+ @serializer.attribute_blocks[@name].present?
37
+ end
38
+
39
+ def previously_applied_via_resource?
40
+ @serializer.send(applied_method).include?(@name)
41
+ end
42
+
37
43
  def previously_guarded?
38
44
  @serializer.field_condition_blocks[@name]
39
45
  end
@@ -1,3 +1,3 @@
1
1
  module Graphiti
2
- VERSION = "1.2.34"
2
+ VERSION = "1.2.39"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphiti
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.34
4
+ version: 1.2.39
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lee Richmond
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-03-16 00:00:00.000000000 Z
11
+ date: 2021-03-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jsonapi-serializable
@@ -361,7 +361,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
361
361
  - !ruby/object:Gem::Version
362
362
  version: '0'
363
363
  requirements: []
364
- rubygems_version: 3.0.1
364
+ rubygems_version: 3.0.6
365
365
  signing_key:
366
366
  specification_version: 4
367
367
  summary: Easily build jsonapi.org-compatible APIs