Skip to content

Commit d7ac8d9

Browse files
committed
Remove taggings and polymorphic from references
Now that we've moved form references.includes to preload and eager_load, They are respecting the difference between those 2. So we can pass polymorphic to preload and it won't blow up. We can pass taggings to preload and it won't join and bring back too much data. This commit updates our ar_references methods to filter out polymorphic and taggings from an includes hash. Also removed virtual attributes since we can't access ruby virtual attributes in sql (and sql virtual attributes are sub-selects so they shouldn't be in an includes anyway) We can drop the PolymorphicError issues (handled in rbac filterer and report generator) We could drop `includes_to_references` support but it is hanging around for legacy purposes and can be dropped soon.
1 parent 0d1b816 commit d7ac8d9

File tree

5 files changed

+67
-75
lines changed

5 files changed

+67
-75
lines changed

app/models/miq_report/generator.rb

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -88,27 +88,12 @@ def get_include
8888
include_as_hash(include.presence || invent_report_includes)
8989
end
9090

91-
def polymorphic_includes
92-
@polymorphic_includes ||= begin
93-
top_level_rels = Array(get_include.try(:keys)) + Array(get_include_for_find.try(:keys))
94-
95-
top_level_rels.uniq.each_with_object([]) do |assoc, polymorphic_rels|
96-
reflection = db_class.reflect_on_association(assoc)
97-
polymorphic_rels << assoc if reflection && reflection.polymorphic?
98-
end
99-
end
100-
end
101-
10291
def get_ar_includes
103-
polymorphic_includes.each_with_object(get_include_for_find.dup) do |key, includes|
104-
includes.delete(key)
105-
end
92+
include_for_find
10693
end
10794

10895
def get_ar_references
109-
polymorphic_includes.each_with_object(get_include.dup) do |key, includes|
110-
includes.delete(key)
111-
end
96+
db_class.prune_references(get_include)
11297
end
11398

11499
def invent_includes

lib/extensions/ar_references.rb

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
module ArReferences
2-
# Given a nested hash of associations (used by includes)
3-
# convert into an array of table names (used by references)
4-
# If given an array of table names, will output the same array
5-
def includes_to_references(inc)
6-
return [] unless inc
2+
# Tags bring back too many rows, since we can not limit on the particular tag
3+
# So dont require them (meaning we don't join to the taggings table)
4+
SKIP_TABLES = [:tags, :taggings].freeze
75

8-
inc = Array(inc) unless inc.kind_of?(Hash)
9-
inc.flat_map do |n, v|
10-
if (ref = reflect_on_association(n.to_sym)) && !ref.polymorphic?
11-
n_table = ref.table_name
12-
v_tables = v ? ref.klass.try(:includes_to_references, v) : []
13-
[n_table] + v_tables
14-
elsif reflection_with_virtual(n.to_sym) # ignore polymorphic and virtual attribute
15-
[]
16-
else # it is probably a table name - keep it
17-
n
6+
# Given a nested hash of associations (used by includes, preload, and eager_load)
7+
# prune out polymorphic, and references to tags
8+
def prune_references(inc)
9+
return {} unless inc
10+
11+
inc = Array(inc) unless inc.kind_of?(Hash) || inc.kind_of?(Array)
12+
inc.each_with_object({}) do |(n, v), ret|
13+
n = n.to_sym
14+
if (ref = reflect_on_association(n))
15+
if !ref.polymorphic? && !SKIP_TABLES.include?(n)
16+
ret[n] = (v.present? && ref.klass.try(:prune_references, v)) || {}
17+
end
18+
# ignore virtual collections and virtual attribute
19+
elsif !reflection_with_virtual(n) && !virtual_attribute?(n)
20+
# Think this is an error. letting it slide (assuming it will throw an error elsewhere)
21+
ret[n] = {}
1822
end
1923
end
2024
end

lib/rbac/filterer.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,12 +325,15 @@ def search(options = {})
325325
if inline_view?(options, scope)
326326
inner_scope = scope.except(:select, :includes, :references, :eager_load, :preload)
327327
# similar to include_references but using joins
328-
# TODO: optimization: Can we remove these from the outer query?
328+
# TODO: optimization: Can we remove these references from the outer query?
329+
# To do this, we may need to introduce join() to complete the triad: include/reference/join
329330
inner_scope = add_joins(klass, inner_scope, references)
331+
# TODO: do we want to klass.prune_references(exp_includes)? (see also include_references)
330332
inner_scope = add_joins(klass, inner_scope, exp_includes)
331333
if inner_scope.order_values.present?
332334
inner_scope = apply_select(klass, inner_scope, select_from_order_columns(inner_scope.order_values))
333335
end
336+
# TODO: Convert from(inner_scope.to_sql) to from(inner_scope)
334337
scope = scope.from(Arel.sql("(#{inner_scope.to_sql})").as(scope.table_name))
335338
.except(:offset, :limit, :where)
336339

@@ -428,6 +431,7 @@ def select_from_order_columns(columns)
428431

429432
def include_references(scope, klass, includes, references, exp_includes)
430433
if scope.respond_to?(:eager_load)
434+
# TODO: do we want to klass.prune_references(exp_includes)? (see same comment for inline_view? section)
431435
scope.eager_load(references || {}).eager_load(exp_includes || {}).preload(includes)
432436
else
433437
# This is the AAAR / QueryRelation branch
@@ -440,13 +444,16 @@ def include_references(scope, klass, includes, references, exp_includes)
440444
def add_joins(klass, scope, includes)
441445
return scope unless includes
442446

447+
# NOTE: We should be pre-pruning polymorphic out of here, since they shouldn't be in references.
448+
# TODO: do we want to be less lenient? i.e.: raise PolymorphicError if reflection&.polymorphic?
443449
includes = Array(includes) unless includes.kind_of?(Enumerable)
444450
includes.each do |association, value|
445451
reflection = klass.reflect_on_association(association)
446452
if reflection && !reflection.polymorphic?
447453
scope = value ? scope.left_outer_joins(association => value) : scope.left_outer_joins(association)
448454
end
449455
end
456+
# This adds a DISTINCT to safeguard "JOIN has_many" screwing up the LIMIT.
450457
scope = scope.distinct if !scope.distinct_value && is_collection?(klass, includes)
451458
scope
452459
end
Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,40 @@
11
RSpec.describe "ar_references" do
2-
describe ".includes_to_references" do
2+
describe ".prune_references" do
33
it "supports none" do
4-
expect(Vm.includes_to_references(nil)).to eq([])
5-
expect(Vm.includes_to_references([])).to eq([])
4+
expect(Vm.prune_references(nil)).to eq({})
5+
expect(Vm.prune_references([])).to eq({})
66
end
77

88
it "supports arrays" do
9-
expect(Vm.includes_to_references(%w[host operating_system])).to eq(%w[hosts operating_systems])
10-
expect(Vm.includes_to_references(%i[host])).to eq(%w[hosts])
9+
expect(Vm.prune_references(%w[host operating_system])).to eq(:host => {}, :operating_system => {})
10+
expect(Vm.prune_references(%i[host])).to eq(:host => {})
1111
end
1212

1313
it "supports hashes" do
14-
expect(Hardware.includes_to_references(:vm => {})).to eq(%w[vms])
15-
expect(Hardware.includes_to_references(:vm => :ext_management_system)).to eq(%w[vms ext_management_systems])
16-
expect(Hardware.includes_to_references(:vm => {:ext_management_system => {}})).to eq(%w[vms ext_management_systems])
14+
expect(Hardware.prune_references(:vm => {})).to eq(:vm => {})
15+
expect(Hardware.prune_references(:vm => :ext_management_system)).to eq(:vm => {:ext_management_system => {}})
16+
expect(Hardware.prune_references(:vm => {:ext_management_system => {}})).to eq(:vm => {:ext_management_system => {}})
1717
end
1818

19-
it "supports table array" do
20-
expect(Vm.includes_to_references(%w[hosts operating_systems])).to eq(%w[hosts operating_systems])
19+
it "skips tags" do
20+
expect(Vm.prune_references(:taggings => {})).to eq({})
21+
end
22+
23+
it "skips virtual has many" do
24+
expect(Vm.prune_references(:processes => {})).to eq({})
25+
end
26+
27+
it "skips virtual attributes" do
28+
expect(Vm.prune_references(:archived => {}, :platform => {})).to eq({})
29+
end
30+
31+
it "skips polymorphic references" do
32+
expect(MetricRollup.prune_references(:resource=>{})).to eq({})
33+
expect(MiqGroup.prune_references(:tenant => :source)).to eq({:tenant => {}})
34+
end
35+
36+
it "skips uses with a polymorphic reference" do
37+
expect(MetricRollup.prune_references(:v_derived_storage_used => {})).to eq({})
2138
end
2239
end
2340
end

spec/lib/rbac/filterer_spec.rb

Lines changed: 11 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,51 +1169,42 @@ def combine_filtered_ids(user_filtered_ids, belongsto_filtered_ids, managed_filt
11691169
}
11701170
end
11711171

1172-
# NOTE: Think long and hard before you consider removing this test.
1173-
# Many-a-hours wasted here determining this bug that resulted in
1174-
# re-adding this test again.
1172+
# We've had an on and off again success with polymorphics (first documented in 2015 by Greg Tanzillo)
1173+
# references().includes() with polymorphic does not work
1174+
# now that we've converted to preload and eager_load, this is no longer an issue
11751175
#
1176-
# 2nd NOTE: I did think (and wrote the above comment as well), however,
1177-
# now it seems we DO NOT SUPPORT polymorphic code, since we removed it
1178-
# here:
1179-
#
1180-
# https://github.com/ManageIQ/manageiq/commit/8cc2277b
1181-
#
1182-
# That said, we should make sure this is erroring and no other callers
1183-
# are trying to do this (example: MiqReport), so make sure this raises
1184-
# an error to inform the caller that fixing needs to happen.
1176+
# Since we've been so mix and match, please leave this test in here
11851177
#
11861178
it "raises an error when a polymorphic reflection is included and referenced" do
1187-
# NOTE: Fails if :references is passed with a value, or with no key,
1188-
# which it uses :include_for_find as the default.
1179+
# This is not valid. You should not be able to pass a polymorphic to references
11891180
expect do
11901181
described_class.search :class => "MetricRollup",
11911182
:include_for_find => @include,
11921183
:references => @include
11931184
end.to raise_error(Rbac::PolymorphicError)
11941185

1186+
# defaults to includes when no :references passed in
11951187
expect do
11961188
described_class.search :class => "MetricRollup",
11971189
:include_for_find => @include
11981190
end.to raise_error(Rbac::PolymorphicError)
11991191
end
12001192

12011193
it "does not raise an error when a polymorphic reflection is only included" do
1202-
# NOTE: if references is passed in, but is blank, then doesn't override and is fine
12031194
expect do
12041195
described_class.search :class => "MetricRollup",
12051196
:include_for_find => @include,
1206-
:references => {}
1197+
:references => MetricRollup.prune_references(@include)
12071198
end.not_to raise_error
12081199

1200+
# NOTE: if a nil or blank references is passed in, then it doesn't default to the include_for_find and is fine
12091201
expect do
12101202
described_class.search :class => "MetricRollup",
12111203
:include_for_find => @include,
1212-
:references => nil
1204+
:references => {}
12131205
end.not_to raise_error
12141206

1215-
# ensure going forward that polymorphics can be passed as references
1216-
# this has been broken and fixed many times. Please don't remove
1207+
# Any references with a polymorphic in the includes (and a supported references value should work. (it wasn't working before)
12171208
expect do
12181209
described_class.search :class => "MetricRollup",
12191210
:include_for_find => @include,
@@ -2627,6 +2618,7 @@ def get_rbac_results_for_and_expect_objects(klass, expected_objects)
26272618

26282619
recs, attrs = Rbac.search(:targets => MetricRollup,
26292620
:include_for_find => {:resource => {}, :parent_host => {}},
2621+
:references => MetricRollup.prune_references({:resource => {}, :parent_host => {}}),
26302622
:extra_cols => [:v_derived_storage_used],
26312623
:use_sql_view => true,
26322624
:limit => 20,
@@ -2769,19 +2761,6 @@ def get_rbac_results_for_and_expect_objects(klass, expected_objects)
27692761
expect(resulting_scope.eager_load_values).to match_array([:miq_server, :host])
27702762
end
27712763
end
2772-
2773-
context "if the include is polymorphic" do
2774-
let(:klass) { MetricRollup }
2775-
let(:include_for_find) { {:resource => {}} }
2776-
2777-
it "does not add .references to the scope" do
2778-
method_args = [scope, klass, include_for_find, include_for_find, nil]
2779-
resulting_scope = subject.send(:include_references, *method_args)
2780-
2781-
expect(resulting_scope.preload_values).to eq([include_for_find])
2782-
expect(resulting_scope.eager_load_values).to eq([include_for_find])
2783-
end
2784-
end
27852764
end
27862765

27872766
describe "using right RBAC rules to VM and Templates" do

0 commit comments

Comments
 (0)