graphiti 1.0.beta.15 → 1.0.beta.16

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e06792e034635555f5687496aebef2ed8e0eac93
4
- data.tar.gz: c3d10077cc25cff01a1adf79059dc3c3b6ee213d
3
+ metadata.gz: ff5dc08a7d35f114a138a26759c857eb11a226de
4
+ data.tar.gz: 00d815886fc13aca087ce7b140fc472020cbfd5c
5
5
  SHA512:
6
- metadata.gz: 4cba835660dc04c71d333703860acd47eb408a463bc1cdb5c4d29d75d0904ee680fddceb6497490fd4c47effaadef6bd65ddb3285984931f999d950073e8a894
7
- data.tar.gz: 1d3f3b288997909c4763966ce32dab8cf4998f56a29122c630a3f953741e471d2bb5bcfa56676c710007f2a51eaff1948593d5619270c4f606093fa5ce886052
6
+ metadata.gz: bff46d68fcb42853a4d9e31e169ce1a06ea6bb84007728f86cc5f3b2901a3b7b5e310c906e5ad147b14161fc215c7158dd56b92b7dd12b810fd9a599c6b7f74a
7
+ data.tar.gz: f519c2331d8c4e30550bcd5481ff07c03ab9ce020855e6b1c7993f60e94d1a12f4930a39eebb4c1c4615cb7cf588ee4127bb6c820d17d4c67ed552c4b61cf20e
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2016 Venkata Pasupuleti
3
+ Copyright (c) 2018 Lee Richmond
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/lib/graphiti.rb CHANGED
@@ -27,6 +27,8 @@ require "graphiti/resource/configuration"
27
27
  require "graphiti/resource/dsl"
28
28
  require "graphiti/resource/interface"
29
29
  require "graphiti/resource/polymorphism"
30
+ require "graphiti/resource/documentation"
31
+ require "graphiti/resource/persistence"
30
32
  require "graphiti/sideload"
31
33
  require "graphiti/sideload/has_many"
32
34
  require "graphiti/sideload/belongs_to"
@@ -194,3 +196,5 @@ end
194
196
  class Object
195
197
  prepend InstanceVariableOverride
196
198
  end
199
+
200
+ ActiveSupport.run_load_hooks(:graphiti, Graphiti)
@@ -41,7 +41,9 @@ module Graphiti
41
41
  float: numerical_operators,
42
42
  boolean: [:eq],
43
43
  date: numerical_operators,
44
- datetime: numerical_operators
44
+ datetime: numerical_operators,
45
+ hash: [:eq],
46
+ array: [:eq]
45
47
  }
46
48
  end
47
49
 
@@ -379,75 +381,26 @@ module Graphiti
379
381
  end
380
382
  end
381
383
 
382
- # Remove the association without destroying objects
383
- #
384
- # This is NOT needed in the standard use case. The standard use case would be:
385
- #
386
- # def update(attrs)
387
- # # attrs[:the_foreign_key] is nil, so updating the record disassociates
388
- # end
389
- #
390
- # However, sometimes you need side-effect or elsewise non-standard behavior. Consider
391
- # using {{https://github.com/mbleigh/acts-as-taggable-on acts_as_taggable_on}} gem:
392
- #
393
- # # Not actually needed, just an example
394
- # def disassociate(parent, child, association_name, association_type)
395
- # parent.tag_list.remove(child.name)
396
- # end
397
- #
398
- # @example Basic accessor
399
- # def disassociate(parent, child, association_name, association_type)
400
- # if association_type == :has_many
401
- # parent.send(association_name).delete(child)
402
- # else
403
- # child.send(:"#{association_name}=", nil)
404
- # end
405
- # end
406
- #
407
- # +association_name+ and +association_type+ come from your sideload
408
- # configuration:
409
- #
410
- # allow_sideload :the_name, type: the_type do
411
- # # ... code.
412
- # end
413
- #
414
- # @param parent The parent object (via the JSONAPI 'relationships' graph)
415
- # @param child The child object (via the JSONAPI 'relationships' graph)
416
- # @param association_name The 'relationships' key we are processing
417
- # @param association_type The Sideload type (see Sideload#type). Usually :has_many/:belongs_to/etc
418
384
  def disassociate(parent, child, association_name, association_type)
419
385
  raise 'you must override #disassociate in an adapter subclass'
420
386
  end
421
387
 
422
- # @param [Class] model_class The configured model class (see Resource.model)
423
- # @param [Hash] create_params Attributes + id
424
- # @return the model instance just created
425
- # @see Resource.model
426
- # @example ActiveRecord default
427
- # def create(model_class, create_params)
428
- # instance = model_class.new(create_params)
429
- # instance.save
430
- # instance
431
- # end
432
- def create(model_class, create_params)
433
- raise 'you must override #create in an adapter subclass'
388
+ def build(model_class)
389
+ model_class.new
434
390
  end
435
391
 
436
- # @param [Class] model_class The configured model class (see Resource.model)
437
- # @param [Hash] update_params Attributes + id
438
- # @return the model instance just created
439
- # @see Resource.model
440
- # @example ActiveRecord default
441
- # def update(model_class, update_params)
442
- # instance = model_class.find(update_params.delete(:id))
443
- # instance.update_attributes(update_params)
444
- # instance
445
- # end
446
- def update(model_class, update_params)
447
- raise 'you must override #update in an adapter subclass'
392
+ # TODO respond to and specific error
393
+ def assign_attributes(model_instance, attributes)
394
+ attributes.each_pair do |k, v|
395
+ model_instance.send(:"#{k}=", v)
396
+ end
397
+ end
398
+
399
+ def save(model_instance)
400
+ raise 'you must override #save in an adapter subclass'
448
401
  end
449
402
 
450
- def destroy(model)
403
+ def destroy(model_instance)
451
404
  raise 'you must override #destroy in an adapter subclass'
452
405
  end
453
406
 
@@ -260,6 +260,16 @@ module Graphiti
260
260
  model.destroy
261
261
  model
262
262
  end
263
+
264
+ def save(model_instance)
265
+ model_instance.save
266
+ model_instance
267
+ end
268
+
269
+ def destroy(model_instance)
270
+ model_instance.destroy
271
+ model_instance
272
+ end
263
273
  end
264
274
  end
265
275
  end
@@ -16,6 +16,19 @@ The adapter #{@adapter.class} does not implement method '#{@method}', which was
16
16
  end
17
17
  end
18
18
 
19
+ class AroundCallbackProc < Base
20
+ def initialize(resource_class, method_name)
21
+ @resource_class = resource_class
22
+ @method_name = method_name
23
+ end
24
+
25
+ def message
26
+ <<-MSG
27
+ #{@resource_class}: Tried to pass block to .#{@method_name}, which only accepts a method name.
28
+ MSG
29
+ end
30
+ end
31
+
19
32
  class UnsupportedOperator < Base
20
33
  def initialize(resource, filter_name, supported, operator)
21
34
  @resource = resource
@@ -5,7 +5,7 @@ module Graphiti
5
5
 
6
6
  def initialize(resource, type_name, opts)
7
7
  @procs = {}
8
- defaults = resource.adapter.default_operators[type_name] || []
8
+ defaults = resource.adapter.default_operators[type_name] || [:eq]
9
9
  if opts[:only]
10
10
  defaults = defaults.select { |op| opts[:only].include?(op) }
11
11
  end
@@ -13,6 +13,12 @@ module Graphiti
13
13
  end
14
14
  end
15
15
 
16
+ initializer 'graphti.logger' do
17
+ config.after_initialize do
18
+ Graphiti.logger = ::Rails.logger
19
+ end
20
+ end
21
+
16
22
  initializer 'graphiti.init' do
17
23
  if ::Rails.application.config.eager_load
18
24
  config.after_initialize do |app|
@@ -5,6 +5,8 @@ module Graphiti
5
5
  include Configuration
6
6
  include Sideloading
7
7
  include Links
8
+ include Documentation
9
+ include Persistence
8
10
 
9
11
  attr_reader :context
10
12
 
@@ -80,18 +82,6 @@ module Graphiti
80
82
  end
81
83
  end
82
84
 
83
- def create(create_params)
84
- adapter.create(model, create_params)
85
- end
86
-
87
- def update(update_params)
88
- adapter.update(model, update_params)
89
- end
90
-
91
- def destroy(model)
92
- adapter.destroy(model)
93
- end
94
-
95
85
  def associate_all(parent, children, association_name, type)
96
86
  adapter.associate_all(parent, children, association_name, type)
97
87
  end
@@ -169,7 +169,8 @@ module Graphiti
169
169
  before_commit: {},
170
170
  attributes: {},
171
171
  extra_attributes: {},
172
- sideloads: {}
172
+ sideloads: {},
173
+ callbacks: {}
173
174
  }
174
175
  end
175
176
 
@@ -0,0 +1,55 @@
1
+ module Graphiti
2
+ class Resource
3
+ module Documentation
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ def description=(val)
8
+ @description = val
9
+ end
10
+
11
+ def description
12
+ return @description if @description.present?
13
+
14
+ if defined?(::I18n)
15
+ desc = ::I18n.t :description,
16
+ scope: i18n_resource_scope,
17
+ default: nil
18
+ desc ||= ::I18n.t :description,
19
+ scope: i18n_type_scope,
20
+ default: nil
21
+ end
22
+ end
23
+
24
+ # @api private
25
+ def attribute_description(attr_name)
26
+ desc = all_attributes[attr_name][:description]
27
+ return desc if desc.present?
28
+
29
+ if defined?(::I18n)
30
+ desc = ::I18n.t :description,
31
+ scope: [*i18n_type_scope, :attributes, attr_name],
32
+ default: nil
33
+ desc ||= ::I18n.t :description,
34
+ scope: [*i18n_resource_scope, :attributes, attr_name],
35
+ default: nil
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def underscored_resource_name
42
+ self.name.gsub(/Resource$/, '').underscore
43
+ end
44
+
45
+ def i18n_resource_scope
46
+ [:graphiti, :resources, underscored_resource_name]
47
+ end
48
+
49
+ def i18n_type_scope
50
+ [:graphiti, :types, type]
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,182 @@
1
+ module Graphiti
2
+ class Resource
3
+ module Persistence
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ def before_attributes(method = nil, only: [:create, :update], &blk)
8
+ add_callback(:attributes, :before, method, only, &blk)
9
+ end
10
+
11
+ def after_attributes(method = nil, only: [:create, :update], &blk)
12
+ add_callback(:attributes, :after, method, only, &blk)
13
+ end
14
+
15
+ def before_save(method = nil, only: [:create, :update], &blk)
16
+ add_callback(:save, :before, method, only, &blk)
17
+ end
18
+
19
+ def after_save(method = nil, only: [:create, :update], &blk)
20
+ add_callback(:save, :after, method, only, &blk)
21
+ end
22
+
23
+ def before_destroy(method = nil, &blk)
24
+ add_callback(:destroy, :before, method, [:destroy], &blk)
25
+ end
26
+
27
+ def after_destroy(method = nil, &blk)
28
+ add_callback(:destroy, :after, method, [:destroy], &blk)
29
+ end
30
+
31
+ def around_attributes(method = nil, only: [:create, :update], &blk)
32
+ if blk
33
+ raise Errors::AroundCallbackProc.new(self, 'around_attributes')
34
+ else
35
+ add_callback(:attributes, :around, method, only, &blk)
36
+ end
37
+ end
38
+
39
+ def around_save(method = nil, only: [:create, :update], &blk)
40
+ if blk
41
+ raise Errors::AroundCallbackProc.new(self, 'around_save')
42
+ else
43
+ add_callback(:save, :around, method, only, &blk)
44
+ end
45
+ end
46
+
47
+ def around_destroy(method = nil, &blk)
48
+ if blk
49
+ raise Errors::AroundCallbackProc.new(self, 'around_destroy')
50
+ else
51
+ add_callback(:destroy, :around, method, [:destroy], &blk)
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def add_callback(kind, lifecycle, method = nil, only, &blk)
58
+ config[:callbacks][kind] ||= {}
59
+ config[:callbacks][kind][lifecycle] ||= []
60
+ config[:callbacks][kind][lifecycle] << { callback: (method || blk), only: only }
61
+ end
62
+ end
63
+
64
+ def create(create_params)
65
+ model_instance = nil
66
+
67
+ run_callbacks :attributes, :create, create_params do |params|
68
+ model_instance = build(model)
69
+ assign_attributes(model_instance, params)
70
+ model_instance
71
+ end
72
+
73
+ run_callbacks :save, :create, model_instance do
74
+ model_instance = save(model_instance)
75
+ end
76
+
77
+ model_instance
78
+ end
79
+
80
+ def update(update_params)
81
+ model_instance = nil
82
+ id = update_params.delete(:id)
83
+
84
+ run_callbacks :attributes, :update, update_params do |params|
85
+ model_instance = self.class._find(params.merge(id: id)).data
86
+ assign_attributes(model_instance, params)
87
+ model_instance
88
+ end
89
+
90
+ run_callbacks :save, :update, model_instance do
91
+ model_instance = save(model_instance)
92
+ end
93
+
94
+ model_instance
95
+ end
96
+
97
+ def destroy(id)
98
+ model_instance = self.class._find(id: id).data
99
+ run_callbacks :destroy, :destroy, model_instance do
100
+ adapter.destroy(model_instance)
101
+ end
102
+ model_instance
103
+ end
104
+
105
+ def build(model)
106
+ adapter.build(model)
107
+ end
108
+
109
+ def assign_attributes(model_instance, update_params)
110
+ adapter.assign_attributes(model_instance, update_params)
111
+ end
112
+
113
+ def save(model_instance)
114
+ adapter.save(model_instance)
115
+ end
116
+
117
+ private
118
+
119
+ def run_callbacks(kind, action, *args)
120
+ fire_around_callbacks(kind, action, *args) do |*yieldargs|
121
+ fire_callbacks(kind, :before, action, *yieldargs)
122
+ result = yield(*yieldargs)
123
+ fire_callbacks(kind, :after, action, result)
124
+ result
125
+ end
126
+ end
127
+
128
+ def fire_callbacks(kind, lifecycle, action, *args)
129
+ if callbacks = self.class.config[:callbacks][kind]
130
+ callbacks = callbacks[lifecycle] || []
131
+ callbacks.each do |config|
132
+ callback = config[:callback]
133
+ next unless config[:only].include?(action)
134
+
135
+ if callback.respond_to?(:call)
136
+ instance_exec(*args, &callback)
137
+ else
138
+ send(callback, *args)
139
+ end
140
+ end
141
+ end
142
+ end
143
+
144
+ def fire_around_callbacks(kind, action, *args, &blk)
145
+ callbacks = self.class.config[:callbacks][kind].try(:[], :around) || []
146
+ callbacks = callbacks.select { |cb| cb[:only].include?(action) }
147
+ if callbacks.length.zero?
148
+ yield(*args)
149
+ else
150
+ prc = around_callback_proc(callbacks, 0, *args, &blk)
151
+ instance_exec(*args, &prc)
152
+ end
153
+ end
154
+
155
+ # The tricky thing here is we need to yield to the next around callback
156
+ # until there are no more callbacks, then we want to call the original block
157
+ # Also keep in mind each callback needs to yield to the next
158
+ def around_callback_proc(callbacks, index, *args, &blk)
159
+ method_name = callbacks[index][:callback]
160
+
161
+ if callbacks[index + 1]
162
+ proc do
163
+ r = nil
164
+ send(method_name, *args) do |r2|
165
+ wrapped = around_callback_proc(callbacks, index+1, r2, &blk)
166
+ r = instance_exec(r2, &wrapped)
167
+ end
168
+ r
169
+ end
170
+ else
171
+ proc do |result|
172
+ r = nil
173
+ send(callbacks[index][:callback], result) do |r2|
174
+ r = instance_exec(r2, &blk)
175
+ end
176
+ r
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
@@ -95,9 +95,8 @@ module Graphiti
95
95
  end
96
96
 
97
97
  def destroy
98
- record = data
99
98
  validator = @resource.transaction do
100
- model = @resource.destroy(record)
99
+ model = @resource.destroy(@query.filters[:id])
101
100
  model.instance_variable_set(:@__serializer_klass, @resource.serializer)
102
101
  validator = ::Graphiti::Util::ValidationResponse.new \
103
102
  model, @payload
@@ -83,6 +83,7 @@ module Graphiti
83
83
  config = {
84
84
  name: r.name,
85
85
  type: r.type.to_s,
86
+ description: r.description,
86
87
  attributes: attributes(r),
87
88
  extra_attributes: extra_attributes(r),
88
89
  sorts: sorts(r),
@@ -116,7 +117,8 @@ module Graphiti
116
117
  attrs[name] = {
117
118
  type: config[:type].to_s,
118
119
  readable: flag(config[:readable]),
119
- writable: flag(config[:writable])
120
+ writable: flag(config[:writable]),
121
+ description: resource.attribute_description(name),
120
122
  }
121
123
  end
122
124
  end
@@ -128,7 +130,8 @@ module Graphiti
128
130
  resource.extra_attributes.each_pair do |name, config|
129
131
  attrs[name] = {
130
132
  type: config[:type].to_s,
131
- readable: flag(config[:readable])
133
+ readable: flag(config[:readable]),
134
+ description: resource.attribute_description(name),
132
135
  }
133
136
  end
134
137
  end
@@ -1,32 +1,7 @@
1
1
  module Graphiti
2
- # Apply filtering logic to the scope
3
- #
4
- # If the user requests to filter a field that has not been allowlisted,
5
- # a +Graphiti::Errors::BadFilter+ error will be raised.
6
- #
7
- # allow_filter :title # :title now allowlisted
8
- #
9
- # If the user requests a filter field that has been allowlisted, but
10
- # does not pass the associated `+:if+ clause, +BadFilter+ will be raised.
11
- #
12
- # allow_filter :title, if: :admin?
13
- #
14
- # This will also honor filter aliases.
15
- #
16
- # # GET /posts?filter[headline]=foo will filter on title
17
- # allow_filter :title, aliases: [:headline]
18
- #
19
- # @see Adapters::Abstract#filter
20
- # @see Adapters::ActiveRecord#filter
21
- # @see Resource.allow_filter
22
2
  class Scoping::Filter < Scoping::Base
23
3
  include Scoping::Filterable
24
4
 
25
- # Apply the filtering logic.
26
- #
27
- # Loop and parse all requested filters, taking into account guards and
28
- # aliases. If valid, call either the default or custom filtering logic.
29
- # @return the scope we are chaining/modifying
30
5
  def apply
31
6
  if missing_required_filters.any?
32
7
  raise Errors::RequiredFilter.new(resource, missing_required_filters)
@@ -47,8 +22,6 @@ module Graphiti
47
22
  private
48
23
 
49
24
  def filter_scope(filter, operator, value)
50
- operator = operator.to_s.gsub('!', 'not_').to_sym
51
-
52
25
  if custom_scope = filter.values[0][:operators][operator]
53
26
  custom_scope.call(@scope, value, resource.context)
54
27
  else
@@ -73,6 +46,7 @@ module Graphiti
73
46
  filter_param.each_pair do |param_name, param_value|
74
47
  filter = find_filter!(param_name)
75
48
  value, operator = normalize_param(filter, param_value)
49
+ operator = operator.to_s.gsub('!', 'not_').to_sym
76
50
  validate_operator(filter, operator)
77
51
  value = parse_string_value(filter.values[0], value) if value.is_a?(String)
78
52
  validate_singular(resource, filter, value)
@@ -98,7 +72,9 @@ module Graphiti
98
72
  end
99
73
 
100
74
  def normalize_param(filter, param_value)
101
- param_value = { eq: param_value } unless param_value.is_a?(Hash)
75
+ unless param_value.is_a?(Hash) && param_value.present?
76
+ param_value = { eq: param_value }
77
+ end
102
78
  value = param_value.values.first
103
79
  operator = param_value.keys.first
104
80
 
@@ -149,8 +149,7 @@ class Graphiti::Util::Persistence
149
149
  def persist_object(method, attributes)
150
150
  case method
151
151
  when :destroy
152
- model = @resource.class._find(attributes.slice(:id)).data
153
- call_resource_method(:destroy, model, @caller_model)
152
+ call_resource_method(:destroy, attributes[:id], @caller_model)
154
153
  when :update, nil, :disassociate
155
154
  call_resource_method(:update, attributes, @caller_model)
156
155
  else
@@ -1,3 +1,3 @@
1
1
  module Graphiti
2
- VERSION = "1.0.beta.15"
2
+ VERSION = "1.0.beta.16"
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.0.beta.15
4
+ version: 1.0.beta.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lee Richmond
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-11-02 00:00:00.000000000 Z
11
+ date: 2018-11-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jsonapi-serializable
@@ -280,9 +280,11 @@ files:
280
280
  - lib/graphiti/renderer.rb
281
281
  - lib/graphiti/resource.rb
282
282
  - lib/graphiti/resource/configuration.rb
283
+ - lib/graphiti/resource/documentation.rb
283
284
  - lib/graphiti/resource/dsl.rb
284
285
  - lib/graphiti/resource/interface.rb
285
286
  - lib/graphiti/resource/links.rb
287
+ - lib/graphiti/resource/persistence.rb
286
288
  - lib/graphiti/resource/polymorphism.rb
287
289
  - lib/graphiti/resource/sideloading.rb
288
290
  - lib/graphiti/resource_proxy.rb