Skip to content

Commit 2668b6b

Browse files
authored
V0 11 dev performance (#1431)
* Reduce number of string allocations in LinkBuilder * Consistently access `include_related` * Remove unused class variable * Cache `id` after retrieving it from the model * Cache `module_path` * Cache resource_klass_for and resource_type_for * Remove no longer used method _setup_relationship * Delete nil values without creating a new object * Rework resource naming for method caches
1 parent 0bbbc0b commit 2668b6b

File tree

5 files changed

+70
-52
lines changed

5 files changed

+70
-52
lines changed

lib/jsonapi/link_builder.rb

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ def query_link(query_params)
4949

5050
def relationships_related_link(source, relationship, query_params = {})
5151
if relationship._routed
52-
url = "#{ self_link(source) }/#{ route_for_relationship(relationship) }"
53-
url = "#{ url }?#{ query_params.to_query }" if query_params.present?
52+
url = +"#{ self_link(source) }/#{ route_for_relationship(relationship) }"
53+
url << "?#{ query_params.to_query }" if query_params.present?
5454
url
5555
else
5656
if JSONAPI.configuration.warn_on_missing_routes && !relationship._warned_missing_route
@@ -129,18 +129,14 @@ def resources_path(source_klass)
129129
@_resources_path[source_klass] ||= formatted_module_path_from_class(source_klass) + format_route(source_klass._type.to_s)
130130
end
131131

132-
def resource_path(source)
132+
def resource_url(source)
133133
if source.class.singleton?
134-
resources_path(source.class)
134+
"#{ base_url }#{ engine_mount_point }#{ resources_path(source.class) }"
135135
else
136-
"#{resources_path(source.class)}/#{source.id}"
136+
"#{ base_url }#{ engine_mount_point }#{resources_path(source.class)}/#{source.id}"
137137
end
138138
end
139139

140-
def resource_url(source)
141-
"#{ base_url }#{ engine_mount_point }#{ resource_path(source) }"
142-
end
143-
144140
def route_for_relationship(relationship)
145141
format_route(relationship.name)
146142
end

lib/jsonapi/resource_common.rb

Lines changed: 57 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def _model
3939
end
4040

4141
def id
42-
_model.public_send(self.class._primary_key)
42+
@id ||= _model.public_send(self.class._primary_key)
4343
end
4444

4545
def identity
@@ -510,7 +510,10 @@ def inherited(subclass)
510510
subclass._routed = false
511511
subclass._warned_missing_route = false
512512

513-
subclass._clear_cached_attribute_options
513+
subclass._attribute_options_cache = {}
514+
subclass._model_class_to_resource_type_cache = {}
515+
subclass._resource_type_to_class_cache = {}
516+
514517
subclass._clear_fields_cache
515518

516519
subclass._resource_retrieval_strategy_loaded = @_resource_retrieval_strategy_loaded
@@ -533,15 +536,19 @@ def rebuild_relationships(relationships)
533536
end
534537

535538
def resource_klass_for(type)
539+
@_resource_type_to_class_cache ||= {}
536540
type = type.underscore
537-
type_with_module = type.start_with?(module_path) ? type : module_path + type
538541

539-
resource_name = _resource_name_from_type(type_with_module)
540-
resource = resource_name.safe_constantize if resource_name
541-
if resource.nil?
542-
fail NameError, "JSONAPI: Could not find resource '#{type}'. (Class #{resource_name} not found)"
542+
@_resource_type_to_class_cache.fetch(type) do
543+
type_with_module = type.start_with?(module_path) ? type : module_path + type
544+
545+
resource_name = _resource_name_from_type(type_with_module)
546+
resource_klass = resource_name.safe_constantize if resource_name
547+
if resource_klass.nil?
548+
fail NameError, "JSONAPI: Could not find resource '#{type}'. (Class #{resource_name} not found)"
549+
end
550+
@_resource_type_to_class_cache[type] = resource_klass
543551
end
544-
resource
545552
end
546553

547554
def resource_klass_for_model(model)
@@ -553,17 +560,33 @@ def _resource_name_from_type(type)
553560
end
554561

555562
def resource_type_for(model)
556-
model_name = model.class.to_s.underscore
557-
if _model_hints[model_name]
558-
_model_hints[model_name]
559-
else
560-
model_name.rpartition('/').last
563+
@_model_class_to_resource_type_cache.fetch(model.class) do
564+
model_name = model.class.name.underscore
565+
566+
resource_type = if _model_hints[model_name]
567+
_model_hints[model_name]
568+
else
569+
model_name.rpartition('/').last
570+
end
571+
572+
@_model_class_to_resource_type_cache[model.class] = resource_type
561573
end
562574
end
563575

564-
attr_accessor :_attributes, :_relationships, :_type, :_model_hints, :_routed, :_warned_missing_route,
576+
attr_accessor :_attributes,
577+
:_relationships,
578+
:_type,
579+
:_model_hints,
580+
:_routed,
581+
:_warned_missing_route,
565582
:_resource_retrieval_strategy_loaded
566-
attr_writer :_allowed_filters, :_paginator, :_allowed_sort
583+
584+
attr_writer :_allowed_filters,
585+
:_paginator,
586+
:_allowed_sort,
587+
:_model_class_to_resource_type_cache,
588+
:_resource_type_to_class_cache,
589+
:_attribute_options_cache
567590

568591
def create(context)
569592
new(create_model, context)
@@ -590,7 +613,7 @@ def attributes(*attrs)
590613
end
591614

592615
def attribute(attribute_name, options = {})
593-
_clear_cached_attribute_options
616+
_clear_attribute_options_cache
594617
_clear_fields_cache
595618

596619
attr = attribute_name.to_sym
@@ -903,7 +926,7 @@ def verify_relationship_filter(filter, raw, _context = nil)
903926

904927
# quasi private class methods
905928
def _attribute_options(attr)
906-
@_cached_attribute_options[attr] ||= default_attribute_options.merge(@_attributes[attr])
929+
@_attribute_options_cache[attr] ||= default_attribute_options.merge(@_attributes[attr])
907930
end
908931

909932
def _attribute_delegated_name(attr)
@@ -915,11 +938,11 @@ def _has_attribute?(attr)
915938
end
916939

917940
def _updatable_attributes
918-
_attributes.map { |key, options| key unless options[:readonly] }.compact
941+
_attributes.map { |key, options| key unless options[:readonly] }.delete_if {|v| v.nil? }
919942
end
920943

921944
def _updatable_relationships
922-
@_relationships.map { |key, relationship| key unless relationship.readonly? }.compact
945+
@_relationships.map { |key, relationship| key unless relationship.readonly? }.delete_if {|v| v.nil? }
923946
end
924947

925948
def _relationship(type)
@@ -1132,11 +1155,11 @@ def _has_sort?(sorting)
11321155
end
11331156

11341157
def module_path
1135-
if name == 'JSONAPI::Resource'
1136-
''
1137-
else
1138-
name =~ /::[^:]+\Z/ ? ($`.freeze.gsub('::', '/') + '/').underscore : ''
1139-
end
1158+
@module_path ||= if name == 'JSONAPI::Resource'
1159+
''
1160+
else
1161+
name =~ /::[^:]+\Z/ ? ($`.freeze.gsub('::', '/') + '/').underscore : ''
1162+
end
11401163
end
11411164

11421165
def default_sort
@@ -1169,18 +1192,6 @@ def _add_relationship(klass, *attrs)
11691192
end
11701193
end
11711194

1172-
def _setup_relationship(klass, *attrs)
1173-
_clear_fields_cache
1174-
1175-
options = attrs.extract_options!
1176-
options[:parent_resource] = self
1177-
1178-
relationship_name = attrs[0].to_sym
1179-
check_duplicate_relationship_name(relationship_name)
1180-
1181-
define_relationship_methods(relationship_name.to_sym, klass, options)
1182-
end
1183-
11841195
# ResourceBuilder methods
11851196
def define_relationship_methods(relationship_name, relationship_klass, options)
11861197
relationship = register_relationship(
@@ -1214,8 +1225,16 @@ def register_relationship(name, relationship_object)
12141225
@_relationships[name] = relationship_object
12151226
end
12161227

1217-
def _clear_cached_attribute_options
1218-
@_cached_attribute_options = {}
1228+
def _clear_attribute_options_cache
1229+
@_attribute_options_cache&.clear
1230+
end
1231+
1232+
def _clear_model_to_resource_type_cache
1233+
@_model_class_to_resource_type_cache&.clear
1234+
end
1235+
1236+
def _clear_resource_type_to_klass_cache
1237+
@_resource_type_to_class_cache&.clear
12191238
end
12201239

12211240
def _clear_fields_cache

lib/jsonapi/resource_serializer.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ def links_hash(source)
262262
if !links.key?('self') && !source.class.exclude_link?(:self)
263263
links['self'] = link_builder.self_link(source)
264264
end
265-
links.compact
265+
links.delete_if {|k,v| v.nil? }
266266
end
267267

268268
def custom_links_hash(source)
@@ -340,7 +340,7 @@ def default_relationship_links(source, relationship)
340340
links = {}
341341
links['self'] = self_link(source, relationship) unless relationship.exclude_link?(:self)
342342
links['related'] = related_link(source, relationship) unless relationship.exclude_link?(:related)
343-
links.compact
343+
links.delete_if {|k,v| v.nil? }
344344
end
345345

346346
def to_many_linkage(rids)

lib/jsonapi/resource_tree.rb

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,13 @@ def add_resource(resource, include_related)
6868
private
6969

7070
def init_included_relationships(fragment, include_related)
71-
include_related && include_related.each_key do |relationship_name|
71+
include_related&.each_key do |relationship_name|
7272
fragment.initialize_related(relationship_name)
7373
end
7474
end
7575

7676
def load_included(resource_klass, source_resource_tree, include_related, options)
77-
include_related.try(:each_key) do |key|
77+
include_related&.each_key do |key|
7878
relationship = resource_klass._relationship(key)
7979
relationship_name = relationship.name.to_sym
8080

@@ -159,7 +159,6 @@ def initialize(parent_relationship, source_resource_tree)
159159
@related_resource_trees ||= {}
160160

161161
@parent_relationship = parent_relationship
162-
@parent_relationship_name = parent_relationship.name.to_sym
163162
@source_resource_tree = source_resource_tree
164163
end
165164

test/controllers/controller_test.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4028,6 +4028,8 @@ def test_immutable_update_not_supported
40284028

40294029
class Api::V7::ClientsControllerTest < ActionController::TestCase
40304030
def test_get_namespaced_model_not_matching_resource_using_model_hint
4031+
Api::V7::ClientResource._clear_model_to_resource_type_cache
4032+
Api::V7::ClientResource._clear_resource_type_to_klass_cache
40314033
assert_cacheable_get :index
40324034
assert_response :success
40334035
assert_equal 'clients', json_response['data'][0]['type']
@@ -4037,6 +4039,8 @@ def test_get_namespaced_model_not_matching_resource_using_model_hint
40374039

40384040
def test_get_namespaced_model_not_matching_resource_not_using_model_hint
40394041
Api::V7::ClientResource._model_hints.delete('api/v7/customer')
4042+
Api::V7::ClientResource._clear_model_to_resource_type_cache
4043+
Api::V7::ClientResource._clear_resource_type_to_klass_cache
40404044
assert_cacheable_get :index
40414045
assert_response :success
40424046
assert_equal 'customers', json_response['data'][0]['type']

0 commit comments

Comments
 (0)