graphiti-rb 1.0.alpha.1
Sign up to get free protection for your applications and to get access to all the features.
- 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,87 @@
|
|
1
|
+
module Graphiti
|
2
|
+
# Apply pagination logic to the scope
|
3
|
+
#
|
4
|
+
# If the user requests a page size greater than +MAX_PAGE_SIZE+,
|
5
|
+
# a +Graphiti::Errors::UnsupportedPageSize+ error will be raised.
|
6
|
+
#
|
7
|
+
# Notably, this will not fire when the `default: false` option is passed.
|
8
|
+
# This is the case for sideloads - if the user requests "give me the post
|
9
|
+
# and its comments", we shouldn't implicitly limit those comments to 20.
|
10
|
+
# *BUT* if the user requests, "give me the post and 3 of its comments", we
|
11
|
+
# *should* honor that pagination.
|
12
|
+
#
|
13
|
+
# This can be confusing because there are also 'default' and 'customized'
|
14
|
+
# pagination procs. The default comes 'for free'. Customized pagination
|
15
|
+
# looks like
|
16
|
+
#
|
17
|
+
# class PostResource < ApplicationResource
|
18
|
+
# paginate do |scope, current_page, per_page|
|
19
|
+
# # ... the custom logic ...
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# We should use the default unless the user has customized.
|
24
|
+
# @see Resource.paginate
|
25
|
+
class Scoping::Paginate < Scoping::Base
|
26
|
+
MAX_PAGE_SIZE = 1_000
|
27
|
+
|
28
|
+
# Apply the pagination logic. Raise error if over the max page size.
|
29
|
+
# @return the scope object we are chaining/modifying
|
30
|
+
def apply
|
31
|
+
if size > MAX_PAGE_SIZE
|
32
|
+
raise Graphiti::Errors::UnsupportedPageSize
|
33
|
+
.new(size, MAX_PAGE_SIZE)
|
34
|
+
elsif requested? && @opts[:sideload_parent_length].to_i > 1
|
35
|
+
raise Graphiti::Errors::UnsupportedPagination
|
36
|
+
else
|
37
|
+
super
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# We want to apply this logic unless we've explicitly received the
|
42
|
+
# +default: false+ option. In that case, only apply if pagination
|
43
|
+
# was explicitly specified in the request.
|
44
|
+
#
|
45
|
+
# @return [Boolean] should we apply this logic?
|
46
|
+
def apply?
|
47
|
+
if @opts[:default_paginate] == false
|
48
|
+
requested?
|
49
|
+
else
|
50
|
+
true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# @return [Proc, Nil] the custom pagination proc
|
55
|
+
def custom_scope
|
56
|
+
resource.pagination
|
57
|
+
end
|
58
|
+
|
59
|
+
# Apply default pagination proc via the Resource adapter
|
60
|
+
def apply_standard_scope
|
61
|
+
resource.adapter.paginate(@scope, number, size)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Apply the custom pagination proc
|
65
|
+
def apply_custom_scope
|
66
|
+
custom_scope.call(@scope, number, size, resource.context)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def requested?
|
72
|
+
not [page_param[:size], page_param[:number]].all?(&:nil?)
|
73
|
+
end
|
74
|
+
|
75
|
+
def page_param
|
76
|
+
@page_param ||= (query_hash[:page] || {})
|
77
|
+
end
|
78
|
+
|
79
|
+
def number
|
80
|
+
(page_param[:number] || 1).to_i
|
81
|
+
end
|
82
|
+
|
83
|
+
def size
|
84
|
+
(page_param[:size] || resource.default_page_size).to_i
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Graphiti
|
2
|
+
# Apply sorting logic to the scope.
|
3
|
+
#
|
4
|
+
# By default, sorting comes 'for free'. To specify a custom sorting proc:
|
5
|
+
#
|
6
|
+
# class PostResource < ApplicationResource
|
7
|
+
# sort do |scope, att, dir|
|
8
|
+
# int = dir == :desc ? -1 : 1
|
9
|
+
# scope.sort_by { |x| x[att] * int }
|
10
|
+
# end
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# The sorting proc will be called once for each sort att/dir requested.
|
14
|
+
# @see Resource.sort
|
15
|
+
class Scoping::Sort < Scoping::Base
|
16
|
+
# @return [Proc, Nil] The custom proc specified by Resource DSL
|
17
|
+
def custom_scope
|
18
|
+
resource.sort_all
|
19
|
+
end
|
20
|
+
|
21
|
+
# Apply default scope logic via Resource adapter
|
22
|
+
# @return the scope we are chaining/modifying
|
23
|
+
def apply_standard_scope
|
24
|
+
each_sort do |attribute, direction|
|
25
|
+
if sort = resource.sorts[attribute]
|
26
|
+
@scope = sort.call(@scope, direction)
|
27
|
+
else
|
28
|
+
@scope = resource.adapter.order(@scope, attribute, direction)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
@scope
|
32
|
+
end
|
33
|
+
|
34
|
+
# Apply custom scoping proc configured via Resource DSL
|
35
|
+
# @return the scope we are chaining/modifying
|
36
|
+
def apply_custom_scope
|
37
|
+
each_sort do |attribute, direction|
|
38
|
+
@scope = custom_scope
|
39
|
+
.call(@scope, attribute, direction, resource.context)
|
40
|
+
end
|
41
|
+
@scope
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def each_sort
|
47
|
+
sort_param.each do |sort_hash|
|
48
|
+
attribute = sort_hash.keys.first
|
49
|
+
direction = sort_hash.values.first
|
50
|
+
yield attribute, direction
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def sort_param
|
55
|
+
@sort_param ||= begin
|
56
|
+
if query_hash[:sort].blank?
|
57
|
+
resource.default_sort
|
58
|
+
else
|
59
|
+
query_hash[:sort]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,281 @@
|
|
1
|
+
module Graphiti
|
2
|
+
class Sideload
|
3
|
+
HOOK_ACTIONS = [:save, :create, :update, :destroy, :disassociate]
|
4
|
+
TYPES = [:has_many, :belongs_to, :has_one, :many_to_many]
|
5
|
+
|
6
|
+
attr_reader :name,
|
7
|
+
:resource_class,
|
8
|
+
:parent_resource_class,
|
9
|
+
:foreign_key,
|
10
|
+
:primary_key,
|
11
|
+
:parent,
|
12
|
+
:group_name
|
13
|
+
|
14
|
+
class_attribute :scope_proc,
|
15
|
+
:assign_proc,
|
16
|
+
:assign_each_proc,
|
17
|
+
:params_proc,
|
18
|
+
:pre_load_proc
|
19
|
+
|
20
|
+
def initialize(name, opts)
|
21
|
+
@name = name
|
22
|
+
@parent_resource_class = opts[:parent_resource]
|
23
|
+
@resource_class = opts[:resource]
|
24
|
+
@primary_key = opts[:primary_key]
|
25
|
+
@foreign_key = opts[:foreign_key]
|
26
|
+
@type = opts[:type]
|
27
|
+
@base_scope = opts[:base_scope]
|
28
|
+
@readable = opts[:readable]
|
29
|
+
@writable = opts[:writable]
|
30
|
+
@as = opts[:as]
|
31
|
+
|
32
|
+
# polymorphic-specific
|
33
|
+
@group_name = opts[:group_name]
|
34
|
+
@polymorphic_child = opts[:polymorphic_child]
|
35
|
+
@parent = opts[:parent]
|
36
|
+
if polymorphic_child?
|
37
|
+
parent.resource.polymorphic << resource_class
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.scope(&blk)
|
42
|
+
self.scope_proc = blk
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.assign(&blk)
|
46
|
+
self.assign_proc = blk
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.assign_each(&blk)
|
50
|
+
self.assign_each_proc = blk
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.params(&blk)
|
54
|
+
self.params_proc = blk
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.pre_load(&blk)
|
58
|
+
self.pre_load_proc = blk
|
59
|
+
end
|
60
|
+
|
61
|
+
def readable?
|
62
|
+
!!@readable
|
63
|
+
end
|
64
|
+
|
65
|
+
def writable?
|
66
|
+
!!@writable
|
67
|
+
end
|
68
|
+
|
69
|
+
def polymorphic_parent?
|
70
|
+
resource.polymorphic?
|
71
|
+
end
|
72
|
+
|
73
|
+
def polymorphic_child?
|
74
|
+
!!@polymorphic_child
|
75
|
+
end
|
76
|
+
|
77
|
+
def primary_key
|
78
|
+
@primary_key ||= :id
|
79
|
+
end
|
80
|
+
|
81
|
+
def foreign_key
|
82
|
+
@foreign_key ||= infer_foreign_key
|
83
|
+
end
|
84
|
+
|
85
|
+
def association_name
|
86
|
+
@as || name
|
87
|
+
end
|
88
|
+
|
89
|
+
def resource_class
|
90
|
+
@resource_class ||= infer_resource_class
|
91
|
+
end
|
92
|
+
|
93
|
+
def scope(parents)
|
94
|
+
raise "No #scope defined for sideload with name '#{name}'. Make sure to define this in your adapter, or pass a block that defines the scope."
|
95
|
+
end
|
96
|
+
|
97
|
+
def assign_each(parent, children)
|
98
|
+
raise 'Override #assign_each in subclass'
|
99
|
+
end
|
100
|
+
|
101
|
+
def type
|
102
|
+
@type || raise("Override #type in subclass. Should be one of #{TYPES.inspect}")
|
103
|
+
end
|
104
|
+
|
105
|
+
def load_params(parents, query)
|
106
|
+
raise 'Override #load_params in subclass'
|
107
|
+
end
|
108
|
+
|
109
|
+
def base_scope
|
110
|
+
if @base_scope
|
111
|
+
@base_scope.respond_to?(:call) ? @base_scope.call : @base_scope
|
112
|
+
else
|
113
|
+
resource.base_scope
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def load(parents, query)
|
118
|
+
params = load_params(parents, query)
|
119
|
+
params_proc.call(params, parents, query) if params_proc
|
120
|
+
opts = load_options(parents, query)
|
121
|
+
proxy = resource.class._all(params, opts, base_scope)
|
122
|
+
pre_load_proc.call(proxy) if pre_load_proc
|
123
|
+
proxy.to_a
|
124
|
+
end
|
125
|
+
|
126
|
+
# Override in subclass
|
127
|
+
def infer_foreign_key
|
128
|
+
model = parent_resource_class.model
|
129
|
+
namespace = namespace_for(model)
|
130
|
+
model_name = model.name.gsub("#{namespace}::", '')
|
131
|
+
:"#{model_name.underscore}_id"
|
132
|
+
end
|
133
|
+
|
134
|
+
def resource
|
135
|
+
@resource ||= resource_class.new
|
136
|
+
end
|
137
|
+
|
138
|
+
def parent_resource
|
139
|
+
@parent_resource ||= parent_resource_class.new
|
140
|
+
end
|
141
|
+
|
142
|
+
def assign(parents, children)
|
143
|
+
associated = []
|
144
|
+
parents.each do |parent|
|
145
|
+
relevant_children = fire_assign_each(parent, children)
|
146
|
+
if relevant_children.is_a?(Array)
|
147
|
+
associated |= relevant_children
|
148
|
+
associate_all(parent, relevant_children)
|
149
|
+
else
|
150
|
+
associated << relevant_children
|
151
|
+
associate(parent, relevant_children)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
(children - associated).each do |unassigned|
|
155
|
+
children.delete(unassigned)
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
def resolve(parents, query)
|
160
|
+
# legacy / custom / many-to-many
|
161
|
+
if self.class.scope_proc || type == :many_to_many
|
162
|
+
sideload_scope = fire_scope(parents)
|
163
|
+
sideload_scope = Scope.new sideload_scope,
|
164
|
+
resource,
|
165
|
+
query,
|
166
|
+
sideload_parent_length: parents.length,
|
167
|
+
default_paginate: false
|
168
|
+
sideload_scope.resolve do |sideload_results|
|
169
|
+
fire_assign(parents, sideload_results)
|
170
|
+
end
|
171
|
+
else
|
172
|
+
load(parents, query)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def self.after_save(only: [], except: [], &blk)
|
177
|
+
actions = HOOK_ACTIONS - except
|
178
|
+
actions = only & actions
|
179
|
+
actions = [:save] if only.empty? && except.empty?
|
180
|
+
actions.each do |a|
|
181
|
+
hooks[:"after_#{a}"] << blk
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def self.hooks
|
186
|
+
@hooks ||= {}.tap do |h|
|
187
|
+
HOOK_ACTIONS.each do |a|
|
188
|
+
h[:"after_#{a}"] = []
|
189
|
+
h[:"before_#{a}"] = []
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def fire_hooks!(parent, objects, method)
|
195
|
+
return unless self.class.hooks
|
196
|
+
|
197
|
+
all = self.class.hooks[:"after_#{method}"] + self.class.hooks[:after_save]
|
198
|
+
all.compact.each do |hook|
|
199
|
+
resource.instance_exec(parent, objects, &hook)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def associate_all(parent, children)
|
204
|
+
parent_resource.associate_all(parent, children, association_name, type)
|
205
|
+
end
|
206
|
+
|
207
|
+
def associate(parent, child)
|
208
|
+
parent_resource.associate(parent, child, association_name, type)
|
209
|
+
end
|
210
|
+
|
211
|
+
def disassociate(parent, child)
|
212
|
+
parent_resource.disassociate(parent, child, association_name, type)
|
213
|
+
end
|
214
|
+
|
215
|
+
def ids_for_parents(parents)
|
216
|
+
parent_ids = parents.map(&primary_key)
|
217
|
+
parent_ids.compact!
|
218
|
+
parent_ids.uniq!
|
219
|
+
parent_ids
|
220
|
+
end
|
221
|
+
|
222
|
+
private
|
223
|
+
|
224
|
+
def load_options(parents, query)
|
225
|
+
{}.tap do |opts|
|
226
|
+
opts[:default_paginate] = false
|
227
|
+
opts[:sideload_parent_length] = parents.length
|
228
|
+
opts[:after_resolve] = ->(results) {
|
229
|
+
fire_assign(parents, results)
|
230
|
+
}
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def fire_assign_each(parent, children)
|
235
|
+
if self.class.assign_each_proc
|
236
|
+
instance_exec(parent, children, &self.class.assign_each_proc)
|
237
|
+
else
|
238
|
+
assign_each(parent, children)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def fire_assign(parents, children)
|
243
|
+
if self.class.assign_proc
|
244
|
+
instance_exec(parents, children, &self.class.assign_proc)
|
245
|
+
else
|
246
|
+
assign(parents, children)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def fire_scope(parents)
|
251
|
+
parent_ids = ids_for_parents(parents)
|
252
|
+
if self.class.scope_proc
|
253
|
+
instance_exec(parent_ids, parents, &self.class.scope_proc)
|
254
|
+
else
|
255
|
+
method = method(:scope)
|
256
|
+
if [2,-2].include?(method.arity)
|
257
|
+
scope(parent_ids, parents)
|
258
|
+
else
|
259
|
+
scope(parent_ids)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
def namespace_for(klass)
|
265
|
+
namespace = klass.name
|
266
|
+
split = namespace.split('::')
|
267
|
+
split[0,split.length-1].join('::')
|
268
|
+
end
|
269
|
+
|
270
|
+
def infer_resource_class
|
271
|
+
namespace = namespace_for(parent_resource.class)
|
272
|
+
inferred_name = "#{name.to_s.singularize.classify}Resource"
|
273
|
+
klass = "#{namespace}::#{inferred_name}".safe_constantize
|
274
|
+
klass ||= inferred_name.safe_constantize
|
275
|
+
unless klass
|
276
|
+
raise Errors::ResourceNotFound.new(parent_resource, name)
|
277
|
+
end
|
278
|
+
klass
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Graphiti::Sideload::BelongsTo < Graphiti::Sideload
|
2
|
+
def type
|
3
|
+
:belongs_to
|
4
|
+
end
|
5
|
+
|
6
|
+
def load_params(parents, query)
|
7
|
+
query.to_hash.tap do |hash|
|
8
|
+
hash[:filter] ||= {}
|
9
|
+
hash[:filter][primary_key] = ids_for_parents(parents)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def assign_each(parent, children)
|
14
|
+
children.find { |c| c.send(primary_key) == parent.send(foreign_key) }
|
15
|
+
end
|
16
|
+
|
17
|
+
def ids_for_parents(parents)
|
18
|
+
parent_ids = parents.map(&foreign_key)
|
19
|
+
parent_ids.compact!
|
20
|
+
parent_ids.uniq!
|
21
|
+
parent_ids
|
22
|
+
end
|
23
|
+
|
24
|
+
def infer_foreign_key
|
25
|
+
if polymorphic_child?
|
26
|
+
parent.foreign_key
|
27
|
+
else
|
28
|
+
model = resource.model
|
29
|
+
namespace = namespace_for(model)
|
30
|
+
model_name = model.name.gsub("#{namespace}::", '')
|
31
|
+
:"#{model_name.underscore}_id"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|