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

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
  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