jsonapi-resources 0.10.0.beta2 → 0.10.0.beta2.1
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 +5 -5
- data/lib/jsonapi-resources.rb +2 -1
- data/lib/jsonapi/active_relation_resource_finder.rb +117 -130
- data/lib/jsonapi/active_relation_resource_finder/join_manager.rb +297 -0
- data/lib/jsonapi/cached_response_fragment.rb +66 -49
- data/lib/jsonapi/path.rb +8 -0
- data/lib/jsonapi/path_segment.rb +17 -5
- data/lib/jsonapi/relationship.rb +1 -0
- data/lib/jsonapi/resource_set.rb +118 -48
- data/lib/jsonapi/resources/railtie.rb +9 -0
- data/lib/jsonapi/resources/version.rb +1 -1
- data/lib/tasks/check_upgrade.rake +52 -0
- metadata +6 -5
- data/lib/jsonapi/active_relation_resource_finder/join_tree.rb +0 -227
data/lib/jsonapi/path_segment.rb
CHANGED
@@ -22,19 +22,27 @@ module JSONAPI
|
|
22
22
|
end
|
23
23
|
|
24
24
|
class Relationship
|
25
|
-
attr_reader :relationship
|
25
|
+
attr_reader :relationship, :resource_klass
|
26
26
|
|
27
|
-
def initialize(relationship:, resource_klass:)
|
27
|
+
def initialize(relationship:, resource_klass: nil)
|
28
28
|
@relationship = relationship
|
29
29
|
@resource_klass = resource_klass
|
30
30
|
end
|
31
31
|
|
32
|
+
def eql?(other)
|
33
|
+
relationship == other.relationship && resource_klass == other.resource_klass
|
34
|
+
end
|
35
|
+
|
36
|
+
def hash
|
37
|
+
[relationship, resource_klass].hash
|
38
|
+
end
|
39
|
+
|
32
40
|
def to_s
|
33
|
-
@resource_klass ? "#{relationship.name}##{resource_klass._type}" : "#{relationship.name}"
|
41
|
+
@resource_klass ? "#{relationship.parent_resource_klass._type}.#{relationship.name}##{resource_klass._type}" : "#{resource_klass._type}.#{relationship.name}"
|
34
42
|
end
|
35
43
|
|
36
44
|
def resource_klass
|
37
|
-
@resource_klass ||
|
45
|
+
@resource_klass || relationship.resource_klass
|
38
46
|
end
|
39
47
|
|
40
48
|
def path_specified_resource_klass?
|
@@ -50,13 +58,17 @@ module JSONAPI
|
|
50
58
|
@field_name = field_name
|
51
59
|
end
|
52
60
|
|
61
|
+
def eql?(other)
|
62
|
+
field_name == other.field_name && resource_klass == other.resource_klass
|
63
|
+
end
|
64
|
+
|
53
65
|
def delegated_field_name
|
54
66
|
resource_klass._attribute_delegated_name(field_name)
|
55
67
|
end
|
56
68
|
|
57
69
|
def to_s
|
58
70
|
# :nocov:
|
59
|
-
field_name.to_s
|
71
|
+
"#{resource_klass._type}.#{field_name.to_s}"
|
60
72
|
# :nocov:
|
61
73
|
end
|
62
74
|
end
|
data/lib/jsonapi/relationship.rb
CHANGED
data/lib/jsonapi/resource_set.rb
CHANGED
@@ -5,14 +5,25 @@ module JSONAPI
|
|
5
5
|
|
6
6
|
attr_reader :resource_klasses, :populated
|
7
7
|
|
8
|
-
def initialize(resource_id_tree)
|
8
|
+
def initialize(resource_id_tree = nil)
|
9
9
|
@populated = false
|
10
|
-
@resource_klasses = flatten_resource_id_tree(resource_id_tree)
|
10
|
+
@resource_klasses = resource_id_tree.nil? ? {} : flatten_resource_id_tree(resource_id_tree)
|
11
11
|
end
|
12
12
|
|
13
13
|
def populate!(serializer, context, find_options)
|
14
|
+
# For each resource klass we want to generate the caching key
|
15
|
+
|
16
|
+
# Hash for collecting types and ids
|
17
|
+
# @type [Hash<Class<Resource>, Id[]]]
|
18
|
+
missed_resource_ids = {}
|
19
|
+
|
20
|
+
# Array for collecting CachedResponseFragment::Lookups
|
21
|
+
# @type [Lookup[]]
|
22
|
+
lookups = []
|
23
|
+
|
24
|
+
|
25
|
+
# Step One collect all of the lookups for the cache, or keys that don't require cache access
|
14
26
|
@resource_klasses.each_key do |resource_klass|
|
15
|
-
missed_ids = []
|
16
27
|
|
17
28
|
serializer_config_key = serializer.config_key(resource_klass).gsub("/", "_")
|
18
29
|
context_json = resource_klass.attribute_caching_context(context).to_json
|
@@ -20,65 +31,124 @@ module JSONAPI
|
|
20
31
|
context_key = "ATTR-CTX-#{context_b64.gsub("/", "_")}"
|
21
32
|
|
22
33
|
if resource_klass.caching?
|
23
|
-
cache_ids = []
|
24
|
-
|
25
|
-
@resource_klasses[resource_klass].each_pair do |k, v|
|
34
|
+
cache_ids = @resource_klasses[resource_klass].map do |(k, v)|
|
26
35
|
# Store the hashcode of the cache_field to avoid storing objects and to ensure precision isn't lost
|
27
36
|
# on timestamp types (i.e. string conversions dropping milliseconds)
|
28
|
-
|
37
|
+
[k, resource_klass.hash_cache_field(v[:cache_id])]
|
29
38
|
end
|
30
39
|
|
31
|
-
|
40
|
+
lookups.push(
|
41
|
+
CachedResponseFragment::Lookup.new(
|
32
42
|
resource_klass,
|
33
43
|
serializer_config_key,
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
if resource.nil?
|
40
|
-
missed_ids.push(found_result[0])
|
41
|
-
else
|
42
|
-
@resource_klasses[resource_klass][resource.id][:resource] = resource
|
43
|
-
end
|
44
|
-
end
|
44
|
+
context,
|
45
|
+
context_key,
|
46
|
+
cache_ids
|
47
|
+
)
|
48
|
+
)
|
45
49
|
else
|
46
|
-
|
50
|
+
missed_resource_ids[resource_klass] ||= {}
|
51
|
+
missed_resource_ids[resource_klass] = @resource_klasses[resource_klass].keys
|
47
52
|
end
|
53
|
+
end
|
54
|
+
|
55
|
+
if lookups.any?
|
56
|
+
raise "You've declared some Resources as caching without providing a caching store" if JSONAPI.configuration.resource_cache.nil?
|
57
|
+
|
58
|
+
# Step Two execute the cache lookup
|
59
|
+
found_resources = CachedResponseFragment.lookup(lookups, context)
|
60
|
+
else
|
61
|
+
found_resources = {}
|
62
|
+
end
|
48
63
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
relationship_data)
|
69
|
-
|
70
|
-
@resource_klasses[resource_klass][id][:resource] = cr
|
71
|
-
else
|
72
|
-
@resource_klasses[resource_klass][resource.id][:resource] = resource
|
73
|
-
end
|
64
|
+
|
65
|
+
# Step Three collect the results and collect hit/miss stats
|
66
|
+
stats = {}
|
67
|
+
found_resources.each do |resource_klass, resources|
|
68
|
+
resources.each do |id, cached_resource|
|
69
|
+
stats[resource_klass] ||= {}
|
70
|
+
|
71
|
+
if cached_resource.nil?
|
72
|
+
stats[resource_klass][:misses] ||= 0
|
73
|
+
stats[resource_klass][:misses] += 1
|
74
|
+
|
75
|
+
# Collect misses
|
76
|
+
missed_resource_ids[resource_klass] ||= []
|
77
|
+
missed_resource_ids[resource_klass].push(id)
|
78
|
+
else
|
79
|
+
stats[resource_klass][:hits] ||= 0
|
80
|
+
stats[resource_klass][:hits] += 1
|
81
|
+
|
82
|
+
register_resource(resource_klass, cached_resource)
|
74
83
|
end
|
75
84
|
end
|
76
85
|
end
|
77
|
-
|
86
|
+
|
87
|
+
report_stats(stats)
|
88
|
+
|
89
|
+
writes = []
|
90
|
+
|
91
|
+
# Step Four find any of the missing resources and join them into the result
|
92
|
+
missed_resource_ids.each_pair do |resource_klass, ids|
|
93
|
+
find_opts = {context: context, fields: find_options[:fields]}
|
94
|
+
found_resources = resource_klass.find_by_keys(ids, find_opts)
|
95
|
+
|
96
|
+
found_resources.each do |resource|
|
97
|
+
relationship_data = @resource_klasses[resource_klass][resource.id][:relationships]
|
98
|
+
|
99
|
+
if resource_klass.caching?
|
100
|
+
|
101
|
+
serializer_config_key = serializer.config_key(resource_klass).gsub("/", "_")
|
102
|
+
context_json = resource_klass.attribute_caching_context(context).to_json
|
103
|
+
context_b64 = JSONAPI.configuration.resource_cache_digest_function.call(context_json)
|
104
|
+
context_key = "ATTR-CTX-#{context_b64.gsub("/", "_")}"
|
105
|
+
|
106
|
+
writes.push(CachedResponseFragment::Write.new(
|
107
|
+
resource_klass,
|
108
|
+
resource,
|
109
|
+
serializer,
|
110
|
+
serializer_config_key,
|
111
|
+
context,
|
112
|
+
context_key,
|
113
|
+
relationship_data
|
114
|
+
))
|
115
|
+
end
|
116
|
+
|
117
|
+
register_resource(resource_klass, resource)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Step Five conditionally write to the cache
|
122
|
+
CachedResponseFragment.write(writes) unless JSONAPI.configuration.resource_cache.nil?
|
123
|
+
|
124
|
+
mark_populated!
|
78
125
|
self
|
79
126
|
end
|
80
127
|
|
128
|
+
def mark_populated!
|
129
|
+
@populated = true
|
130
|
+
end
|
131
|
+
|
132
|
+
def register_resource(resource_klass, resource, primary = false)
|
133
|
+
@resource_klasses[resource_klass] ||= {}
|
134
|
+
@resource_klasses[resource_klass][resource.id] ||= {primary: resource.try(:primary) || primary, relationships: {}}
|
135
|
+
@resource_klasses[resource_klass][resource.id][:resource] = resource
|
136
|
+
end
|
137
|
+
|
81
138
|
private
|
139
|
+
|
140
|
+
def report_stats(stats)
|
141
|
+
return unless JSONAPI.configuration.resource_cache_usage_report_function || JSONAPI.configuration.resource_cache.nil?
|
142
|
+
|
143
|
+
stats.each_pair do |resource_klass, stat|
|
144
|
+
JSONAPI.configuration.resource_cache_usage_report_function.call(
|
145
|
+
resource_klass.name,
|
146
|
+
stat[:hits] || 0,
|
147
|
+
stat[:misses] || 0
|
148
|
+
)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
82
152
|
def flatten_resource_id_tree(resource_id_tree, flattened_tree = {})
|
83
153
|
resource_id_tree.fragments.each_pair do |resource_rid, fragment|
|
84
154
|
|
@@ -87,7 +157,7 @@ module JSONAPI
|
|
87
157
|
|
88
158
|
flattened_tree[resource_klass] ||= {}
|
89
159
|
|
90
|
-
flattened_tree[resource_klass][id] ||= {
|
160
|
+
flattened_tree[resource_klass][id] ||= {primary: fragment.primary, relationships: {}}
|
91
161
|
flattened_tree[resource_klass][id][:cache_id] ||= fragment.cache
|
92
162
|
|
93
163
|
fragment.related.try(:each_pair) do |relationship_name, related_rids|
|
@@ -104,4 +174,4 @@ module JSONAPI
|
|
104
174
|
flattened_tree
|
105
175
|
end
|
106
176
|
end
|
107
|
-
end
|
177
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'jsonapi-resources'
|
3
|
+
|
4
|
+
namespace :jsonapi do
|
5
|
+
namespace :resources do
|
6
|
+
desc 'Checks application for orphaned overrides'
|
7
|
+
task :check_upgrade => :environment do
|
8
|
+
Rails.application.eager_load!
|
9
|
+
|
10
|
+
resource_klasses = ObjectSpace.each_object(Class).select { |klass| klass < JSONAPI::Resource}
|
11
|
+
|
12
|
+
puts "Checking #{resource_klasses.count} resources"
|
13
|
+
|
14
|
+
issues_found = 0
|
15
|
+
|
16
|
+
klasses_with_deprecated = resource_klasses.select { |klass| klass.methods.include?(:find_records) }
|
17
|
+
unless klasses_with_deprecated.empty?
|
18
|
+
puts " Found the following resources the still implement `find_records`:"
|
19
|
+
klasses_with_deprecated.each { |klass| puts " #{klass}"}
|
20
|
+
puts " The `find_records` method is no longer called by JR. Please review and ensure your functionality is ported over."
|
21
|
+
|
22
|
+
issues_found = issues_found + klasses_with_deprecated.length
|
23
|
+
end
|
24
|
+
|
25
|
+
klasses_with_deprecated = resource_klasses.select { |klass| klass.methods.include?(:records_for) }
|
26
|
+
unless klasses_with_deprecated.empty?
|
27
|
+
puts " Found the following resources the still implement `records_for`:"
|
28
|
+
klasses_with_deprecated.each { |klass| puts " #{klass}"}
|
29
|
+
puts " The `records_for` method is no longer called by JR. Please review and ensure your functionality is ported over."
|
30
|
+
|
31
|
+
issues_found = issues_found + klasses_with_deprecated.length
|
32
|
+
end
|
33
|
+
|
34
|
+
klasses_with_deprecated = resource_klasses.select { |klass| klass.methods.include?(:apply_includes) }
|
35
|
+
unless klasses_with_deprecated.empty?
|
36
|
+
puts " Found the following resources the still implement `apply_includes`:"
|
37
|
+
klasses_with_deprecated.each { |klass| puts " #{klass}"}
|
38
|
+
puts " The `apply_includes` method is no longer called by JR. Please review and ensure your functionality is ported over."
|
39
|
+
|
40
|
+
issues_found = issues_found + klasses_with_deprecated.length
|
41
|
+
end
|
42
|
+
|
43
|
+
if issues_found > 0
|
44
|
+
puts "Finished inspection. #{issues_found} issues found that may impact upgrading. Please address these issues. "
|
45
|
+
else
|
46
|
+
puts "Finished inspection with no issues found. Note this is only a cursory check for method overrides that will no \n" \
|
47
|
+
"longer be called by JSONAPI::Resources. This check in no way assures your code will continue to function as \n" \
|
48
|
+
"it did before the upgrade. Please do adequate testing before using in production."
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jsonapi-resources
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.10.0.beta2
|
4
|
+
version: 0.10.0.beta2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dan Gebhardt
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2019-02-
|
12
|
+
date: 2019-02-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -178,7 +178,7 @@ files:
|
|
178
178
|
- lib/jsonapi-resources.rb
|
179
179
|
- lib/jsonapi/active_relation_resource_finder.rb
|
180
180
|
- lib/jsonapi/active_relation_resource_finder/adapters/join_left_active_record_adapter.rb
|
181
|
-
- lib/jsonapi/active_relation_resource_finder/
|
181
|
+
- lib/jsonapi/active_relation_resource_finder/join_manager.rb
|
182
182
|
- lib/jsonapi/acts_as_resource_controller.rb
|
183
183
|
- lib/jsonapi/cached_response_fragment.rb
|
184
184
|
- lib/jsonapi/callbacks.rb
|
@@ -208,9 +208,11 @@ files:
|
|
208
208
|
- lib/jsonapi/resource_identity.rb
|
209
209
|
- lib/jsonapi/resource_serializer.rb
|
210
210
|
- lib/jsonapi/resource_set.rb
|
211
|
+
- lib/jsonapi/resources/railtie.rb
|
211
212
|
- lib/jsonapi/resources/version.rb
|
212
213
|
- lib/jsonapi/response_document.rb
|
213
214
|
- lib/jsonapi/routing_ext.rb
|
215
|
+
- lib/tasks/check_upgrade.rake
|
214
216
|
homepage: https://github.com/cerebris/jsonapi-resources
|
215
217
|
licenses:
|
216
218
|
- MIT
|
@@ -230,8 +232,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
230
232
|
- !ruby/object:Gem::Version
|
231
233
|
version: 1.3.1
|
232
234
|
requirements: []
|
233
|
-
|
234
|
-
rubygems_version: 2.6.14.3
|
235
|
+
rubygems_version: 3.0.1
|
235
236
|
signing_key:
|
236
237
|
specification_version: 4
|
237
238
|
summary: Easily support JSON API in Rails.
|
@@ -1,227 +0,0 @@
|
|
1
|
-
module JSONAPI
|
2
|
-
module ActiveRelationResourceFinder
|
3
|
-
class JoinTree
|
4
|
-
# Stores relationship paths starting from the resource_klass. This allows consolidation of duplicate paths from
|
5
|
-
# relationships, filters and sorts. This enables the determination of table aliases as they are joined.
|
6
|
-
|
7
|
-
attr_reader :resource_klass, :options, :source_relationship, :resource_joins, :joins
|
8
|
-
|
9
|
-
def initialize(resource_klass:,
|
10
|
-
options: {},
|
11
|
-
source_relationship: nil,
|
12
|
-
relationships: nil,
|
13
|
-
filters: nil,
|
14
|
-
sort_criteria: nil)
|
15
|
-
|
16
|
-
@resource_klass = resource_klass
|
17
|
-
@options = options
|
18
|
-
|
19
|
-
@resource_joins = {
|
20
|
-
root: {
|
21
|
-
join_type: :root,
|
22
|
-
resource_klasses: {
|
23
|
-
resource_klass => {
|
24
|
-
relationships: {}
|
25
|
-
}
|
26
|
-
}
|
27
|
-
}
|
28
|
-
}
|
29
|
-
add_source_relationship(source_relationship)
|
30
|
-
add_sort_criteria(sort_criteria)
|
31
|
-
add_filters(filters)
|
32
|
-
add_relationships(relationships)
|
33
|
-
|
34
|
-
@joins = {}
|
35
|
-
construct_joins(@resource_joins)
|
36
|
-
end
|
37
|
-
|
38
|
-
private
|
39
|
-
|
40
|
-
def add_join(path, default_type = :inner, default_polymorphic_join_type = :left)
|
41
|
-
if source_relationship
|
42
|
-
if source_relationship.polymorphic?
|
43
|
-
# Polymorphic paths will come it with the resource_type as the first segment (for example `#documents.comments`)
|
44
|
-
# We just need to prepend the relationship portion the
|
45
|
-
sourced_path = "#{source_relationship.name}#{path}"
|
46
|
-
else
|
47
|
-
sourced_path = "#{source_relationship.name}.#{path}"
|
48
|
-
end
|
49
|
-
else
|
50
|
-
sourced_path = path
|
51
|
-
end
|
52
|
-
|
53
|
-
join_tree, _field = parse_path_to_tree(sourced_path, resource_klass, default_type, default_polymorphic_join_type)
|
54
|
-
|
55
|
-
@resource_joins[:root].deep_merge!(join_tree) { |key, val, other_val|
|
56
|
-
if key == :join_type
|
57
|
-
if val == other_val
|
58
|
-
val
|
59
|
-
else
|
60
|
-
:inner
|
61
|
-
end
|
62
|
-
end
|
63
|
-
}
|
64
|
-
end
|
65
|
-
|
66
|
-
def process_path_to_tree(path_segments, resource_klass, default_join_type, default_polymorphic_join_type)
|
67
|
-
node = {
|
68
|
-
resource_klasses: {
|
69
|
-
resource_klass => {
|
70
|
-
relationships: {}
|
71
|
-
}
|
72
|
-
}
|
73
|
-
}
|
74
|
-
|
75
|
-
segment = path_segments.shift
|
76
|
-
|
77
|
-
if segment.is_a?(PathSegment::Relationship)
|
78
|
-
node[:resource_klasses][resource_klass][:relationships][segment.relationship] ||= {}
|
79
|
-
|
80
|
-
# join polymorphic as left joins
|
81
|
-
node[:resource_klasses][resource_klass][:relationships][segment.relationship][:join_type] ||=
|
82
|
-
segment.relationship.polymorphic? ? default_polymorphic_join_type : default_join_type
|
83
|
-
|
84
|
-
segment.relationship.resource_types.each do |related_resource_type|
|
85
|
-
related_resource_klass = resource_klass.resource_klass_for(related_resource_type)
|
86
|
-
|
87
|
-
# If the resource type was specified in the path segment we want to only process the next segments for
|
88
|
-
# that resource type, otherwise process for all
|
89
|
-
process_all_types = !segment.path_specified_resource_klass?
|
90
|
-
|
91
|
-
if process_all_types || related_resource_klass == segment.resource_klass
|
92
|
-
related_resource_tree = process_path_to_tree(path_segments.dup, related_resource_klass, default_join_type, default_polymorphic_join_type)
|
93
|
-
node[:resource_klasses][resource_klass][:relationships][segment.relationship].deep_merge!(related_resource_tree)
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
node
|
98
|
-
end
|
99
|
-
|
100
|
-
def parse_path_to_tree(path_string, resource_klass, default_join_type = :inner, default_polymorphic_join_type = :left)
|
101
|
-
path = JSONAPI::Path.new(resource_klass: resource_klass, path_string: path_string)
|
102
|
-
field = path.segments[-1]
|
103
|
-
return process_path_to_tree(path.segments, resource_klass, default_join_type, default_polymorphic_join_type), field
|
104
|
-
end
|
105
|
-
|
106
|
-
def add_source_relationship(source_relationship)
|
107
|
-
@source_relationship = source_relationship
|
108
|
-
|
109
|
-
if @source_relationship
|
110
|
-
resource_klasses = {}
|
111
|
-
source_relationship.resource_types.each do |related_resource_type|
|
112
|
-
related_resource_klass = resource_klass.resource_klass_for(related_resource_type)
|
113
|
-
resource_klasses[related_resource_klass] = {relationships: {}}
|
114
|
-
end
|
115
|
-
|
116
|
-
join_type = source_relationship.polymorphic? ? :left : :inner
|
117
|
-
|
118
|
-
@resource_joins[:root][:resource_klasses][resource_klass][:relationships][@source_relationship] = {
|
119
|
-
source: true, resource_klasses: resource_klasses, join_type: join_type
|
120
|
-
}
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
def add_filters(filters)
|
125
|
-
return if filters.blank?
|
126
|
-
filters.each_key do |filter|
|
127
|
-
# Do not add joins for filters with an apply callable. This can be overridden by setting perform_joins to true
|
128
|
-
next if resource_klass._allowed_filters[filter].try(:[], :apply) &&
|
129
|
-
!resource_klass._allowed_filters[filter].try(:[], :perform_joins)
|
130
|
-
|
131
|
-
add_join(filter)
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
def add_sort_criteria(sort_criteria)
|
136
|
-
return if sort_criteria.blank?
|
137
|
-
|
138
|
-
sort_criteria.each do |sort|
|
139
|
-
add_join(sort[:field], :left)
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
def add_relationships(relationships)
|
144
|
-
return if relationships.blank?
|
145
|
-
relationships.each do |relationship|
|
146
|
-
add_join(relationship, :left)
|
147
|
-
end
|
148
|
-
end
|
149
|
-
|
150
|
-
# Create a nested set of hashes from an array of path components. This will be used by the `join` methods.
|
151
|
-
# [post, comments] => { post: { comments: {} }
|
152
|
-
def relation_join_hash(path, path_hash = {})
|
153
|
-
relation = path.shift
|
154
|
-
if relation
|
155
|
-
path_hash[relation] = {}
|
156
|
-
relation_join_hash(path, path_hash[relation])
|
157
|
-
end
|
158
|
-
path_hash
|
159
|
-
end
|
160
|
-
|
161
|
-
# Returns the paths from shortest to longest, allowing the capture of the table alias for earlier paths. For
|
162
|
-
# example posts, posts.comments and then posts.comments.author joined in that order will allow each
|
163
|
-
# alias to be determined whereas just joining posts.comments.author will only record the author alias.
|
164
|
-
# ToDo: Dependence on this specialized logic should be removed in the future, if possible.
|
165
|
-
def construct_joins(node, current_relation_path = [], current_relationship_path = [])
|
166
|
-
node.each do |relationship, relationship_details|
|
167
|
-
join_type = relationship_details[:join_type]
|
168
|
-
if relationship == :root
|
169
|
-
@joins[:root] = {alias: resource_klass._table_name, join_type: :root}
|
170
|
-
|
171
|
-
# alias to the default table unless a source_relationship is specified
|
172
|
-
unless source_relationship
|
173
|
-
@joins[''] = {alias: resource_klass._table_name, join_type: :root}
|
174
|
-
end
|
175
|
-
|
176
|
-
return construct_joins(relationship_details[:resource_klasses].values[0][:relationships],
|
177
|
-
current_relation_path,
|
178
|
-
current_relationship_path)
|
179
|
-
end
|
180
|
-
|
181
|
-
relationship_details[:resource_klasses].each do |resource_klass, resource_details|
|
182
|
-
if relationship.polymorphic? && relationship.belongs_to?
|
183
|
-
current_relationship_path << "#{relationship.name.to_s}##{resource_klass._type.to_s}"
|
184
|
-
relation_name = resource_klass._type.to_s.singularize
|
185
|
-
else
|
186
|
-
current_relationship_path << relationship.name.to_s
|
187
|
-
relation_name = relationship.relation_name(options).to_s
|
188
|
-
end
|
189
|
-
|
190
|
-
current_relation_path << relation_name
|
191
|
-
|
192
|
-
rel_path = calc_path_string(current_relationship_path)
|
193
|
-
|
194
|
-
@joins[rel_path] = {
|
195
|
-
alias: nil,
|
196
|
-
join_type: join_type,
|
197
|
-
relation_join_hash: relation_join_hash(current_relation_path.dup)
|
198
|
-
}
|
199
|
-
|
200
|
-
construct_joins(resource_details[:relationships],
|
201
|
-
current_relation_path.dup,
|
202
|
-
current_relationship_path.dup)
|
203
|
-
|
204
|
-
current_relation_path.pop
|
205
|
-
current_relationship_path.pop
|
206
|
-
end
|
207
|
-
end
|
208
|
-
end
|
209
|
-
|
210
|
-
def calc_path_string(path_array)
|
211
|
-
if source_relationship
|
212
|
-
if source_relationship.polymorphic?
|
213
|
-
_relationship_name, resource_name = path_array[0].split('#', 2)
|
214
|
-
path = path_array.dup
|
215
|
-
path[0] = "##{resource_name}"
|
216
|
-
else
|
217
|
-
path = path_array.dup.drop(1)
|
218
|
-
end
|
219
|
-
else
|
220
|
-
path = path_array.dup
|
221
|
-
end
|
222
|
-
|
223
|
-
path.join('.')
|
224
|
-
end
|
225
|
-
end
|
226
|
-
end
|
227
|
-
end
|