Skip to content

Commit d87cd2d

Browse files
committed
Add specialized records methods, find_to_populate_by_keys
Specialized records methods allow the system to avoid duplicating expensive permission checks for records that have already been found. This is needed because of the new multiphase approach building up the result set. Closes gh-1228
1 parent 013d090 commit d87cd2d

File tree

3 files changed

+66
-5
lines changed

3 files changed

+66
-5
lines changed

lib/jsonapi/active_relation_resource_finder.rb

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,16 @@ def find_by_keys(keys, options = {})
7070
resources_for(records, options[:context])
7171
end
7272

73+
# Returns an array of Resources identified by the `keys` array. The resources are not filtered as this
74+
# will have been done in a prior step
75+
#
76+
# @param keys [Array<key>] Array of primary keys to find resources for
77+
# @option options [Hash] :context The context of the request, set in the controller
78+
def find_to_populate_by_keys(keys, options = {})
79+
records = records_for_populate(options).where(_primary_key => keys)
80+
resources_for(records, options[:context])
81+
end
82+
7383
# Finds Resource fragments using the `filters`. Pagination and sort options are used when provided.
7484
# Retrieving the ResourceIdentities and attributes does not instantiate a model instance.
7585
# Note: This is incompatible with Polymorphic resources (which are going to come from two separate tables)
@@ -242,10 +252,57 @@ def count_related(source_rid, relationship_name, options = {})
242252
count_records(records)
243253
end
244254

245-
def records(_options = {})
255+
# This resource finder (ActiveRecordResourceFinder) uses an `ActiveRecord::Relation` as the starting point for
256+
# retrieving models. From this relation filters, sorts and joins are applied as needed.
257+
# Depending on which phase of the request processing different `records` methods will be called, giving the user
258+
# the opportunity to override them differently for performance and security reasons.
259+
260+
# begin `records`methods
261+
262+
# Base for the `records` methods that follow and is not directly used for accessing model data by this class.
263+
# Overriding this method gives a single place to affect the `ActiveRecord::Relation` used for the resource.
264+
#
265+
# @option options [Hash] :context The context of the request, set in the controller
266+
#
267+
# @return [ActiveRecord::Relation]
268+
def records_base(_options = {})
246269
_model_class.all
247270
end
248271

272+
# The `ActiveRecord::Relation` used for finding user requested models. This may be overridden to enforce
273+
# permissions checks on the request.
274+
#
275+
# @option options [Hash] :context The context of the request, set in the controller
276+
#
277+
# @return [ActiveRecord::Relation]
278+
def records(options = {})
279+
records_base(options)
280+
end
281+
282+
# The `ActiveRecord::Relation` used for populating the ResourceSet. Only resources that have been previously
283+
# identified through the `records` method will be accessed. Thus it should not be necessary to reapply permissions
284+
# checks. However if the model needs to include other models adding `includes` is appropriate
285+
#
286+
# @option options [Hash] :context The context of the request, set in the controller
287+
#
288+
# @return [ActiveRecord::Relation]
289+
def records_for_populate(options = {})
290+
records_base(options)
291+
end
292+
293+
# The `ActiveRecord::Relation` used for the finding related resources. Only resources that have been previously
294+
# identified through the `records` method will be accessed and used as the basis to find related resources. Thus
295+
# it should not be necessary to reapply permissions checks.
296+
#
297+
# @option options [Hash] :context The context of the request, set in the controller
298+
#
299+
# @return [ActiveRecord::Relation]
300+
def records_for_source_to_related(options = {})
301+
records_base(options)
302+
end
303+
304+
# end `records` methods
305+
249306
def apply_join(records:, relationship:, resource_type:, join_type:, options:)
250307
if relationship.polymorphic? && relationship.belongs_to?
251308
case join_type
@@ -267,7 +324,7 @@ def apply_join(records:, relationship:, resource_type:, join_type:, options:)
267324
end
268325

269326
def relationship_records(relationship:, join_type: :inner, resource_type: nil, options: {})
270-
records = relationship.parent_resource.records(options)
327+
records = relationship.parent_resource.records_for_source_to_related(options)
271328
strategy = relationship.options[:apply_join]
272329

273330
if strategy
@@ -336,7 +393,7 @@ def find_related_monomorphic_fragments(source_rids, relationship, options, conne
336393

337394
paginator = options[:paginator] if source_rids.count == 1
338395

339-
records = apply_request_settings_to_records(records: records(options),
396+
records = apply_request_settings_to_records(records: records_for_source_to_related(options),
340397
resource_klass: resource_klass,
341398
sort_criteria: sort_criteria,
342399
primary_keys: source_ids,
@@ -463,7 +520,7 @@ def find_related_polymorphic_fragments(source_rids, relationship, options, conne
463520

464521
# Note: We will sort by the source table. Without using unions we can't sort on a polymorphic relationship
465522
# in any manner that makes sense
466-
records = apply_request_settings_to_records(records: records(options),
523+
records = apply_request_settings_to_records(records: records_for_source_to_related(options),
467524
resource_klass: resource_klass,
468525
sort_primary: true,
469526
primary_keys: source_ids,

lib/jsonapi/resource_set.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def populate!(serializer, context, find_options)
9191
# Step Four find any of the missing resources and join them into the result
9292
missed_resource_ids.each_pair do |resource_klass, ids|
9393
find_opts = {context: context, fields: find_options[:fields]}
94-
found_resources = resource_klass.find_by_keys(ids, find_opts)
94+
found_resources = resource_klass.find_to_populate_by_keys(ids, find_opts)
9595

9696
found_resources.each do |resource|
9797
relationship_data = @resource_klasses[resource_klass][resource.id][:relationships]

test/fixtures/active_record.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,6 +1527,10 @@ def find_by_key(key, options = {})
15271527
resource_for(record, options[:context])
15281528
end
15291529

1530+
def find_to_populate_by_keys(keys, options = {})
1531+
find_by_keys(keys, options)
1532+
end
1533+
15301534
def find_by_keys(keys, options = {})
15311535
records = find_breeds_by_keys(keys, options)
15321536
resources_for(records, options[:context])

0 commit comments

Comments
 (0)