jsonapi-resources 0.10.0.beta1 → 0.10.0.beta2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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