jsonapi-resources 0.10.0.beta1 → 0.10.0.beta2

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: 1da47a9966931af4ff9a5bab22e41097bf9075bf
4
- data.tar.gz: 7ed9248ebb089d4c3f76b03d9e1eb47c8810be75
3
+ metadata.gz: f0435c60c6dca20674f60da2681d58407de97f96
4
+ data.tar.gz: 9bad26baa66aaf9a6c5d3aa9f7e3797229826b67
5
5
  SHA512:
6
- metadata.gz: 7f8a2a940460ca620cf2495856523c2cab00e72ee169ecc9c7bb84cb3499d96e4fe9a39b39f439b116bef24417e766cd93f8b2bf2be98e397cfe4994806abcc8
7
- data.tar.gz: c54161c677a2b7dcf1db569bf1c05685ce8d5b84349157f7ab6f3b927c0a357322b57f762b2feeb8225ca8f9e04aeb63aa8313467d77e1f0e1d7d44616ff3357
6
+ metadata.gz: 97daeb8e0a39bac96c4c840e86fedd7a576a1877537a855c20b21ea29d3b29001fcf2c3e4b8e8f7ce3fbf544d84a6a54bd7742fd7a7c49f4a18579d1cc639645
7
+ data.tar.gz: 10983a6d90cd6e83b63a65e8a9fae0a1fc010b9dae266d1365ee11c8494e89bce1f32f18dce2151b4d2b3657d3cb41202d2556464e3d0ba651f933158c40e225
@@ -30,3 +30,5 @@ require 'jsonapi/resource_identity'
30
30
  require 'jsonapi/resource_fragment'
31
31
  require 'jsonapi/resource_id_tree'
32
32
  require 'jsonapi/resource_set'
33
+ require 'jsonapi/path'
34
+ require 'jsonapi/path_segment'
@@ -1,3 +1,5 @@
1
+ require 'jsonapi/active_relation_resource_finder/adapters/join_left_active_record_adapter'
2
+
1
3
  module JSONAPI
2
4
  module ActiveRelationResourceFinder
3
5
  def self.included(base)
@@ -15,7 +17,21 @@ module JSONAPI
15
17
  #
16
18
  # @return [Array<Resource>] the Resource instances matching the filters, sorting and pagination rules.
17
19
  def find(filters, options = {})
18
- records = find_records(filters, options)
20
+ sort_criteria = options.fetch(:sort_criteria) { [] }
21
+
22
+ join_tree = JoinTree.new(resource_klass: self,
23
+ options: options,
24
+ filters: filters,
25
+ sort_criteria: sort_criteria)
26
+
27
+ paginator = options[:paginator]
28
+
29
+ records = find_records(records: records(options),
30
+ filters: filters,
31
+ join_tree: join_tree,
32
+ paginator: paginator,
33
+ options: options)
34
+
19
35
  resources_for(records, options[:context])
20
36
  end
21
37
 
@@ -26,7 +42,16 @@ module JSONAPI
26
42
  #
27
43
  # @return [Integer] the count
28
44
  def count(filters, options = {})
29
- count_records(filter_records(records(options), filters, options))
45
+ join_tree = JoinTree.new(resource_klass: self,
46
+ options: options,
47
+ filters: filters)
48
+
49
+ records = find_records(records: records(options),
50
+ filters: filters,
51
+ join_tree: join_tree,
52
+ options: options)
53
+
54
+ count_records(records)
30
55
  end
31
56
 
32
57
  # Returns the single Resource identified by `key`
@@ -49,6 +74,7 @@ module JSONAPI
49
74
 
50
75
  # Finds Resource fragments using the `filters`. Pagination and sort options are used when provided.
51
76
  # Retrieving the ResourceIdentities and attributes does not instantiate a model instance.
77
+ # Note: This is incompatible with Polymorphic resources (which are going to come from two separate tables)
52
78
  #
53
79
  # @param filters [Hash] the filters hash
54
80
  # @option options [Hash] :context The context of the request, set in the controller
@@ -61,27 +87,76 @@ module JSONAPI
61
87
  # the ResourceInstances matching the filters, sorting, and pagination rules along with any request
62
88
  # additional_field values
63
89
  def find_fragments(filters, options = {})
64
- records = find_records(filters, options)
90
+ include_directives = options[:include_directives] ? options[:include_directives].include_directives : {}
91
+ resource_klass = self
92
+ linkage_relationships = to_one_relationships_for_linkage(include_directives[:include_related])
65
93
 
66
- table_name = _model_class.table_name
67
- pluck_fields = [Arel.sql("#{concat_table_field(table_name, _primary_key)} AS #{table_name}_#{_primary_key}")]
94
+ sort_criteria = options.fetch(:sort_criteria) { [] }
95
+
96
+ join_tree = JoinTree.new(resource_klass: resource_klass,
97
+ source_relationship: nil,
98
+ relationships: linkage_relationships,
99
+ sort_criteria: sort_criteria,
100
+ filters: filters,
101
+ options: options)
102
+
103
+ paginator = options[:paginator]
104
+
105
+ records = find_records(records: records(options),
106
+ filters: filters,
107
+ sort_criteria: sort_criteria,
108
+ paginator: paginator,
109
+ join_tree: join_tree,
110
+ options: options)
111
+
112
+ joins = join_tree.joins
113
+
114
+ # This alias is going to be resolve down to the model's table name and will not actually be an alias
115
+ resource_table_alias = joins[''][:alias]
116
+
117
+ pluck_fields = [Arel.sql("#{concat_table_field(resource_table_alias, resource_klass._primary_key)} AS #{resource_table_alias}_#{resource_klass._primary_key}")]
68
118
 
69
119
  cache_field = attribute_to_model_field(:_cache_field) if options[:cache]
70
120
  if cache_field
71
- pluck_fields << Arel.sql("#{concat_table_field(table_name, cache_field[:name])} AS #{table_name}_#{cache_field[:name]}")
121
+ pluck_fields << Arel.sql("#{concat_table_field(resource_table_alias, cache_field[:name])} AS #{resource_table_alias}_#{cache_field[:name]}")
122
+ end
123
+
124
+ linkage_fields = []
125
+
126
+ linkage_relationships.each do |name|
127
+ linkage_relationship = resource_klass._relationship(name)
128
+
129
+ if linkage_relationship.polymorphic? && linkage_relationship.belongs_to?
130
+ linkage_relationship.resource_types.each do |resource_type|
131
+ klass = resource_klass_for(resource_type)
132
+ linkage_fields << {relationship_name: name, resource_klass: klass}
133
+
134
+ linkage_table_alias = joins["#{linkage_relationship.name.to_s}##{resource_type}"][:alias]
135
+ primary_key = klass._primary_key
136
+ pluck_fields << Arel.sql("#{concat_table_field(linkage_table_alias, primary_key)} AS #{linkage_table_alias}_#{primary_key}")
137
+ end
138
+ else
139
+ klass = linkage_relationship.resource_klass
140
+ linkage_fields << {relationship_name: name, resource_klass: klass}
141
+
142
+ linkage_table_alias = joins[name.to_s][:alias]
143
+ primary_key = klass._primary_key
144
+ pluck_fields << Arel.sql("#{concat_table_field(linkage_table_alias, primary_key)} AS #{linkage_table_alias}_#{primary_key}")
145
+ end
72
146
  end
73
147
 
74
148
  model_fields = {}
75
149
  attributes = options[:attributes]
76
150
  attributes.try(:each) do |attribute|
77
- model_field = attribute_to_model_field(attribute)
151
+ model_field = resource_klass.attribute_to_model_field(attribute)
78
152
  model_fields[attribute] = model_field
79
- pluck_fields << Arel.sql("#{concat_table_field(table_name, model_field[:name])} AS #{table_name}_#{model_field[:name]}")
153
+ pluck_fields << Arel.sql("#{concat_table_field(resource_table_alias, model_field[:name])} AS #{resource_table_alias}_#{model_field[:name]}")
80
154
  end
81
155
 
82
156
  fragments = {}
83
- records.pluck(*pluck_fields).collect do |row|
84
- rid = JSONAPI::ResourceIdentity.new(self, pluck_fields.length == 1 ? row : row[0])
157
+ rows = records.pluck(*pluck_fields)
158
+ rows.collect do |row|
159
+ rid = JSONAPI::ResourceIdentity.new(resource_klass, pluck_fields.length == 1 ? row : row[0])
85
160
 
86
161
  fragments[rid] ||= JSONAPI::ResourceFragment.new(rid)
87
162
  attributes_offset = 1
@@ -91,6 +166,16 @@ module JSONAPI
91
166
  attributes_offset+= 1
92
167
  end
93
168
 
169
+ linkage_fields.each do |linkage_field_details|
170
+ fragments[rid].initialize_related(linkage_field_details[:relationship_name])
171
+ related_id = row[attributes_offset]
172
+ if related_id
173
+ related_rid = JSONAPI::ResourceIdentity.new(linkage_field_details[:resource_klass], related_id)
174
+ fragments[rid].add_related_identity(linkage_field_details[:relationship_name], related_rid)
175
+ end
176
+ attributes_offset+= 1
177
+ end
178
+
94
179
  model_fields.each_with_index do |k, idx|
95
180
  fragments[rid].attributes[k[0]]= cast_to_attribute_type(row[idx + attributes_offset], k[1][:type])
96
181
  end
@@ -113,17 +198,17 @@ module JSONAPI
113
198
  def find_related_fragments(source_rids, relationship_name, options = {})
114
199
  relationship = _relationship(relationship_name)
115
200
 
116
- if relationship.polymorphic? && relationship.foreign_key_on == :self
201
+ if relationship.polymorphic? # && relationship.foreign_key_on == :self
117
202
  find_related_polymorphic_fragments(source_rids, relationship, options, false)
118
203
  else
119
204
  find_related_monomorphic_fragments(source_rids, relationship, options, false)
120
205
  end
121
206
  end
122
207
 
123
- def find_included_fragments(source_rids, relationship_name, options = {})
208
+ def find_included_fragments(source_rids, relationship_name, options)
124
209
  relationship = _relationship(relationship_name)
125
210
 
126
- if relationship.polymorphic? && relationship.foreign_key_on == :self
211
+ if relationship.polymorphic? # && relationship.foreign_key_on == :self
127
212
  find_related_polymorphic_fragments(source_rids, relationship, options, true)
128
213
  else
129
214
  find_related_monomorphic_fragments(source_rids, relationship, options, true)
@@ -138,207 +223,221 @@ module JSONAPI
138
223
  #
139
224
  # @return [Integer] the count
140
225
  def count_related(source_rid, relationship_name, options = {})
141
- opts = options.dup
142
-
143
226
  relationship = _relationship(relationship_name)
144
227
  related_klass = relationship.resource_klass
145
228
 
146
- context = opts[:context]
147
-
148
- primary_key_field = "#{_table_name}.#{_primary_key}"
149
-
150
- records = records(context: context).where(primary_key_field => source_rid.id)
151
-
152
- # join in related to the source records
153
- records, related_alias = get_join_alias(records) { |records| records.joins(relationship.relation_name(opts)) }
229
+ filters = options.fetch(:filters, {})
154
230
 
155
- join_tree = JoinTree.new(resource_klass: related_klass,
231
+ # Joins in this case are related to the related_klass
232
+ join_tree = JoinTree.new(resource_klass: self,
156
233
  source_relationship: relationship,
157
234
  filters: filters,
158
- options: opts)
235
+ options: options)
159
236
 
160
- records, joins = apply_joins(records, join_tree, opts)
237
+ records = find_records(records: records(options),
238
+ resource_klass: related_klass,
239
+ primary_keys: source_rid.id,
240
+ join_tree: join_tree,
241
+ filters: filters,
242
+ options: options)
161
243
 
162
- # Options for filtering
163
- opts[:joins] = joins
164
- opts[:related_alias] = related_alias
244
+ joins = join_tree.joins
245
+ related_alias = joins[''][:alias]
165
246
 
166
- filters = opts.fetch(:filters, {})
167
- records = related_klass.filter_records(records, filters, opts)
247
+ records = records.select(Arel.sql("#{concat_table_field(related_alias, related_klass._primary_key)}"))
168
248
 
169
- records.count(:all)
249
+ count_records(records)
170
250
  end
171
251
 
172
- def parse_relationship_path(path)
173
- relationships = []
174
- relationship_names = []
175
- field = nil
176
-
177
- current_path = path
178
- current_resource_klass = self
179
- loop do
180
- parts = current_path.to_s.partition('.')
181
- relationship = current_resource_klass._relationship(parts[0])
182
- if relationship
183
- relationships << relationship
184
- relationship_names << relationship.name
185
- else
186
- if parts[2].blank?
187
- field = parts[0]
188
- break
189
- else
190
- # :nocov:
191
- warn "Unknown relationship #{parts[0]}"
192
- # :nocov:
193
- end
194
- end
252
+ def records(_options = {})
253
+ _model_class.distinct.all
254
+ end
195
255
 
196
- current_resource_klass = relationship.resource_klass
256
+ protected
197
257
 
198
- if parts[2].include?('.')
199
- current_path = parts[2]
200
- else
201
- relationship = current_resource_klass._relationship(parts[2])
202
- if relationship
203
- relationships << relationship
204
- relationship_names << relationship.name
205
- else
206
- field = parts[2]
207
- end
208
- break
258
+ def to_one_relationships_for_linkage(include_related)
259
+ include_related ||= {}
260
+ relationships = []
261
+ _relationships.each do |name, relationship|
262
+ if relationship.is_a?(JSONAPI::Relationship::ToOne) && !include_related.has_key?(name) && relationship.include_optional_linkage_data?
263
+ relationships << name
209
264
  end
210
265
  end
211
-
212
- return relationships, relationship_names.join('.'), field
266
+ relationships
213
267
  end
214
268
 
215
- protected
216
-
217
269
  def find_record_by_key(key, options = {})
218
- records = find_records({ _primary_key => key }, options.except(:paginator, :sort_criteria))
219
- record = records.first
270
+ record = find_records(records: records(options), primary_keys: key, options: options).first
220
271
  fail JSONAPI::Exceptions::RecordNotFound.new(key) if record.nil?
221
272
  record
222
273
  end
223
274
 
224
275
  def find_records_by_keys(keys, options = {})
225
- records(options).where({ _primary_key => keys })
276
+ find_records(records: records(options), primary_keys: keys, options: options)
226
277
  end
227
278
 
228
279
  def find_related_monomorphic_fragments(source_rids, relationship, options, connect_source_identity)
229
- opts = options.dup
230
-
280
+ filters = options.fetch(:filters, {})
231
281
  source_ids = source_rids.collect {|rid| rid.id}
232
282
 
233
- context = opts[:context]
234
-
235
- related_klass = relationship.resource_klass
236
-
237
- primary_key_field = "#{_table_name}.#{_primary_key}"
238
-
239
- records = records(context: context).where(primary_key_field => source_ids)
240
-
241
- # join in related to the source records
242
- records, related_alias = get_join_alias(records) { |records| records.joins(relationship.relation_name(opts)) }
283
+ include_directives = options[:include_directives] ? options[:include_directives].include_directives : {}
284
+ resource_klass = relationship.resource_klass
285
+ linkage_relationships = resource_klass.to_one_relationships_for_linkage(include_directives[:include_related])
243
286
 
244
287
  sort_criteria = []
245
- opts[:sort_criteria].try(:each) do |sort|
246
- field = sort[:field].to_s == 'id' ? related_klass._primary_key : sort[:field]
288
+ options[:sort_criteria].try(:each) do |sort|
289
+ field = sort[:field].to_s == 'id' ? resource_klass._primary_key : sort[:field]
247
290
  sort_criteria << { field: field, direction: sort[:direction] }
248
291
  end
249
292
 
250
- paginator = opts[:paginator]
251
-
252
- filters = opts.fetch(:filters, {})
253
-
254
- # Joins in this case are related to the related_klass
255
- join_tree = JoinTree.new(resource_klass: related_klass,
293
+ join_tree = JoinTree.new(resource_klass: self,
256
294
  source_relationship: relationship,
257
- filters: filters,
295
+ relationships: linkage_relationships,
258
296
  sort_criteria: sort_criteria,
259
- options: opts)
297
+ filters: filters,
298
+ options: options)
260
299
 
261
- records, joins = apply_joins(records, join_tree, opts)
300
+ paginator = options[:paginator] if source_rids.count == 1
262
301
 
263
- # Options for filtering
264
- opts[:joins] = joins
265
- opts[:related_alias] = related_alias
302
+ records = find_records(records: records(options),
303
+ resource_klass: resource_klass,
304
+ sort_criteria: sort_criteria,
305
+ primary_keys: source_ids,
306
+ paginator: paginator,
307
+ filters: filters,
308
+ join_tree: join_tree,
309
+ options: options)
266
310
 
267
- records = related_klass.filter_records(records, filters, opts)
311
+ joins = join_tree.joins
312
+ resource_table_alias = joins[''][:alias]
268
313
 
269
- order_options = related_klass.construct_order_options(sort_criteria)
314
+ pluck_fields = [
315
+ Arel.sql("#{_table_name}.#{_primary_key} AS source_id"),
316
+ Arel.sql("#{concat_table_field(resource_table_alias, resource_klass._primary_key)} AS #{resource_table_alias}_#{resource_klass._primary_key}")
317
+ ]
270
318
 
271
- # ToDO: Remove count check. Currently pagination isn't working with multiple source_rids (i.e. it only works
272
- # for show relationships, not related includes).
273
- if paginator && source_rids.count == 1
274
- records = related_klass.apply_pagination(records, paginator, order_options)
319
+ cache_field = resource_klass.attribute_to_model_field(:_cache_field) if options[:cache]
320
+ if cache_field
321
+ pluck_fields << Arel.sql("#{concat_table_field(resource_table_alias, cache_field[:name])} AS #{resource_table_alias}_#{cache_field[:name]}")
275
322
  end
276
323
 
277
- records = sort_records(records, order_options, opts)
324
+ linkage_fields = []
278
325
 
279
- pluck_fields = [
280
- Arel.sql(primary_key_field),
281
- Arel.sql("#{concat_table_field(related_alias, related_klass._primary_key)} AS #{related_alias}_#{related_klass._primary_key}")
282
- ]
326
+ linkage_relationships.each do |name|
327
+ linkage_relationship = resource_klass._relationship(name)
283
328
 
284
- cache_field = related_klass.attribute_to_model_field(:_cache_field) if opts[:cache]
285
- if cache_field
286
- pluck_fields << Arel.sql("#{concat_table_field(related_alias, cache_field[:name])} AS #{related_alias}_#{cache_field[:name]}")
329
+ if linkage_relationship.polymorphic? && linkage_relationship.belongs_to?
330
+ linkage_relationship.resource_types.each do |resource_type|
331
+ klass = resource_klass_for(resource_type)
332
+ linkage_fields << {relationship_name: name, resource_klass: klass}
333
+
334
+ linkage_table_alias = joins["#{linkage_relationship.name.to_s}##{resource_type}"][:alias]
335
+ primary_key = klass._primary_key
336
+ pluck_fields << Arel.sql("#{concat_table_field(linkage_table_alias, primary_key)} AS #{linkage_table_alias}_#{primary_key}")
337
+ end
338
+ else
339
+ klass = linkage_relationship.resource_klass
340
+ linkage_fields << {relationship_name: name, resource_klass: klass}
341
+
342
+ linkage_table_alias = joins[name.to_s][:alias]
343
+ primary_key = klass._primary_key
344
+ pluck_fields << Arel.sql("#{concat_table_field(linkage_table_alias, primary_key)} AS #{linkage_table_alias}_#{primary_key}")
345
+ end
287
346
  end
288
347
 
289
348
  model_fields = {}
290
- attributes = opts[:attributes]
349
+ attributes = options[:attributes]
291
350
  attributes.try(:each) do |attribute|
292
- model_field = related_klass.attribute_to_model_field(attribute)
351
+ model_field = resource_klass.attribute_to_model_field(attribute)
293
352
  model_fields[attribute] = model_field
294
- pluck_fields << Arel.sql("#{concat_table_field(related_alias, model_field[:name])} AS #{related_alias}_#{model_field[:name]}")
353
+ pluck_fields << Arel.sql("#{concat_table_field(resource_table_alias, model_field[:name])} AS #{resource_table_alias}_#{model_field[:name]}")
295
354
  end
296
355
 
356
+ fragments = {}
297
357
  rows = records.pluck(*pluck_fields)
298
-
299
- related_fragments = {}
300
-
301
358
  rows.each do |row|
302
- unless row[1].nil?
303
- rid = JSONAPI::ResourceIdentity.new(related_klass, row[1])
359
+ rid = JSONAPI::ResourceIdentity.new(resource_klass, row[1])
304
360
 
305
- related_fragments[rid] ||= JSONAPI::ResourceFragment.new(rid)
361
+ fragments[rid] ||= JSONAPI::ResourceFragment.new(rid)
306
362
 
307
- attributes_offset = 2
363
+ attributes_offset = 2
308
364
 
309
- if cache_field
310
- related_fragments[rid].cache = cast_to_attribute_type(row[attributes_offset], cache_field[:type])
311
- attributes_offset+= 1
312
- end
365
+ if cache_field
366
+ fragments[rid].cache = cast_to_attribute_type(row[attributes_offset], cache_field[:type])
367
+ attributes_offset+= 1
368
+ end
313
369
 
314
- model_fields.each_with_index do |k, idx|
315
- related_fragments[rid].attributes[k[0]] = cast_to_attribute_type(row[idx + attributes_offset], k[1][:type])
316
- end
370
+ model_fields.each_with_index do |k, idx|
371
+ fragments[rid].add_attribute(k[0], cast_to_attribute_type(row[idx + attributes_offset], k[1][:type]))
372
+ attributes_offset+= 1
373
+ end
317
374
 
318
- source_rid = JSONAPI::ResourceIdentity.new(self, row[0])
375
+ source_rid = JSONAPI::ResourceIdentity.new(self, row[0])
319
376
 
320
- related_fragments[rid].add_related_from(source_rid)
377
+ fragments[rid].add_related_from(source_rid)
321
378
 
322
- if connect_source_identity
323
- related_relationship = related_klass._relationships[relationship.inverse_relationship]
324
- if related_relationship
325
- related_fragments[rid].add_related_identity(related_relationship.name, source_rid)
326
- end
379
+ linkage_fields.each do |linkage_field|
380
+ fragments[rid].initialize_related(linkage_field[:relationship_name])
381
+ related_id = row[attributes_offset]
382
+ if related_id
383
+ related_rid = JSONAPI::ResourceIdentity.new(linkage_field[:resource_klass], related_id)
384
+ fragments[rid].add_related_identity(linkage_field[:relationship_name], related_rid)
385
+ end
386
+ attributes_offset+= 1
387
+ end
388
+
389
+ if connect_source_identity
390
+ related_relationship = resource_klass._relationships[relationship.inverse_relationship]
391
+ if related_relationship
392
+ fragments[rid].add_related_identity(related_relationship.name, source_rid)
327
393
  end
328
394
  end
329
395
  end
330
396
 
331
- related_fragments
397
+ fragments
332
398
  end
333
399
 
334
400
  # Gets resource identities where the related resource is polymorphic and the resource type and id
335
401
  # are stored on the primary resources. Cache fields will always be on the related resources.
336
402
  def find_related_polymorphic_fragments(source_rids, relationship, options, connect_source_identity)
403
+ filters = options.fetch(:filters, {})
337
404
  source_ids = source_rids.collect {|rid| rid.id}
338
405
 
339
- context = options[:context]
406
+ resource_klass = relationship.resource_klass
407
+ include_directives = options[:include_directives] ? options[:include_directives].include_directives : {}
408
+
409
+ linkage_relationships = []
410
+
411
+ resource_types = relationship.resource_types
412
+
413
+ resource_types.each do |resource_type|
414
+ related_resource_klass = resource_klass_for(resource_type)
415
+ relationships = related_resource_klass.to_one_relationships_for_linkage(include_directives[:include_related])
416
+ relationships.each do |r|
417
+ linkage_relationships << "##{resource_type}.#{r}"
418
+ end
419
+ end
420
+
421
+ join_tree = JoinTree.new(resource_klass: self,
422
+ source_relationship: relationship,
423
+ relationships: linkage_relationships,
424
+ filters: filters,
425
+ options: options)
426
+
427
+ paginator = options[:paginator] if source_rids.count == 1
340
428
 
341
- records = records(context: context)
429
+ # Note: We will sort by the source table. Without using unions we can't sort on a polymorphic relationship
430
+ # in any manner that makes sense
431
+ records = find_records(records: records(options),
432
+ resource_klass: resource_klass,
433
+ sort_primary: true,
434
+ primary_keys: source_ids,
435
+ paginator: paginator,
436
+ filters: filters,
437
+ join_tree: join_tree,
438
+ options: options)
439
+
440
+ joins = join_tree.joins
342
441
 
343
442
  primary_key = concat_table_field(_table_name, _primary_key)
344
443
  related_key = concat_table_field(_table_name, relationship.foreign_key)
@@ -350,62 +449,85 @@ module JSONAPI
350
449
  Arel.sql("#{related_type} AS #{_table_name}_#{relationship.polymorphic_type}")
351
450
  ]
352
451
 
353
- relations = relationship.polymorphic_relations
354
-
355
452
  # Get the additional fields from each relation. There's a limitation that the fields must exist in each relation
356
453
 
357
454
  relation_positions = {}
358
- relation_index = 3
455
+ relation_index = pluck_fields.length
359
456
 
360
457
  attributes = options.fetch(:attributes, [])
361
458
 
362
- if relations.nil? || relations.length == 0
459
+ # Add resource specific fields
460
+ if resource_types.nil? || resource_types.length == 0
363
461
  # :nocov:
364
- warn "No relations found for polymorphic relationship."
462
+ warn "No resource types found for polymorphic relationship."
365
463
  # :nocov:
366
464
  else
367
- relations.try(:each) do |relation|
368
- related_klass = resource_klass_for(relation.to_s)
465
+ resource_types.try(:each) do |type|
466
+ related_klass = resource_klass_for(type.to_s)
369
467
 
370
468
  cache_field = related_klass.attribute_to_model_field(:_cache_field) if options[:cache]
371
469
 
372
- # We only need to join the relations if we are getting additional fields
373
- if cache_field || attributes.length > 0
374
- records, table_alias = get_join_alias(records) { |records| records.left_joins(relation.to_sym) }
375
-
376
- if cache_field
377
- pluck_fields << concat_table_field(table_alias, cache_field[:name])
378
- end
379
-
380
- model_fields = {}
381
- attributes.try(:each) do |attribute|
382
- model_field = related_klass.attribute_to_model_field(attribute)
383
- model_fields[attribute] = model_field
384
- end
470
+ table_alias = joins["##{type}"][:alias]
385
471
 
386
- model_fields.each do |_k, v|
387
- pluck_fields << concat_table_field(table_alias, v[:name])
388
- end
472
+ cache_offset = relation_index
473
+ if cache_field
474
+ pluck_fields << Arel.sql("#{concat_table_field(table_alias, cache_field[:name])} AS cache_#{type}_#{cache_field[:name]}")
475
+ relation_index+= 1
476
+ end
389
477
 
478
+ model_fields = {}
479
+ field_offset = relation_index
480
+ attributes.try(:each) do |attribute|
481
+ model_field = related_klass.attribute_to_model_field(attribute)
482
+ model_fields[attribute] = model_field
483
+ pluck_fields << Arel.sql("#{concat_table_field(table_alias, model_field[:name])} AS #{table_alias}_#{model_field[:name]}")
484
+ relation_index+= 1
390
485
  end
391
486
 
392
- related = related_klass._model_class.name
393
- relation_positions[related] = { relation_klass: related_klass,
394
- cache_field: cache_field,
395
- model_fields: model_fields,
396
- field_offset: relation_index}
487
+ model_offset = relation_index
488
+ model_fields.each do |_k, v|
489
+ pluck_fields << Arel.sql("#{concat_table_field(table_alias, v[:name])}")
490
+ relation_index+= 1
491
+ end
397
492
 
398
- relation_index+= 1 if cache_field
399
- relation_index+= attributes.length if attributes.length > 0
493
+ relation_positions[type] = {relation_klass: related_klass,
494
+ cache_field: cache_field,
495
+ cache_offset: cache_offset,
496
+ model_fields: model_fields,
497
+ model_offset: model_offset,
498
+ field_offset: field_offset}
400
499
  end
401
500
  end
402
501
 
403
- primary_resource_filters = options[:filters]
404
- primary_resource_filters ||= {}
502
+ # Add to_one linkage fields
503
+ linkage_fields = []
504
+ linkage_offset = relation_index
405
505
 
406
- primary_resource_filters[_primary_key] = source_ids
506
+ linkage_relationships.each do |linkage_relationship_path|
507
+ path = JSONAPI::Path.new(resource_klass: self,
508
+ path_string: "#{relationship.name}#{linkage_relationship_path}",
509
+ ensure_default_field: false)
407
510
 
408
- records = apply_filters(records, primary_resource_filters, options)
511
+ linkage_relationship = path.segments[-1].relationship
512
+
513
+ if linkage_relationship.polymorphic? && linkage_relationship.belongs_to?
514
+ linkage_relationship.resource_types.each do |resource_type|
515
+ klass = resource_klass_for(resource_type)
516
+ linkage_fields << {relationship: linkage_relationship, resource_klass: klass}
517
+
518
+ linkage_table_alias = joins[linkage_relationship_path][:alias]
519
+ primary_key = klass._primary_key
520
+ pluck_fields << Arel.sql("#{concat_table_field(linkage_table_alias, primary_key)} AS #{linkage_table_alias}_#{primary_key}")
521
+ end
522
+ else
523
+ klass = linkage_relationship.resource_klass
524
+ linkage_fields << {relationship: linkage_relationship, resource_klass: klass}
525
+
526
+ linkage_table_alias = joins[linkage_relationship_path.to_s][:alias]
527
+ primary_key = klass._primary_key
528
+ pluck_fields << Arel.sql("#{concat_table_field(linkage_table_alias, primary_key)} AS #{linkage_table_alias}_#{primary_key}")
529
+ end
530
+ end
409
531
 
410
532
  rows = records.pluck(*pluck_fields)
411
533
 
@@ -428,21 +550,29 @@ module JSONAPI
428
550
  end
429
551
  end
430
552
 
431
- relation_position = relation_positions[row[2]]
553
+ relation_position = relation_positions[row[2].downcase.pluralize]
432
554
  model_fields = relation_position[:model_fields]
433
555
  cache_field = relation_position[:cache_field]
556
+ cache_offset = relation_position[:cache_offset]
434
557
  field_offset = relation_position[:field_offset]
435
558
 
436
- attributes_offset = 0
437
-
438
559
  if cache_field
439
- related_fragments[rid].cache = cast_to_attribute_type(row[field_offset], cache_field[:type])
440
- attributes_offset+= 1
560
+ related_fragments[rid].cache = cast_to_attribute_type(row[cache_offset], cache_field[:type])
441
561
  end
442
562
 
443
563
  if attributes.length > 0
444
564
  model_fields.each_with_index do |k, idx|
445
- related_fragments[rid].add_attribute(k[0], cast_to_attribute_type(row[idx + field_offset + attributes_offset], k[1][:type]))
565
+ related_fragments[rid].add_attribute(k[0], cast_to_attribute_type(row[idx + field_offset], k[1][:type]))
566
+ end
567
+ end
568
+
569
+ linkage_fields.each_with_index do |linkage_field_details, idx|
570
+ relationship = linkage_field_details[:relationship]
571
+ related_fragments[rid].initialize_related(relationship.name)
572
+ related_id = row[linkage_offset + idx]
573
+ if related_id
574
+ related_rid = JSONAPI::ResourceIdentity.new(linkage_field_details[:resource_klass], related_id)
575
+ related_fragments[rid].add_related_identity(relationship.name, related_rid)
446
576
  end
447
577
  end
448
578
  end
@@ -451,28 +581,41 @@ module JSONAPI
451
581
  related_fragments
452
582
  end
453
583
 
454
- def find_records(filters, options = {})
455
- opts = options.dup
456
-
457
- sort_criteria = opts.fetch(:sort_criteria) { [] }
584
+ def find_records(records:,
585
+ join_tree: JoinTree.new(resource_klass: self),
586
+ resource_klass: self,
587
+ filters: nil,
588
+ primary_keys: nil,
589
+ sort_criteria: nil,
590
+ sort_primary: nil,
591
+ paginator: nil,
592
+ options: {})
458
593
 
459
- join_tree = JoinTree.new(resource_klass: self,
460
- filters: filters,
461
- sort_criteria: sort_criteria,
462
- options: opts)
594
+ opts = options.dup
595
+ records = resource_klass.apply_joins(records, join_tree, opts)
463
596
 
464
- records, joins = apply_joins(records(opts), join_tree, opts)
597
+ if primary_keys
598
+ records = records.where(_primary_key => primary_keys)
599
+ end
465
600
 
466
- opts[:joins] = joins
601
+ opts[:joins] = join_tree.joins
467
602
 
468
- records = filter_records(records, filters, opts)
603
+ if filters
604
+ records = resource_klass.filter_records(records, filters, opts)
605
+ end
469
606
 
470
- order_options = construct_order_options(sort_criteria)
471
- records = sort_records(records, order_options, opts)
607
+ if sort_primary
608
+ records = records.order(_primary_key => :asc)
609
+ else
610
+ order_options = resource_klass.construct_order_options(sort_criteria)
611
+ records = resource_klass.sort_records(records, order_options, opts)
612
+ end
472
613
 
473
- records = apply_pagination(records, opts[:paginator], order_options)
614
+ if paginator
615
+ records = resource_klass.apply_pagination(records, paginator, order_options)
616
+ end
474
617
 
475
- records.distinct
618
+ records
476
619
  end
477
620
 
478
621
  def get_join_alias(records, &block)
@@ -507,20 +650,20 @@ module JSONAPI
507
650
  end
508
651
 
509
652
  def apply_joins(records, join_tree, _options)
510
- joins = join_tree.get_joins
653
+ joins = join_tree.joins
511
654
 
512
- joins.each do |key, join_details|
513
- case join_details[:join_type]
655
+ joins.each_value do |join|
656
+ case join[:join_type]
514
657
  when :inner
515
- records, join_alias = get_join_alias(records) { |records| records.joins(join_details[:relation_join_hash]) }
658
+ records, join_alias = get_join_alias(records) { |records| records.joins(join[:relation_join_hash]) }
659
+ join[:alias] = join_alias
516
660
  when :left
517
- records, join_alias = get_join_alias(records) { |records| records.left_joins(join_details[:relation_join_hash]) }
661
+ records, join_alias = get_join_alias(records) { |records| records.joins_left(join[:relation_join_hash]) }
662
+ join[:alias] = join_alias
518
663
  end
519
-
520
- joins[key][:alias] = join_alias
521
664
  end
522
665
 
523
- return records, joins
666
+ return records
524
667
  end
525
668
 
526
669
  def apply_pagination(records, paginator, order_options)
@@ -544,21 +687,41 @@ module JSONAPI
544
687
  strategy = _allowed_sort.fetch(field.to_sym, {})[:apply]
545
688
 
546
689
  if strategy
547
- call_method_or_proc(strategy, records, direction, context)
690
+ records = call_method_or_proc(strategy, records, direction, context)
548
691
  else
549
692
  joins = options[:joins] || {}
550
693
 
551
- records.order("#{get_aliased_field(field, joins, options[:related_alias])} #{direction}")
694
+ records = records.order("#{get_aliased_field(field, joins)} #{direction}")
552
695
  end
696
+ records
553
697
  end
554
698
 
555
699
  # Assumes ActiveRecord's counting. Override if you need a different counting method
556
700
  def count_records(records)
557
- records.count(:all)
701
+ if Rails::VERSION::MAJOR >= 5 && ActiveRecord::VERSION::MINOR >= 1
702
+ records.count(:all)
703
+ else
704
+ records.count
705
+ end
558
706
  end
559
707
 
560
708
  def filter_records(records, filters, options)
561
- apply_filters(records, filters, options)
709
+ if _polymorphic
710
+ _polymorphic_resource_klasses.each do |klass|
711
+ records = klass.apply_filters(records, filters, options)
712
+ end
713
+ else
714
+ records = apply_filters(records, filters, options)
715
+ end
716
+ records
717
+ end
718
+
719
+ def construct_order_options(sort_params)
720
+ if _polymorphic
721
+ warn "Sorting is not supported on polymorphic relationships"
722
+ else
723
+ super(sort_params)
724
+ end
562
725
  end
563
726
 
564
727
  def sort_records(records, order_options, options)
@@ -595,31 +758,22 @@ module JSONAPI
595
758
  records
596
759
  end
597
760
 
598
- def get_aliased_field(path_with_field, joins, related_alias)
599
- relationships, relationship_path, field = parse_relationship_path(path_with_field)
600
- relationship = relationships.last
601
-
602
- resource_klass = relationship ? relationship.resource_klass : self
761
+ def get_aliased_field(path_with_field, joins)
762
+ path = JSONAPI::Path.new(resource_klass: self, path_string: path_with_field)
603
763
 
604
- if field.empty?
605
- field_name = resource_klass._primary_key
606
- else
607
- field_name = resource_klass._attribute_delegated_name(field)
608
- end
764
+ relationship = path.segments[-2]
765
+ field = path.segments[-1]
766
+ relationship_path = path.relationship_path_string
609
767
 
610
768
  if relationship
611
769
  join_name = relationship_path
612
-
613
770
  join = joins.try(:[], join_name)
614
-
615
771
  table_alias = join.try(:[], :alias)
616
- else
617
- table_alias = related_alias
618
772
  end
619
773
 
620
- table_alias ||= resource_klass._table_name
774
+ table_alias ||= joins[''][:alias]
621
775
 
622
- concat_table_field(table_alias, field_name)
776
+ concat_table_field(table_alias, field.delegated_field_name)
623
777
  end
624
778
 
625
779
  def apply_filter(records, filter, value, options = {})
@@ -629,8 +783,7 @@ module JSONAPI
629
783
  records = call_method_or_proc(strategy, records, value, options)
630
784
  else
631
785
  joins = options[:joins] || {}
632
- related_alias = options[:related_alias]
633
- records = records.where(get_aliased_field(filter, joins, related_alias) => value)
786
+ records = records.where(get_aliased_field(filter, joins) => value)
634
787
  end
635
788
 
636
789
  records