graphiti-rb 1.0.alpha.1
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 +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +20 -0
- data/.yardopts +2 -0
- data/Appraisals +11 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +12 -0
- data/Guardfile +32 -0
- data/LICENSE.txt +21 -0
- data/README.md +75 -0
- data/Rakefile +15 -0
- data/bin/appraisal +17 -0
- data/bin/console +14 -0
- data/bin/rspec +17 -0
- data/bin/setup +8 -0
- data/gemfiles/rails_4.gemfile +17 -0
- data/gemfiles/rails_5.gemfile +17 -0
- data/graphiti.gemspec +34 -0
- data/lib/generators/jsonapi/resource_generator.rb +169 -0
- data/lib/generators/jsonapi/templates/application_resource.rb.erb +15 -0
- data/lib/generators/jsonapi/templates/controller.rb.erb +61 -0
- data/lib/generators/jsonapi/templates/create_request_spec.rb.erb +30 -0
- data/lib/generators/jsonapi/templates/destroy_request_spec.rb.erb +20 -0
- data/lib/generators/jsonapi/templates/index_request_spec.rb.erb +22 -0
- data/lib/generators/jsonapi/templates/resource.rb.erb +11 -0
- data/lib/generators/jsonapi/templates/resource_reads_spec.rb.erb +62 -0
- data/lib/generators/jsonapi/templates/resource_writes_spec.rb.erb +63 -0
- data/lib/generators/jsonapi/templates/show_request_spec.rb.erb +21 -0
- data/lib/generators/jsonapi/templates/update_request_spec.rb.erb +34 -0
- data/lib/graphiti-rb.rb +1 -0
- data/lib/graphiti.rb +121 -0
- data/lib/graphiti/adapters/abstract.rb +516 -0
- data/lib/graphiti/adapters/active_record.rb +6 -0
- data/lib/graphiti/adapters/active_record/base.rb +249 -0
- data/lib/graphiti/adapters/active_record/belongs_to_sideload.rb +17 -0
- data/lib/graphiti/adapters/active_record/has_many_sideload.rb +17 -0
- data/lib/graphiti/adapters/active_record/has_one_sideload.rb +17 -0
- data/lib/graphiti/adapters/active_record/inferrence.rb +12 -0
- data/lib/graphiti/adapters/active_record/many_to_many_sideload.rb +30 -0
- data/lib/graphiti/adapters/null.rb +236 -0
- data/lib/graphiti/base.rb +70 -0
- data/lib/graphiti/configuration.rb +21 -0
- data/lib/graphiti/context.rb +16 -0
- data/lib/graphiti/deserializer.rb +208 -0
- data/lib/graphiti/errors.rb +309 -0
- data/lib/graphiti/extensions/boolean_attribute.rb +33 -0
- data/lib/graphiti/extensions/extra_attribute.rb +70 -0
- data/lib/graphiti/extensions/temp_id.rb +26 -0
- data/lib/graphiti/filter_operators.rb +25 -0
- data/lib/graphiti/hash_renderer.rb +57 -0
- data/lib/graphiti/jsonapi_serializable_ext.rb +50 -0
- data/lib/graphiti/query.rb +251 -0
- data/lib/graphiti/rails.rb +28 -0
- data/lib/graphiti/railtie.rb +74 -0
- data/lib/graphiti/renderer.rb +60 -0
- data/lib/graphiti/resource.rb +110 -0
- data/lib/graphiti/resource/configuration.rb +239 -0
- data/lib/graphiti/resource/dsl.rb +138 -0
- data/lib/graphiti/resource/interface.rb +32 -0
- data/lib/graphiti/resource/polymorphism.rb +68 -0
- data/lib/graphiti/resource/sideloading.rb +102 -0
- data/lib/graphiti/resource_proxy.rb +127 -0
- data/lib/graphiti/responders.rb +19 -0
- data/lib/graphiti/runner.rb +25 -0
- data/lib/graphiti/scope.rb +98 -0
- data/lib/graphiti/scoping/base.rb +99 -0
- data/lib/graphiti/scoping/default_filter.rb +58 -0
- data/lib/graphiti/scoping/extra_attributes.rb +29 -0
- data/lib/graphiti/scoping/filter.rb +93 -0
- data/lib/graphiti/scoping/filterable.rb +36 -0
- data/lib/graphiti/scoping/paginate.rb +87 -0
- data/lib/graphiti/scoping/sort.rb +64 -0
- data/lib/graphiti/sideload.rb +281 -0
- data/lib/graphiti/sideload/belongs_to.rb +34 -0
- data/lib/graphiti/sideload/has_many.rb +16 -0
- data/lib/graphiti/sideload/has_one.rb +9 -0
- data/lib/graphiti/sideload/many_to_many.rb +24 -0
- data/lib/graphiti/sideload/polymorphic_belongs_to.rb +108 -0
- data/lib/graphiti/stats/dsl.rb +89 -0
- data/lib/graphiti/stats/payload.rb +49 -0
- data/lib/graphiti/types.rb +172 -0
- data/lib/graphiti/util/attribute_check.rb +88 -0
- data/lib/graphiti/util/field_params.rb +16 -0
- data/lib/graphiti/util/hash.rb +51 -0
- data/lib/graphiti/util/hooks.rb +33 -0
- data/lib/graphiti/util/include_params.rb +39 -0
- data/lib/graphiti/util/persistence.rb +219 -0
- data/lib/graphiti/util/relationship_payload.rb +64 -0
- data/lib/graphiti/util/serializer_attributes.rb +97 -0
- data/lib/graphiti/util/sideload.rb +33 -0
- data/lib/graphiti/util/validation_response.rb +78 -0
- data/lib/graphiti/version.rb +3 -0
- metadata +317 -0
@@ -0,0 +1,33 @@
|
|
1
|
+
module Graphiti
|
2
|
+
module Extensions
|
3
|
+
# Turns ruby ? methods into is_ attributes
|
4
|
+
#
|
5
|
+
# @example Basic Usage
|
6
|
+
# boolean_attribute :active?
|
7
|
+
#
|
8
|
+
# # equivalent do
|
9
|
+
# def is_active
|
10
|
+
# @object.active?
|
11
|
+
# end
|
12
|
+
module BooleanAttribute
|
13
|
+
def self.included(klass)
|
14
|
+
klass.extend ClassMethods
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
# Register a boolean attribute
|
19
|
+
# @param name the corresponding ? method
|
20
|
+
# @param [Hash] options Normal .attribute options
|
21
|
+
def boolean_attribute(name, options = {}, &blk)
|
22
|
+
blk ||= proc { @object.public_send(name) }
|
23
|
+
field_name = :"is_#{name.to_s.gsub('?', '')}"
|
24
|
+
attribute field_name, options, &blk
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
JSONAPI::Serializable::Resource.class_eval do
|
32
|
+
include Graphiti::Extensions::BooleanAttribute
|
33
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'jsonapi/serializable/resource/conditional_fields'
|
2
|
+
|
3
|
+
module Graphiti
|
4
|
+
module Extensions
|
5
|
+
# Only render a given attribute when the user specifically requests it.
|
6
|
+
# Useful for computationally-expensive attributes that are not required
|
7
|
+
# on every request.
|
8
|
+
#
|
9
|
+
# This class handles the serialization, but you may also want to run
|
10
|
+
# code during scoping (for instance, to eager load associations referenced
|
11
|
+
# by this extra attribute. See (Resource.extra_field).
|
12
|
+
#
|
13
|
+
# @example Basic Usage
|
14
|
+
# # Will only be rendered on user request, ie
|
15
|
+
# # /people?extra_fields[people]=net_worth
|
16
|
+
# extra_attribute :net_worth
|
17
|
+
#
|
18
|
+
# @example Eager Loading
|
19
|
+
# class PersonResource < ApplicationResource
|
20
|
+
# # If the user requests the 'net_worth' attribute, make sure
|
21
|
+
# # 'assets' are eager loaded
|
22
|
+
# extra_field :net_worth do |scope|
|
23
|
+
# scope.includes(:assets)
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# class SerializablePerson < JSONAPI::Serializable::Resource
|
28
|
+
# # ... code ...
|
29
|
+
# extra_attribute :net_worth do
|
30
|
+
# @object.assets.sum(&:value)
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# @see Resource.extra_field
|
35
|
+
module ExtraAttribute
|
36
|
+
def self.included(klass)
|
37
|
+
klass.extend ClassMethods
|
38
|
+
end
|
39
|
+
|
40
|
+
module ClassMethods
|
41
|
+
# @param [Symbol] name the name of the attribute
|
42
|
+
# @param [Hash] options the options passed on to vanilla to .attribute
|
43
|
+
def extra_attribute(name, options = {}, &blk)
|
44
|
+
allow_field = proc {
|
45
|
+
if options[:if]
|
46
|
+
next false unless instance_eval(&options[:if])
|
47
|
+
end
|
48
|
+
|
49
|
+
@extra_fields &&
|
50
|
+
@extra_fields[@_type] &&
|
51
|
+
@extra_fields[@_type].include?(name)
|
52
|
+
}
|
53
|
+
|
54
|
+
attribute name, if: allow_field, &blk
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
JSONAPI::Serializable::Resource.class_eval do
|
62
|
+
def self.inherited(klass)
|
63
|
+
super
|
64
|
+
klass.class_eval do
|
65
|
+
extend JSONAPI::Serializable::Resource::ConditionalFields
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
include Graphiti::Extensions::ExtraAttribute
|
70
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Graphiti
|
2
|
+
# If the object we are serializing has the instance variable
|
3
|
+
# +@_jsonapi_temp_id+, render +temp-id+ in the {http://jsonapi.org/format/#document-resource-identifier-objects resource identifier}
|
4
|
+
#
|
5
|
+
# Why? Well, when the request is a nested POST, creating the main entity as
|
6
|
+
# well as relationships, we need some way of telling the client, "hey, the
|
7
|
+
# object you have in memory, that you just sent to the server, has been
|
8
|
+
# persisted and now has id X".
|
9
|
+
#
|
10
|
+
# +@_jsonapi_temp_id+ is set within this library. You should never have to
|
11
|
+
# reference it directly.
|
12
|
+
module SerializableTempId
|
13
|
+
# Common interface for jsonapi-rb extensions
|
14
|
+
def as_jsonapi(*)
|
15
|
+
super.tap do |hash|
|
16
|
+
if temp_id = @object.instance_variable_get(:'@_jsonapi_temp_id')
|
17
|
+
hash[:'temp-id'] = temp_id
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
JSONAPI::Serializable::Resource.class_eval do
|
25
|
+
prepend Graphiti::SerializableTempId
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Graphiti
|
2
|
+
class FilterOperators
|
3
|
+
class Catchall
|
4
|
+
attr_reader :procs
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@procs = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def method_missing(name, *args, &blk)
|
11
|
+
@procs[name] = blk
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_hash
|
15
|
+
@procs
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.build(&blk)
|
20
|
+
c = Catchall.new
|
21
|
+
c.instance_eval(&blk) if blk
|
22
|
+
c.to_hash
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Graphiti
|
2
|
+
module SerializableHash
|
3
|
+
def to_hash(fields: nil, include: {})
|
4
|
+
{}.tap do |hash|
|
5
|
+
_fields = fields[jsonapi_type] if fields
|
6
|
+
attrs = requested_attributes(_fields).each_with_object({}) do |(k, v), h|
|
7
|
+
h[k] = instance_eval(&v)
|
8
|
+
end
|
9
|
+
rels = @_relationships.select { |k,v| !!include[k] }
|
10
|
+
rels.each_with_object({}) do |(k, v), h|
|
11
|
+
serializers = v.send(:resources)
|
12
|
+
attrs[k] = if serializers.is_a?(Array)
|
13
|
+
serializers.map do |rr| # use private method to avoid array casting
|
14
|
+
rr.to_hash(fields: fields, include: include[k])
|
15
|
+
end
|
16
|
+
elsif serializers.nil?
|
17
|
+
nil
|
18
|
+
else
|
19
|
+
serializers.to_hash(fields: fields, include: include[k])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
hash[:id] = jsonapi_id
|
24
|
+
hash.merge!(attrs) if attrs.any?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
JSONAPI::Serializable::Resource.send(:include, SerializableHash)
|
29
|
+
|
30
|
+
class HashRenderer
|
31
|
+
def initialize(resource)
|
32
|
+
@resource = resource
|
33
|
+
end
|
34
|
+
|
35
|
+
def render(options)
|
36
|
+
serializers = options[:data]
|
37
|
+
opts = options.slice(:fields, :include)
|
38
|
+
to_hash(serializers, opts).tap do |hash|
|
39
|
+
hash.merge!(options.slice(:meta)) if !options[:meta].empty?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def to_hash(serializers, opts)
|
46
|
+
{}.tap do |hash|
|
47
|
+
if serializers.is_a?(Array)
|
48
|
+
hash[@resource.type] = serializers.map do |s|
|
49
|
+
s.to_hash(opts)
|
50
|
+
end
|
51
|
+
else
|
52
|
+
hash[@resource.type] = serializers.to_hash(opts)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Graphiti
|
2
|
+
module JsonapiSerializableExt
|
3
|
+
# This library looks up a serializer based on the record's class name
|
4
|
+
# This wouldn't work for us, since a model may be associated with
|
5
|
+
# multiple resources.
|
6
|
+
# Instead, this variable is assigned when the query is resolved
|
7
|
+
# To ensure we always render with the *resource* serializer
|
8
|
+
module RendererOverrides
|
9
|
+
def _build(object, exposures, klass)
|
10
|
+
klass = object.instance_variable_get(:@__serializer_klass)
|
11
|
+
klass.new(exposures.merge(object: object))
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# See above comment
|
16
|
+
module RelationshipOverrides
|
17
|
+
def data
|
18
|
+
@_resources_block = proc do
|
19
|
+
resources = yield
|
20
|
+
if resources.nil?
|
21
|
+
nil
|
22
|
+
elsif resources.respond_to?(:to_ary)
|
23
|
+
Array(resources).map do |obj|
|
24
|
+
klass = obj.instance_variable_get(:@__serializer_klass)
|
25
|
+
klass.new(@_exposures.merge(object: obj))
|
26
|
+
end
|
27
|
+
else
|
28
|
+
klass = resources.instance_variable_get(:@__serializer_klass)
|
29
|
+
klass.new(@_exposures.merge(object: resources))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Temporary fix until fixed upstream
|
36
|
+
# https://github.com/jsonapi-rb/jsonapi-serializable/pull/102
|
37
|
+
module ResourceOverrides
|
38
|
+
def requested_relationships(fields)
|
39
|
+
@_relationships
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
JSONAPI::Serializable::Resource
|
44
|
+
.send(:prepend, ResourceOverrides)
|
45
|
+
JSONAPI::Serializable::Relationship
|
46
|
+
.send(:prepend, RelationshipOverrides)
|
47
|
+
JSONAPI::Serializable::Renderer
|
48
|
+
.send(:prepend, RendererOverrides)
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,251 @@
|
|
1
|
+
module Graphiti
|
2
|
+
class Query
|
3
|
+
attr_reader :resource, :include_hash, :association_name
|
4
|
+
|
5
|
+
def initialize(resource, params, association_name = nil, nested_include = nil, parents = [])
|
6
|
+
@resource = resource
|
7
|
+
@association_name = association_name
|
8
|
+
@params = params
|
9
|
+
@params = @params.permit! if @params.respond_to?(:permit!)
|
10
|
+
@params = @params.to_h if @params.respond_to?(:to_h)
|
11
|
+
@params = @params.deep_symbolize_keys
|
12
|
+
@include_param = nested_include || @params[:include]
|
13
|
+
@parents = parents
|
14
|
+
end
|
15
|
+
|
16
|
+
def association?
|
17
|
+
!!@association_name
|
18
|
+
end
|
19
|
+
|
20
|
+
def top_level?
|
21
|
+
not association?
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_hash
|
25
|
+
{}.tap do |hash|
|
26
|
+
hash[:filter] = filters unless filters.empty?
|
27
|
+
hash[:sort] = sorts unless sorts.empty?
|
28
|
+
hash[:page] = pagination unless pagination.empty?
|
29
|
+
unless association?
|
30
|
+
hash[:fields] = fields unless fields.empty?
|
31
|
+
hash[:extra_fields] = extra_fields unless extra_fields.empty?
|
32
|
+
end
|
33
|
+
hash[:stats] = stats unless stats.empty?
|
34
|
+
hash[:include] = sideload_hash unless sideload_hash.empty?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def zero_results?
|
39
|
+
!@params[:page].nil? &&
|
40
|
+
!@params[:page][:size].nil? &&
|
41
|
+
@params[:page][:size].to_i == 0
|
42
|
+
end
|
43
|
+
|
44
|
+
def sideload_hash
|
45
|
+
@sideload_hash = begin
|
46
|
+
{}.tap do |hash|
|
47
|
+
sideloads.each_pair do |key, value|
|
48
|
+
hash[key] = sideloads[key].to_hash
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def sideloads
|
55
|
+
@sideloads ||= begin
|
56
|
+
{}.tap do |hash|
|
57
|
+
include_hash.each_pair do |key, sub_hash|
|
58
|
+
sideload = @resource.class.sideload(key)
|
59
|
+
if sideload
|
60
|
+
_parents = parents + [self]
|
61
|
+
hash[key] = Query.new(sideload.resource, @params, key, sub_hash, _parents)
|
62
|
+
else
|
63
|
+
handle_missing_sideload(key)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def parents
|
71
|
+
@parents ||= []
|
72
|
+
end
|
73
|
+
|
74
|
+
def fields
|
75
|
+
@fields ||= begin
|
76
|
+
hash = parse_fieldset(@params[:fields] || {})
|
77
|
+
hash.each_pair do |type, fields|
|
78
|
+
hash[type] += extra_fields[type] if extra_fields[type]
|
79
|
+
end
|
80
|
+
hash
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def extra_fields
|
85
|
+
@extra_fields ||= parse_fieldset(@params[:extra_fields] || {})
|
86
|
+
end
|
87
|
+
|
88
|
+
def filters
|
89
|
+
@filters ||= begin
|
90
|
+
{}.tap do |hash|
|
91
|
+
(@params[:filter] || {}).each_pair do |name, value|
|
92
|
+
name = name.to_sym
|
93
|
+
|
94
|
+
if legacy_nested?(name)
|
95
|
+
filter_name = value.keys.first.to_sym
|
96
|
+
filter_value = value.values.first
|
97
|
+
if @resource.get_attr!(filter_name, :filterable, request: true)
|
98
|
+
hash[filter_name] = filter_value
|
99
|
+
end
|
100
|
+
elsif nested?(name)
|
101
|
+
name = name.to_s.split('.').last.to_sym
|
102
|
+
validate!(name, :filterable)
|
103
|
+
hash[name] = value
|
104
|
+
elsif top_level? && validate!(name, :filterable)
|
105
|
+
hash[name] = value
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def sorts
|
113
|
+
@sorts ||= begin
|
114
|
+
return @params[:sort] if @params[:sort].is_a?(Array)
|
115
|
+
return [] if @params[:sort].nil?
|
116
|
+
|
117
|
+
[].tap do |arr|
|
118
|
+
sort_hashes do |key, value, type|
|
119
|
+
if legacy_nested?(type)
|
120
|
+
@resource.get_attr!(key, :sortable, request: true)
|
121
|
+
arr << { key => value }
|
122
|
+
elsif !type && top_level? && validate!(key, :sortable)
|
123
|
+
arr << { key => value }
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def pagination
|
131
|
+
@pagination ||= begin
|
132
|
+
{}.tap do |hash|
|
133
|
+
(@params[:page] || {}).each_pair do |name, value|
|
134
|
+
if legacy_nested?(name)
|
135
|
+
value.each_pair do |k,v|
|
136
|
+
hash[k.to_sym] = v.to_i
|
137
|
+
end
|
138
|
+
elsif top_level? && [:number, :size].include?(name.to_sym)
|
139
|
+
hash[name.to_sym] = value.to_i
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def include_hash
|
147
|
+
@include_hash ||= begin
|
148
|
+
requested = include_directive.to_hash
|
149
|
+
|
150
|
+
whitelist = nil
|
151
|
+
if @resource.context && @resource.context.respond_to?(:sideload_whitelist)
|
152
|
+
whitelist = @resource.context.sideload_whitelist
|
153
|
+
whitelist = whitelist[@resource.context_namespace] if whitelist
|
154
|
+
end
|
155
|
+
|
156
|
+
whitelist ? Util::IncludeParams.scrub(requested, whitelist) : requested
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def stats
|
161
|
+
@stats ||= begin
|
162
|
+
{}.tap do |hash|
|
163
|
+
(@params[:stats] || {}).each_pair do |k, v|
|
164
|
+
if legacy_nested?(k)
|
165
|
+
raise NotImplementedError.new('Association statistics are not currently supported')
|
166
|
+
elsif top_level?
|
167
|
+
v = v.split(',') if v.is_a?(String)
|
168
|
+
hash[k.to_sym] = Array(v).flatten.map(&:to_sym)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
def validate!(name, flag)
|
178
|
+
return false if name.to_s.include?('.') # nested
|
179
|
+
|
180
|
+
not_associated_name = !@resource.class.association_names.include?(name)
|
181
|
+
not_associated_type = !@resource.class.association_types.include?(name)
|
182
|
+
|
183
|
+
if not_associated_name && not_associated_type
|
184
|
+
@resource.get_attr!(name, flag, request: true)
|
185
|
+
return true
|
186
|
+
end
|
187
|
+
false
|
188
|
+
end
|
189
|
+
|
190
|
+
def nested?(name)
|
191
|
+
return false unless association?
|
192
|
+
split = name.to_s.split('.')
|
193
|
+
query_names = split[0..split.length-2].map(&:to_sym)
|
194
|
+
my_names = parents.map(&:association_name).compact + [association_name].compact
|
195
|
+
query_names == my_names
|
196
|
+
end
|
197
|
+
|
198
|
+
def legacy_nested?(name)
|
199
|
+
association? &&
|
200
|
+
(name == @resource.type || name == @association_name)
|
201
|
+
end
|
202
|
+
|
203
|
+
def parse_fieldset(fieldset)
|
204
|
+
{}.tap do |hash|
|
205
|
+
fieldset.each_pair do |type, fields|
|
206
|
+
type = type.to_sym
|
207
|
+
fields = fields.split(',') unless fields.is_a?(Array)
|
208
|
+
hash[type] = fields.map(&:to_sym)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def include_directive
|
214
|
+
@include_directive ||= JSONAPI::IncludeDirective.new(@include_param)
|
215
|
+
end
|
216
|
+
|
217
|
+
def handle_missing_sideload(name)
|
218
|
+
if Graphiti.config.raise_on_missing_sideload
|
219
|
+
raise Graphiti::Errors::InvalidInclude
|
220
|
+
.new(name, @resource.type)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def sort_hash(attr)
|
225
|
+
value = attr[0] == '-' ? :desc : :asc
|
226
|
+
key = attr.sub('-', '').to_sym
|
227
|
+
|
228
|
+
{ key => value }
|
229
|
+
end
|
230
|
+
|
231
|
+
def sort_hashes
|
232
|
+
sorts = @params[:sort].split(',')
|
233
|
+
sorts.each do |s|
|
234
|
+
type, attr = s.split('.')
|
235
|
+
|
236
|
+
if attr.nil? # top-level
|
237
|
+
next if @association_name
|
238
|
+
hash = sort_hash(type)
|
239
|
+
yield hash.keys.first.to_sym, hash.values.first
|
240
|
+
else
|
241
|
+
if type[0] == '-'
|
242
|
+
type = type.sub('-', '')
|
243
|
+
attr = "-#{attr}"
|
244
|
+
end
|
245
|
+
hash = sort_hash(attr)
|
246
|
+
yield hash.keys.first.to_sym, hash.values.first, type.to_sym
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|