graphiti 1.2.17 → 1.2.25

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: af3b1ec7bee727eb7526b6ce3057576e323f029bdda9f57447021973fa35b2ed
4
- data.tar.gz: 34deda0508ba3d23299272cb387cc19a3f90e90ee1f084b930858488dcf1222b
3
+ metadata.gz: d5f2ffef76f6dd895acddc9a4d2deb374a3d217c94d0c85fd1feb40ecac9f369
4
+ data.tar.gz: c6ce48631bf54161b0678ac5d16a8e25b49c8259ec1405138559814745569129
5
5
  SHA512:
6
- metadata.gz: 649b13c8e55456bfe9a06a9c26cdb36cd345e69137b50afc3c0861b77c334841b5cccb993d1b263986bc2e385e2fb8c28a64c718ff734ccffadb5e3b2d7d76eb
7
- data.tar.gz: f78993e3439b19afb85520c9955415ac03a4332eefc9f683828a2da0cd999b933f07b50df141e1d305ecd3799c9bef3142d4af2020bb356694ce97872b7f80c9
6
+ metadata.gz: 0e9d797893dfad771fbfe3400fc66e5830b80e8b029a059fdc12102c904e9d535b004fb08bb4501ce190effdda6f247b476ada10c6f0f6f4d963bcb0c971ab50
7
+ data.tar.gz: 5a933a0b8174449530858b06081eb09cd181d1d02f37f5befaac0e1f3dd16bf420e2e6e72d2c64a4750fd8ff2eaf3e2e333b59843c01ac613fa0c10e3e02ef0e
@@ -1,7 +1,7 @@
1
1
  ## Unreleased
2
2
 
3
3
  Features:
4
-
4
+ - [242](https://github.com/graphiti-api/graphiti/pull/242) Bump `jsonapi-renderer` to `~0.2.2` now that (https://github.com/jsonapi-rb/jsonapi-renderer/pull/36) is fixed.
5
5
  - [158](https://github.com/graphiti-api/graphiti/pull/158) Filters options `allow_nil: true`
6
6
  Option can be set at the resource level `Resource.filters_accept_nil_by_default = true`.
7
7
  By default this is set to false. (@zeisler)
data/Gemfile CHANGED
@@ -3,6 +3,8 @@ source "https://rubygems.org"
3
3
  # Specify your gem's dependencies in graphiti.gemspec
4
4
  gemspec
5
5
 
6
+ gem "standard", "0.4.7"
7
+
6
8
  group :test do
7
9
  gem "pry"
8
10
  gem "pry-byebug", platform: [:mri]
data/Guardfile CHANGED
@@ -1,7 +1,7 @@
1
1
  guard :rspec, cmd: "bundle exec rspec --color --format documentation" do
2
2
  require "guard/rspec/dsl"
3
3
  dsl = Guard::RSpec::Dsl.new(self)
4
- watch(%r{^spec/(.*)\/?(.*)_spec\.rb$})
4
+ watch(%r{^spec/(.*)/?(.*)_spec\.rb$})
5
5
 
6
6
  # Feel free to open issues for suggestions and improvements
7
7
 
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
  spec.required_ruby_version = "~> 2.3"
20
20
 
21
21
  spec.add_dependency "jsonapi-serializable", "~> 0.3.0"
22
- spec.add_dependency "jsonapi-renderer", "0.2.0"
22
+ spec.add_dependency "jsonapi-renderer", "~> 0.2", ">= 0.2.2"
23
23
  spec.add_dependency "dry-types", ">= 0.15.0", "< 2.0"
24
24
  spec.add_dependency "graphiti_errors", "~> 1.1.0"
25
25
  spec.add_dependency "concurrent-ruby", "~> 1.0"
@@ -31,5 +31,4 @@ Gem::Specification.new do |spec|
31
31
  spec.add_development_dependency "rake", "~> 10.0"
32
32
  spec.add_development_dependency "activemodel", ">= 4.1"
33
33
  spec.add_development_dependency "graphiti_spec_helpers", "1.0.beta.4"
34
- spec.add_development_dependency "standard"
35
34
  end
@@ -159,7 +159,7 @@ module Graphiti
159
159
 
160
160
  # Ensure fractional seconds don't matter
161
161
  def filter_datetime_eq(scope, attribute, value, is_not: false)
162
- ranges = value.map { |v| (v..v + 1.second - 0.00000001) }
162
+ ranges = value.map { |v| (v..v + 1.second - 0.00000001) unless v.nil? }
163
163
  clause = {attribute => ranges}
164
164
  is_not ? scope.where.not(clause) : scope.where(clause)
165
165
  end
@@ -8,6 +8,18 @@ class Graphiti::Adapters::ActiveRecord::ManyToManySideload < Graphiti::Sideload:
8
8
  foreign_key.keys.first
9
9
  end
10
10
 
11
+ def inverse_filter
12
+ return @inverse_filter if @inverse_filter
13
+
14
+ inferred_name = infer_inverse_association
15
+
16
+ if inferred_name
17
+ "#{inferred_name.to_s.singularize}_id"
18
+ else
19
+ super
20
+ end
21
+ end
22
+
11
23
  def belongs_to_many_filter(scope, value)
12
24
  if polymorphic?
13
25
  clauses = value.group_by { |v| v["type"] }.map { |group|
@@ -36,7 +48,8 @@ class Graphiti::Adapters::ActiveRecord::ManyToManySideload < Graphiti::Sideload:
36
48
 
37
49
  def filter_for(scope, value, type = nil)
38
50
  scope
39
- .includes(through_relationship_name)
51
+ .preload(through_relationship_name)
52
+ .joins(through_relationship_name)
40
53
  .where(belongs_to_many_clause(value, type))
41
54
  end
42
55
 
@@ -75,4 +88,11 @@ class Graphiti::Adapters::ActiveRecord::ManyToManySideload < Graphiti::Sideload:
75
88
  value = through_reflection.foreign_key.to_sym
76
89
  {key => value}
77
90
  end
91
+
92
+ def infer_inverse_association
93
+ through_class = through_reflection.klass
94
+
95
+ foreign_reflection = through_class.reflections[name.to_s.singularize]
96
+ foreign_reflection && foreign_reflection.options[:inverse_of]
97
+ end
78
98
  end
@@ -10,6 +10,8 @@ module Graphiti
10
10
 
11
11
  class << self
12
12
  def on_data(name, start, stop, id, payload)
13
+ return [] unless enabled
14
+
13
15
  took = ((stop - start) * 1000.0).round(2)
14
16
  params = scrub_params(payload[:params])
15
17
 
@@ -24,7 +26,7 @@ module Graphiti
24
26
  end
25
27
  end
26
28
 
27
- def on_data_exception(payload, params)
29
+ private def on_data_exception(payload, params)
28
30
  unless payload[:exception_object].instance_variable_get(:@__graphiti_debug)
29
31
  add_chunk do |logs, json|
30
32
  logs << ["\n=== Graphiti Debug ERROR", :red, true]
@@ -49,11 +51,11 @@ module Graphiti
49
51
  end
50
52
  end
51
53
 
52
- def results(raw_results)
54
+ private def results(raw_results)
53
55
  raw_results.map { |r| "[#{r.class.name}, #{r.id.inspect}]" }.join(", ")
54
56
  end
55
57
 
56
- def on_sideload_data(payload, params, took)
58
+ private def on_sideload_data(payload, params, took)
57
59
  sideload = payload[:sideload]
58
60
  results = results(payload[:results])
59
61
  add_chunk(payload[:resource], payload[:parent]) do |logs, json|
@@ -72,7 +74,7 @@ module Graphiti
72
74
  end
73
75
  end
74
76
 
75
- def on_primary_data(payload, params, took)
77
+ private def on_primary_data(payload, params, took)
76
78
  results = results(payload[:results])
77
79
  add_chunk(payload[:resource], payload[:parent]) do |logs, json|
78
80
  logs << [""]
@@ -90,6 +92,8 @@ module Graphiti
90
92
  end
91
93
 
92
94
  def on_render(name, start, stop, id, payload)
95
+ return [] unless enabled
96
+
93
97
  add_chunk do |logs|
94
98
  took = ((stop - start) * 1000.0).round(2)
95
99
  logs << [""]
@@ -20,13 +20,17 @@ module Graphiti
20
20
 
21
21
  private
22
22
 
23
+ def pagination_params
24
+ @pagination_params ||= @proxy.query.params.reject { |key, _| [:action, :controller, :format].include?(key) }
25
+ end
26
+
23
27
  def pagination_link(page)
24
28
  return nil unless @proxy.resource.endpoint
25
29
 
26
30
  uri = URI(@proxy.resource.endpoint[:url].to_s)
27
31
 
28
32
  # Overwrite the pagination query params with the desired page
29
- uri.query = @proxy.query.hash.merge({
33
+ uri.query = pagination_params.merge({
30
34
  page: {
31
35
  number: page,
32
36
  size: page_size
@@ -43,7 +43,7 @@ module Graphiti
43
43
  def extra_attribute(name, options = {}, &blk)
44
44
  allow_field = proc {
45
45
  if options[:if]
46
- next false unless instance_eval(&options[:if])
46
+ next false unless instance_exec(&options[:if])
47
47
  end
48
48
 
49
49
  @extra_fields &&
@@ -1,8 +1,8 @@
1
1
  module Graphiti
2
2
  class Query
3
- attr_reader :resource, :association_name, :params
3
+ attr_reader :resource, :association_name, :params, :action
4
4
 
5
- def initialize(resource, params, association_name = nil, nested_include = nil, parents = [])
5
+ def initialize(resource, params, association_name = nil, nested_include = nil, parents = [], action = nil)
6
6
  @resource = resource
7
7
  @association_name = association_name
8
8
  @params = params
@@ -11,6 +11,7 @@ module Graphiti
11
11
  @params = @params.deep_symbolize_keys
12
12
  @include_param = nested_include || @params[:include]
13
13
  @parents = parents
14
+ @action = parse_action(action)
14
15
  end
15
16
 
16
17
  def association?
@@ -44,16 +45,19 @@ module Graphiti
44
45
 
45
46
  def hash
46
47
  @hash ||= {}.tap do |h|
47
- h[:filter] = filters unless filters.empty?
48
- h[:sort] = sorts unless sorts.empty?
49
- h[:page] = pagination unless pagination.empty?
50
- unless association?
51
- h[:fields] = fields unless fields.empty?
52
- h[:extra_fields] = extra_fields unless extra_fields.empty?
48
+ h[:filter] = filters
49
+ h[:sort] = sorts
50
+ h[:page] = pagination
51
+ if association?
52
+ resource_type = @resource.class.type
53
+ h[:extra_fields] = {resource_type => extra_fields[resource_type]} if extra_fields.key?(resource_type)
54
+ else
55
+ h[:fields] = fields
56
+ h[:extra_fields] = extra_fields
53
57
  end
54
- h[:stats] = stats unless stats.empty?
55
- h[:include] = sideload_hash unless sideload_hash.empty?
56
- end
58
+ h[:stats] = stats
59
+ h[:include] = sideload_hash
60
+ end.reject { |_, value| value.empty? }
57
61
  end
58
62
 
59
63
  def zero_results?
@@ -92,7 +96,7 @@ module Graphiti
92
96
  sl_resource = resource_for_sideload(sideload)
93
97
  query_parents = parents + [self]
94
98
  sub_hash = sub_hash[:include] if sub_hash.key?(:include)
95
- hash[key] = Query.new(sl_resource, @params, key, sub_hash, query_parents)
99
+ hash[key] = Query.new(sl_resource, @params, key, sub_hash, query_parents, :all)
96
100
  else
97
101
  handle_missing_sideload(key)
98
102
  end
@@ -312,5 +316,17 @@ module Graphiti
312
316
  end
313
317
  end
314
318
  end
319
+
320
+ def parse_action(action)
321
+ action ||= @params.fetch(:action, Graphiti.context[:namespace]).try(:to_sym)
322
+ case action
323
+ when :index
324
+ :all
325
+ when :show
326
+ :find
327
+ else
328
+ action
329
+ end
330
+ end
315
331
  end
316
332
  end
@@ -6,18 +6,18 @@ module Graphiti
6
6
  :deserialized_payload,
7
7
  to: :@validator
8
8
 
9
- def initialize(root_resource, raw_params)
10
- @validator = ValidatorFactory.create(root_resource, raw_params)
9
+ def initialize(root_resource, raw_params, action)
10
+ @validator = ValidatorFactory.create(root_resource, raw_params, action)
11
11
  end
12
12
 
13
13
  class ValidatorFactory
14
- def self.create(root_resource, raw_params)
15
- case raw_params["action"]
16
- when "update" then
14
+ def self.create(root_resource, raw_params, action)
15
+ case action
16
+ when :update then
17
17
  RequestValidators::UpdateValidator
18
18
  else
19
19
  RequestValidators::Validator
20
- end.new(root_resource, raw_params)
20
+ end.new(root_resource, raw_params, action)
21
21
  end
22
22
  end
23
23
  end
@@ -3,10 +3,11 @@ module Graphiti
3
3
  class Validator
4
4
  attr_reader :errors
5
5
 
6
- def initialize(root_resource, raw_params)
6
+ def initialize(root_resource, raw_params, action)
7
7
  @root_resource = root_resource
8
8
  @raw_params = raw_params
9
9
  @errors = Graphiti::Util::SimpleErrors.new(raw_params)
10
+ @action = action
10
11
  end
11
12
 
12
13
  def validate
@@ -68,6 +69,11 @@ module Graphiti
68
69
 
69
70
  def typecast_attributes(resource, attributes, payload_path)
70
71
  attributes.each_pair do |key, value|
72
+ # Only validate id if create action, otherwise it's only used for lookup
73
+ next if @action != :create &&
74
+ key == :id &&
75
+ resource.class.config[:attributes][:id][:writable] == false
76
+
71
77
  begin
72
78
  attributes[key] = resource.typecast(key, value, :writable)
73
79
  rescue Graphiti::Errors::UnknownAttribute
@@ -148,5 +148,15 @@ module Graphiti
148
148
  end
149
149
  response
150
150
  end
151
+
152
+ def links?
153
+ self.class.links.any?
154
+ end
155
+
156
+ def links(model)
157
+ self.class.links.each_with_object({}) do |(name, blk), memo|
158
+ memo[name] = instance_exec(model, &blk)
159
+ end
160
+ end
151
161
  end
152
162
  end
@@ -199,7 +199,8 @@ module Graphiti
199
199
  attributes: {},
200
200
  extra_attributes: {},
201
201
  sideloads: {},
202
- callbacks: {}
202
+ callbacks: {},
203
+ links: {}
203
204
  }
204
205
  end
205
206
 
@@ -238,6 +239,10 @@ module Graphiti
238
239
  def default_filters
239
240
  config[:default_filters]
240
241
  end
242
+
243
+ def links
244
+ config[:links]
245
+ end
241
246
  end
242
247
 
243
248
  def get_attr!(name, flag, options = {})
@@ -5,6 +5,7 @@ module Graphiti
5
5
 
6
6
  class_methods do
7
7
  def filter(name, *args, &blk)
8
+ name = name.to_sym
8
9
  opts = args.extract_options!
9
10
  type_override = args[0]
10
11
 
@@ -135,6 +136,7 @@ module Graphiti
135
136
  filterable: false
136
137
  }
137
138
  options = defaults.merge(options)
139
+ attribute_option(options, :readable)
138
140
  config[:extra_attributes][name] = options
139
141
  apply_extra_attributes_to_serializer
140
142
  end
@@ -147,6 +149,10 @@ module Graphiti
147
149
  end
148
150
  end
149
151
 
152
+ def link(name, &blk)
153
+ config[:links][name.to_sym] = blk
154
+ end
155
+
150
156
  def all_attributes
151
157
  attributes.merge(extra_attributes)
152
158
  end
@@ -11,7 +11,7 @@ module Graphiti
11
11
 
12
12
  # @api private
13
13
  def _all(params, opts, base_scope)
14
- runner = Runner.new(self, params, opts.delete(:query))
14
+ runner = Runner.new(self, params, opts.delete(:query), :all)
15
15
  opts[:params] = params
16
16
  runner.proxy(base_scope, opts)
17
17
  end
@@ -27,7 +27,7 @@ module Graphiti
27
27
  params[:filter] ||= {}
28
28
  params[:filter][:id] = id if id
29
29
 
30
- runner = Runner.new(self, params)
30
+ runner = Runner.new(self, params, nil, :find)
31
31
  runner.proxy base_scope,
32
32
  single: true,
33
33
  raise_on_missing: true,
@@ -93,7 +93,7 @@ module Graphiti
93
93
  original = Graphiti.context[:namespace]
94
94
  begin
95
95
  Graphiti.context[:namespace] = action
96
- ::Graphiti::RequestValidator.new(@resource, @payload.params).validate!
96
+ ::Graphiti::RequestValidator.new(@resource, @payload.params, action).validate!
97
97
  validator = persist {
98
98
  @resource.persist_with_relationships \
99
99
  @payload.meta(action: action),
@@ -118,6 +118,7 @@ module Graphiti
118
118
  end
119
119
 
120
120
  def destroy
121
+ data
121
122
  transaction_response = @resource.transaction do
122
123
  metadata = {method: :destroy}
123
124
  model = @resource.destroy(@query.filters[:id], metadata)
@@ -135,6 +136,7 @@ module Graphiti
135
136
  end
136
137
 
137
138
  def update_attributes
139
+ data
138
140
  save(action: :update)
139
141
  end
140
142
 
@@ -3,13 +3,13 @@ module Graphiti
3
3
  attr_reader :params
4
4
  attr_reader :deserialized_payload
5
5
 
6
- def initialize(resource_class, params, query = nil)
6
+ def initialize(resource_class, params, query = nil, action = nil)
7
7
  @resource_class = resource_class
8
8
  @params = params
9
9
  @query = query
10
+ @action = action
10
11
 
11
- validator = RequestValidator.new(jsonapi_resource, params)
12
-
12
+ validator = RequestValidator.new(jsonapi_resource, params, action)
13
13
  validator.validate!
14
14
 
15
15
  @deserialized_payload = validator.deserialized_payload
@@ -30,7 +30,7 @@ module Graphiti
30
30
  end
31
31
 
32
32
  def query
33
- @query ||= Query.new(jsonapi_resource, params)
33
+ @query ||= Query.new(jsonapi_resource, params, nil, nil, [], @action)
34
34
  end
35
35
 
36
36
  def query_hash
@@ -25,6 +25,7 @@ module Graphiti
25
25
  def as_jsonapi(*)
26
26
  super.tap do |hash|
27
27
  strip_relationships!(hash) if strip_relationships?
28
+ add_links!(hash)
28
29
  end
29
30
  end
30
31
 
@@ -49,6 +50,12 @@ module Graphiti
49
50
 
50
51
  private
51
52
 
53
+ def add_links!(hash)
54
+ return unless @resource.respond_to?(:links?)
55
+
56
+ hash[:links] = @resource.links(@object) if @resource.links?
57
+ end
58
+
52
59
  def strip_relationships!(hash)
53
60
  hash[:relationships]&.select! do |name, payload|
54
61
  payload.key?(:data)
@@ -132,6 +132,10 @@ module Graphiti
132
132
  end
133
133
  end
134
134
 
135
+ def link_filter(parents)
136
+ base_filter(parents)
137
+ end
138
+
135
139
  # The parent resource is a remote,
136
140
  # AND the sideload is a remote to the same endpoint
137
141
  def shared_remote?
@@ -1,8 +1,18 @@
1
1
  class Graphiti::Sideload::HasMany < Graphiti::Sideload
2
+ def initialize(name, opts)
3
+ @inverse_filter = opts[:inverse_filter]
4
+
5
+ super(name, opts)
6
+ end
7
+
2
8
  def type
3
9
  :has_many
4
10
  end
5
11
 
12
+ def inverse_filter
13
+ @inverse_filter || foreign_key
14
+ end
15
+
6
16
  def load_params(parents, query)
7
17
  query.hash.tap do |hash|
8
18
  hash[:filter] ||= {}
@@ -11,11 +21,19 @@ class Graphiti::Sideload::HasMany < Graphiti::Sideload
11
21
  end
12
22
 
13
23
  def base_filter(parents)
14
- {foreign_key => ids_for_parents(parents).join(",")}
24
+ {foreign_key => parent_filter(parents)}
25
+ end
26
+
27
+ def link_filter(parents)
28
+ {inverse_filter => parent_filter(parents)}
15
29
  end
16
30
 
17
31
  private
18
32
 
33
+ def parent_filter(parents)
34
+ ids_for_parents(parents).join(",")
35
+ end
36
+
19
37
  def child_map(children)
20
38
  children.group_by(&foreign_key)
21
39
  end
@@ -11,8 +11,12 @@ class Graphiti::Sideload::ManyToMany < Graphiti::Sideload::HasMany
11
11
  foreign_key.values.first
12
12
  end
13
13
 
14
+ def inverse_filter
15
+ @inverse_filter || true_foreign_key
16
+ end
17
+
14
18
  def base_filter(parents)
15
- {true_foreign_key => ids_for_parents(parents).join(",")}
19
+ {true_foreign_key => parent_filter(parents)}
16
20
  end
17
21
 
18
22
  def infer_foreign_key
@@ -32,7 +36,7 @@ class Graphiti::Sideload::ManyToMany < Graphiti::Sideload::HasMany
32
36
  self_ref = self
33
37
  fk_type = parent_resource_class.attributes[:id][:type]
34
38
  fk_type = :hash if polymorphic?
35
- resource_class.filter true_foreign_key, fk_type do
39
+ resource_class.filter inverse_filter, fk_type do
36
40
  eq do |scope, value|
37
41
  self_ref.belongs_to_many_filter(scope, value)
38
42
  end
@@ -60,7 +60,7 @@ module Graphiti
60
60
  def params
61
61
  @params ||= {}.tap do |params|
62
62
  if @sideload.type != :belongs_to || @sideload.remote?
63
- params[:filter] = @sideload.base_filter([@model])
63
+ params[:filter] = @sideload.link_filter([@model])
64
64
  end
65
65
 
66
66
  @sideload.params_proc&.call(params, [@model], context)
@@ -1,3 +1,3 @@
1
1
  module Graphiti
2
- VERSION = "1.2.17"
2
+ VERSION = "1.2.25"
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.17
4
+ version: 1.2.25
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lee Richmond
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-04-30 00:00:00.000000000 Z
11
+ date: 2020-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jsonapi-serializable
@@ -28,16 +28,22 @@ dependencies:
28
28
  name: jsonapi-renderer
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '='
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.2'
34
+ - - ">="
32
35
  - !ruby/object:Gem::Version
33
- version: 0.2.0
36
+ version: 0.2.2
34
37
  type: :runtime
35
38
  prerelease: false
36
39
  version_requirements: !ruby/object:Gem::Requirement
37
40
  requirements:
38
- - - '='
41
+ - - "~>"
39
42
  - !ruby/object:Gem::Version
40
- version: 0.2.0
43
+ version: '0.2'
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 0.2.2
41
47
  - !ruby/object:Gem::Dependency
42
48
  name: dry-types
43
49
  requirement: !ruby/object:Gem::Requirement
@@ -184,20 +190,6 @@ dependencies:
184
190
  - - '='
185
191
  - !ruby/object:Gem::Version
186
192
  version: 1.0.beta.4
187
- - !ruby/object:Gem::Dependency
188
- name: standard
189
- requirement: !ruby/object:Gem::Requirement
190
- requirements:
191
- - - ">="
192
- - !ruby/object:Gem::Version
193
- version: '0'
194
- type: :development
195
- prerelease: false
196
- version_requirements: !ruby/object:Gem::Requirement
197
- requirements:
198
- - - ">="
199
- - !ruby/object:Gem::Version
200
- version: '0'
201
193
  description:
202
194
  email:
203
195
  - richmolj@gmail.com