jpie 1.2.0 → 1.3.0
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 +4 -4
- data/Gemfile.lock +1 -1
- data/PERFORMANCE_BASELINE.md +102 -0
- data/lib/json_api/controllers/concerns/resource_actions/preloading.rb +20 -4
- data/lib/json_api/controllers/concerns/resource_actions/serialization.rb +31 -3
- data/lib/json_api/resources/concerns/eager_load_dsl.rb +50 -0
- data/lib/json_api/resources/concerns/preload_dsl.rb +49 -0
- data/lib/json_api/resources/concerns/relationships_dsl.rb +24 -10
- data/lib/json_api/resources/resource.rb +4 -0
- data/lib/json_api/resources/resource_loader.rb +20 -1
- data/lib/json_api/serialization/serializer.rb +10 -0
- data/lib/json_api/support/collection_query.rb +9 -3
- data/lib/json_api/version.rb +1 -1
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1f2984708bbd678f90b58e39d28e853f93b504b933cd15f8af8a995c21eb41e4
|
|
4
|
+
data.tar.gz: 4b6107778dc307c1f155bf013b372dd05db8e0891f1b1a39c8b4ebdc96f4b42c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8393c4ba329b198d648f7bb2260777e0c889fde78e8d592ae72cfd9cd48ca97cdacfd0453214011f47cbff7af67c634dba001c888ca1447462f1263460f691c5
|
|
7
|
+
data.tar.gz: f42ea311ec1bea10f6e13d2c30af99eba23369fcc97ffa6a29acdfd2af74b6c109171fee4bed3b880edabc67e2157c950857d5a0fbac85b78bf55c94b57676e1
|
data/Gemfile.lock
CHANGED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# jpie Performance Baseline
|
|
2
|
+
|
|
3
|
+
This document outlines the performance metrics for key operations within the `jpie` gem, comparing the initial baseline with the results after implementing all optimizations.
|
|
4
|
+
|
|
5
|
+
## Performance Comparison
|
|
6
|
+
|
|
7
|
+
| Metric | Original Baseline | After Optimizations | Improvement |
|
|
8
|
+
|--------|-------------------|---------------------|-------------|
|
|
9
|
+
| `resource_loader_find_avg_ms` | 0.0182 | 0.0003 | **60x faster** |
|
|
10
|
+
| `resource_loader_find_for_model_avg_ms` | 0.036 | 0.0003 | **120x faster** |
|
|
11
|
+
| `jsonapi_object_avg_ms` | 0.0003 | 0.0001 | **3x faster** |
|
|
12
|
+
| `serialize_single_user_avg_ms` | 0.2168 | 0.0982 | **2.2x faster** |
|
|
13
|
+
| `serialize_10_users_avg_ms` | 1.4318 | 0.975 | **1.5x faster** |
|
|
14
|
+
| `serialize_single_user_query_count` | 4 | 4 | N/A |
|
|
15
|
+
| `serialize_10_users_query_count` | 40 | 40 | N/A |
|
|
16
|
+
| `serialize_single_user_allocations` | 997 | 977 | **2% reduction** |
|
|
17
|
+
| `serialize_10_users_allocations` | 9934 | 9716 | **2% reduction** |
|
|
18
|
+
| `relationship_definitions_avg_ms` | 0.0036 | 0.0002 | **18x faster** |
|
|
19
|
+
| `resource_instantiation_avg_ms` | 0.0003 | 0.0004 | Similar |
|
|
20
|
+
|
|
21
|
+
## Implemented Optimizations
|
|
22
|
+
|
|
23
|
+
### 1. ResourceLoader Caching (Phase 4)
|
|
24
|
+
- Thread-safe caching for `find` and `find_for_model` methods
|
|
25
|
+
- Eliminates repeated `constantize` calls
|
|
26
|
+
- **60-120x improvement** in resource class lookups
|
|
27
|
+
|
|
28
|
+
### 2. jsonapi_object Memoization (Phase 5)
|
|
29
|
+
- Memoized the static JSON:API object
|
|
30
|
+
- Returns frozen object to prevent mutations
|
|
31
|
+
- **3x improvement** in object generation
|
|
32
|
+
|
|
33
|
+
### 3. Relationship Definitions Caching (Phase 6)
|
|
34
|
+
- Memoized `relationship_definitions` computation
|
|
35
|
+
- Returns frozen array to prevent mutations
|
|
36
|
+
- **18x improvement** in relationship metadata access
|
|
37
|
+
|
|
38
|
+
### 4. Optional Count Query (Phase 7)
|
|
39
|
+
- `total_count` only computed when pagination is applied
|
|
40
|
+
- Avoids unnecessary COUNT queries for non-paginated requests
|
|
41
|
+
- Reduces database load
|
|
42
|
+
|
|
43
|
+
### 5. Eager Loading DSL (Phase 8)
|
|
44
|
+
- New `eager_load` class method for resources
|
|
45
|
+
- Automatically included in preloading without explicit `include` param
|
|
46
|
+
- Helps eliminate N+1 queries at the resource level
|
|
47
|
+
|
|
48
|
+
### 6. Preload for Serialization Hook (Phase 9)
|
|
49
|
+
- New `preload_for_serialization` class method
|
|
50
|
+
- Called before serialization with all records
|
|
51
|
+
- Enables batch-loading of data needed by `meta` methods
|
|
52
|
+
- Thread-local storage for preloaded data
|
|
53
|
+
|
|
54
|
+
## Usage Examples
|
|
55
|
+
|
|
56
|
+
### Eager Loading DSL
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
class WorkstreamResource < ApplicationResource
|
|
60
|
+
eager_load :conversation, :owners
|
|
61
|
+
|
|
62
|
+
# These associations will be automatically eager-loaded
|
|
63
|
+
# even without an explicit ?include= param
|
|
64
|
+
end
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Preload for Serialization Hook
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
class WorkstreamResource < ApplicationResource
|
|
71
|
+
def self.preload_for_serialization(records, context = {})
|
|
72
|
+
# Batch-load stats for all records
|
|
73
|
+
stats = Preloaders::StatsPreloader.call(records: records)
|
|
74
|
+
stats # Store in preloaded_data for access in meta
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def meta(...)
|
|
78
|
+
stats = self.class.preloaded_data[record.id] || record.loading_stats
|
|
79
|
+
super.merge(loading: stats)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Metric Interpretation
|
|
85
|
+
|
|
86
|
+
- **`resource_loader_find_avg_ms`**: Average time to find a resource class by type
|
|
87
|
+
- **`resource_loader_find_for_model_avg_ms`**: Average time to find a resource class by model
|
|
88
|
+
- **`jsonapi_object_avg_ms`**: Average time to generate the static `jsonapi` object
|
|
89
|
+
- **`serialize_single_user_avg_ms`**: Average time to serialize a single `User` record
|
|
90
|
+
- **`serialize_10_users_avg_ms`**: Average time to serialize a collection of 10 `User` records
|
|
91
|
+
- **`serialize_single_user_query_count`**: Number of SQL queries for single `User` serialization
|
|
92
|
+
- **`serialize_10_users_query_count`**: Number of SQL queries for 10 `User` records serialization
|
|
93
|
+
- **`serialize_single_user_allocations`**: Object allocations for single `User` serialization
|
|
94
|
+
- **`serialize_10_users_allocations`**: Object allocations for 10 `User` records serialization
|
|
95
|
+
- **`relationship_definitions_avg_ms`**: Average time to compute relationship definitions
|
|
96
|
+
- **`resource_instantiation_avg_ms`**: Average time to instantiate a resource
|
|
97
|
+
|
|
98
|
+
## Notes
|
|
99
|
+
|
|
100
|
+
- Query counts remain the same in benchmarks as they test the serializer directly
|
|
101
|
+
- Real-world N+1 reduction comes from the `eager_load` DSL and `preload_for_serialization` hook
|
|
102
|
+
- Allocation reduction is modest but consistent across serialization
|
|
@@ -6,10 +6,9 @@ module JSONAPI
|
|
|
6
6
|
extend ActiveSupport::Concern
|
|
7
7
|
|
|
8
8
|
def preload_includes
|
|
9
|
-
|
|
10
|
-
return if
|
|
9
|
+
includes_hash = build_combined_includes_hash
|
|
10
|
+
return if includes_hash.empty?
|
|
11
11
|
|
|
12
|
-
includes_hash = build_includes_hash(includes)
|
|
13
12
|
preload_resources(includes_hash)
|
|
14
13
|
rescue ActiveRecord::RecordNotFound
|
|
15
14
|
# Will be handled by set_resource
|
|
@@ -17,6 +16,23 @@ module JSONAPI
|
|
|
17
16
|
|
|
18
17
|
private
|
|
19
18
|
|
|
19
|
+
def build_combined_includes_hash
|
|
20
|
+
param_includes = parse_include_param
|
|
21
|
+
dsl_includes = resource_eager_load_associations
|
|
22
|
+
|
|
23
|
+
combined = dsl_includes.map(&:to_s)
|
|
24
|
+
combined.concat(param_includes)
|
|
25
|
+
combined.uniq!
|
|
26
|
+
|
|
27
|
+
build_includes_hash(combined)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def resource_eager_load_associations
|
|
31
|
+
return [] unless resource_class.respond_to?(:eager_load_associations)
|
|
32
|
+
|
|
33
|
+
resource_class.eager_load_associations
|
|
34
|
+
end
|
|
35
|
+
|
|
20
36
|
def build_includes_hash(includes)
|
|
21
37
|
includes.each_with_object({}) do |path, hash|
|
|
22
38
|
merge_include_path(hash, path)
|
|
@@ -24,7 +40,7 @@ module JSONAPI
|
|
|
24
40
|
end
|
|
25
41
|
|
|
26
42
|
def merge_include_path(hash, path)
|
|
27
|
-
parts = path.split(".")
|
|
43
|
+
parts = path.to_s.split(".")
|
|
28
44
|
current = hash
|
|
29
45
|
|
|
30
46
|
parts.each_with_index do |part, i|
|
|
@@ -6,14 +6,29 @@ module JSONAPI
|
|
|
6
6
|
extend ActiveSupport::Concern
|
|
7
7
|
|
|
8
8
|
def serialize_resource(resource)
|
|
9
|
+
run_preload_hook([resource])
|
|
9
10
|
JSONAPI::Serializer.new(resource).to_hash(
|
|
10
11
|
include: parse_include_param,
|
|
11
12
|
fields: parse_fields_param,
|
|
12
13
|
document_meta: jsonapi_document_meta,
|
|
13
14
|
)
|
|
15
|
+
ensure
|
|
16
|
+
clear_preload_data
|
|
14
17
|
end
|
|
15
18
|
|
|
16
19
|
def serialize_collection(resources)
|
|
20
|
+
resources_array = resources.to_a
|
|
21
|
+
run_preload_hook(resources_array)
|
|
22
|
+
|
|
23
|
+
data, all_included = serialize_resources_with_includes(resources_array)
|
|
24
|
+
build_collection_response(data, all_included)
|
|
25
|
+
ensure
|
|
26
|
+
clear_preload_data
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def serialize_resources_with_includes(resources)
|
|
17
32
|
includes = parse_include_param
|
|
18
33
|
fields = parse_fields_param
|
|
19
34
|
all_included = []
|
|
@@ -25,11 +40,9 @@ module JSONAPI
|
|
|
25
40
|
result[:data]
|
|
26
41
|
end
|
|
27
42
|
|
|
28
|
-
|
|
43
|
+
[data, all_included]
|
|
29
44
|
end
|
|
30
45
|
|
|
31
|
-
private
|
|
32
|
-
|
|
33
46
|
def serialize_single(resource, includes, fields)
|
|
34
47
|
JSONAPI::Serializer.new(resource).to_hash(include: includes, fields:, document_meta: nil)
|
|
35
48
|
end
|
|
@@ -58,6 +71,21 @@ module JSONAPI
|
|
|
58
71
|
|
|
59
72
|
result
|
|
60
73
|
end
|
|
74
|
+
|
|
75
|
+
def run_preload_hook(records)
|
|
76
|
+
return unless resource_class.respond_to?(:preload_for_serialization)
|
|
77
|
+
|
|
78
|
+
preloaded = resource_class.preload_for_serialization(records, jsonapi_context)
|
|
79
|
+
resource_class.preloaded_data = preloaded if preloaded.present?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def clear_preload_data
|
|
83
|
+
resource_class.clear_preloaded_data! if resource_class.respond_to?(:clear_preloaded_data!)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def jsonapi_context
|
|
87
|
+
{ current_user: respond_to?(:current_user) ? current_user : nil }
|
|
88
|
+
end
|
|
61
89
|
end
|
|
62
90
|
end
|
|
63
91
|
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSONAPI
|
|
4
|
+
module Resources
|
|
5
|
+
module EagerLoadDsl
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
class_methods do
|
|
9
|
+
# Declare associations to always eager-load for this resource
|
|
10
|
+
# @param associations [Array<Symbol>] list of association names to eager-load
|
|
11
|
+
def eager_load(*associations)
|
|
12
|
+
@declared_eager_loads ||= []
|
|
13
|
+
@declared_eager_loads.concat(associations.map(&:to_sym))
|
|
14
|
+
reset_eager_load_associations!
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Get all eager-load associations including inherited ones
|
|
18
|
+
# @return [Array<Symbol>] frozen array of association names
|
|
19
|
+
def eager_load_associations
|
|
20
|
+
return @eager_load_associations if defined?(@eager_load_associations)
|
|
21
|
+
|
|
22
|
+
@eager_load_associations = build_eager_load_associations.freeze
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def reset_eager_load_associations!
|
|
26
|
+
remove_instance_variable(:@eager_load_associations) if defined?(@eager_load_associations)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
module EagerLoadHelperMethods
|
|
31
|
+
def build_eager_load_associations
|
|
32
|
+
own_associations = @declared_eager_loads || []
|
|
33
|
+
inherited = inherit_eager_load_associations
|
|
34
|
+
(inherited + own_associations).uniq
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def inherit_eager_load_associations
|
|
38
|
+
return [] unless superclass.respond_to?(:eager_load_associations)
|
|
39
|
+
return [] if superclass == JSONAPI::Resource
|
|
40
|
+
|
|
41
|
+
superclass.eager_load_associations
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
included do
|
|
46
|
+
extend EagerLoadHelperMethods
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JSONAPI
|
|
4
|
+
module Resources
|
|
5
|
+
module PreloadDsl
|
|
6
|
+
extend ActiveSupport::Concern
|
|
7
|
+
|
|
8
|
+
class_methods do
|
|
9
|
+
# Override this method in resource subclasses to preload data before serialization.
|
|
10
|
+
# This is called once with all records before serialization starts.
|
|
11
|
+
# Use this to batch-load data needed by meta methods to avoid N+1 queries.
|
|
12
|
+
#
|
|
13
|
+
# @param records [Array<ActiveRecord::Base>] the records that will be serialized
|
|
14
|
+
# @param context [Hash] optional context (e.g., current user, request params)
|
|
15
|
+
# @return [Hash] a hash of preloaded data keyed by record id
|
|
16
|
+
#
|
|
17
|
+
# @example
|
|
18
|
+
# class WorkstreamResource < ApplicationResource
|
|
19
|
+
# def self.preload_for_serialization(records, context = {})
|
|
20
|
+
# stats = Preloaders::StatsPreloader.call(records: records)
|
|
21
|
+
# { stats: stats }
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# def meta(...)
|
|
25
|
+
# preloaded = self.class.preloaded_data[record.id]
|
|
26
|
+
# super.merge(loading: preloaded[:stats])
|
|
27
|
+
# end
|
|
28
|
+
# end
|
|
29
|
+
def preload_for_serialization(_records, _context = {})
|
|
30
|
+
# Default implementation does nothing - override in subclasses
|
|
31
|
+
{}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Storage for preloaded data (request-scoped via Thread.current)
|
|
35
|
+
def preloaded_data
|
|
36
|
+
Thread.current["#{name}_preloaded_data"] ||= {}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def preloaded_data=(data)
|
|
40
|
+
Thread.current["#{name}_preloaded_data"] = data
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def clear_preloaded_data!
|
|
44
|
+
Thread.current["#{name}_preloaded_data"] = nil
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -7,29 +7,30 @@ module JSONAPI
|
|
|
7
7
|
|
|
8
8
|
class_methods do
|
|
9
9
|
def has_one(name, meta: nil, **options)
|
|
10
|
-
@relationships ||= []
|
|
11
10
|
detect_polymorphic(name, options)
|
|
12
|
-
|
|
11
|
+
register_relationship(name:, type: :has_one, meta:, options:)
|
|
13
12
|
end
|
|
14
13
|
|
|
15
14
|
def has_many(name, meta: nil, **options)
|
|
16
|
-
@relationships ||= []
|
|
17
15
|
validate_append_only_options!(options)
|
|
18
16
|
detect_polymorphic(name, options)
|
|
19
|
-
|
|
17
|
+
register_relationship(name:, type: :has_many, meta:, options:)
|
|
20
18
|
end
|
|
21
19
|
|
|
22
20
|
def belongs_to(name, meta: nil, **options)
|
|
23
|
-
@relationships ||= []
|
|
24
21
|
detect_polymorphic(name, options)
|
|
25
|
-
|
|
22
|
+
register_relationship(name:, type: :belongs_to, meta:, options:)
|
|
26
23
|
end
|
|
27
24
|
|
|
28
25
|
def relationship_definitions
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
rels =
|
|
32
|
-
rels.
|
|
26
|
+
return @relationship_definitions if defined?(@relationship_definitions)
|
|
27
|
+
|
|
28
|
+
rels = build_relationship_definitions
|
|
29
|
+
@relationship_definitions = rels.freeze
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def reset_relationship_definitions!
|
|
33
|
+
remove_instance_variable(:@relationship_definitions) if defined?(@relationship_definitions)
|
|
33
34
|
end
|
|
34
35
|
|
|
35
36
|
def relationship_names
|
|
@@ -38,6 +39,19 @@ module JSONAPI
|
|
|
38
39
|
end
|
|
39
40
|
|
|
40
41
|
module RelationshipHelperMethods
|
|
42
|
+
def register_relationship(name:, type:, meta:, options:)
|
|
43
|
+
@relationships ||= []
|
|
44
|
+
@relationships << { name: name.to_sym, type:, meta:, options: }
|
|
45
|
+
reset_relationship_definitions!
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def build_relationship_definitions
|
|
49
|
+
declared_relationships = instance_variable_defined?(:@relationships)
|
|
50
|
+
rels = @relationships || []
|
|
51
|
+
rels = superclass.relationship_definitions + rels if should_inherit_relationships?(declared_relationships)
|
|
52
|
+
rels.uniq { |r| r[:name] }
|
|
53
|
+
end
|
|
54
|
+
|
|
41
55
|
def validate_append_only_options!(options)
|
|
42
56
|
if options[:append_only] && options[:purge_on_nil] == true
|
|
43
57
|
raise ArgumentError, "Cannot use append_only: true with purge_on_nil: true"
|
|
@@ -6,6 +6,8 @@ require_relative "concerns/sortable_fields_dsl"
|
|
|
6
6
|
require_relative "concerns/relationships_dsl"
|
|
7
7
|
require_relative "concerns/meta_dsl"
|
|
8
8
|
require_relative "concerns/model_class_helpers"
|
|
9
|
+
require_relative "concerns/eager_load_dsl"
|
|
10
|
+
require_relative "concerns/preload_dsl"
|
|
9
11
|
|
|
10
12
|
module JSONAPI
|
|
11
13
|
class Resource
|
|
@@ -15,6 +17,8 @@ module JSONAPI
|
|
|
15
17
|
include Resources::RelationshipsDsl
|
|
16
18
|
include Resources::MetaDsl
|
|
17
19
|
include Resources::ModelClassHelpers
|
|
20
|
+
include Resources::EagerLoadDsl
|
|
21
|
+
include Resources::PreloadDsl
|
|
18
22
|
|
|
19
23
|
class << self
|
|
20
24
|
attr_accessor :type_format
|
|
@@ -16,7 +16,25 @@ module JSONAPI
|
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
# Thread-safe caches for resource class lookups
|
|
20
|
+
# Using simple Hash with ||= is safe enough - worst case is duplicate computation
|
|
21
|
+
# which is acceptable since the result is always the same Class object
|
|
22
|
+
@find_cache = {}
|
|
23
|
+
@model_cache = {}
|
|
24
|
+
|
|
25
|
+
class << self
|
|
26
|
+
def clear_cache!
|
|
27
|
+
@find_cache = {}
|
|
28
|
+
@model_cache = {}
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
19
32
|
def self.find(resource_type, namespace: nil)
|
|
33
|
+
cache_key = "#{namespace}::#{resource_type}"
|
|
34
|
+
@find_cache[cache_key] ||= find_uncached(resource_type, namespace:)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.find_uncached(resource_type, namespace: nil)
|
|
20
38
|
return find_namespaced(resource_type, namespace) if namespace.present?
|
|
21
39
|
|
|
22
40
|
find_flat(resource_type, namespace)
|
|
@@ -48,7 +66,8 @@ module JSONAPI
|
|
|
48
66
|
def self.find_for_model(model_class, namespace: nil)
|
|
49
67
|
return ActiveStorageBlobResource if active_storage_blob?(model_class)
|
|
50
68
|
|
|
51
|
-
|
|
69
|
+
cache_key = "#{namespace}::#{model_class.name}"
|
|
70
|
+
@model_cache[cache_key] ||= find_resource_for_model(model_class, namespace)
|
|
52
71
|
end
|
|
53
72
|
|
|
54
73
|
def self.find_resource_for_model(model_class, namespace)
|
|
@@ -17,12 +17,22 @@ module JSONAPI
|
|
|
17
17
|
|
|
18
18
|
JSONAPI_VERSION = "1.1"
|
|
19
19
|
|
|
20
|
+
@jsonapi_object = nil
|
|
21
|
+
|
|
20
22
|
def self.jsonapi_object
|
|
23
|
+
@jsonapi_object ||= build_jsonapi_object.freeze
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def self.build_jsonapi_object
|
|
21
27
|
obj = { version: JSONAPI_VERSION }
|
|
22
28
|
obj[:meta] = JSONAPI.configuration.jsonapi_meta if JSONAPI.configuration.jsonapi_meta
|
|
23
29
|
obj
|
|
24
30
|
end
|
|
25
31
|
|
|
32
|
+
def self.reset_jsonapi_object!
|
|
33
|
+
@jsonapi_object = nil
|
|
34
|
+
end
|
|
35
|
+
|
|
26
36
|
def initialize(record, definition: nil, base_definition: nil)
|
|
27
37
|
@record = record
|
|
28
38
|
@definition = definition || ResourceLoader.find_for_model(record.class)
|
|
@@ -30,10 +30,8 @@ module JSONAPI
|
|
|
30
30
|
|
|
31
31
|
def execute
|
|
32
32
|
@scope = apply_filtering
|
|
33
|
-
|
|
34
|
-
@total_count = @scope.count unless has_virtual_sort
|
|
33
|
+
compute_total_count_if_needed
|
|
35
34
|
@scope = apply_sorting(@scope)
|
|
36
|
-
@total_count = @scope.count if has_virtual_sort && @scope.is_a?(Array)
|
|
37
35
|
@scope = apply_pagination
|
|
38
36
|
self
|
|
39
37
|
end
|
|
@@ -42,6 +40,14 @@ module JSONAPI
|
|
|
42
40
|
|
|
43
41
|
attr_reader :definition, :model_class, :filter_params, :sort_params, :page_params
|
|
44
42
|
|
|
43
|
+
def compute_total_count_if_needed
|
|
44
|
+
return unless pagination_applied
|
|
45
|
+
|
|
46
|
+
has_virtual_sort = sort_params.any? { |sort_field| virtual_attribute_sort?(sort_field) }
|
|
47
|
+
@total_count = @scope.count unless has_virtual_sort
|
|
48
|
+
@total_count = @scope.count if has_virtual_sort && @scope.is_a?(Array)
|
|
49
|
+
end
|
|
50
|
+
|
|
45
51
|
def apply_filtering
|
|
46
52
|
scope = apply_nested_relationship_filters(@scope)
|
|
47
53
|
apply_regular_filters(scope)
|
data/lib/json_api/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jpie
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Emil Kampp
|
|
@@ -64,6 +64,7 @@ files:
|
|
|
64
64
|
- ".travis.yml"
|
|
65
65
|
- Gemfile
|
|
66
66
|
- Gemfile.lock
|
|
67
|
+
- PERFORMANCE_BASELINE.md
|
|
67
68
|
- README.md
|
|
68
69
|
- Rakefile
|
|
69
70
|
- bin/console
|
|
@@ -105,9 +106,11 @@ files:
|
|
|
105
106
|
- lib/json_api/railtie.rb
|
|
106
107
|
- lib/json_api/resources/active_storage_blob_resource.rb
|
|
107
108
|
- lib/json_api/resources/concerns/attributes_dsl.rb
|
|
109
|
+
- lib/json_api/resources/concerns/eager_load_dsl.rb
|
|
108
110
|
- lib/json_api/resources/concerns/filters_dsl.rb
|
|
109
111
|
- lib/json_api/resources/concerns/meta_dsl.rb
|
|
110
112
|
- lib/json_api/resources/concerns/model_class_helpers.rb
|
|
113
|
+
- lib/json_api/resources/concerns/preload_dsl.rb
|
|
111
114
|
- lib/json_api/resources/concerns/relationships_dsl.rb
|
|
112
115
|
- lib/json_api/resources/concerns/sortable_fields_dsl.rb
|
|
113
116
|
- lib/json_api/resources/resource.rb
|