diff --git a/app/helpers/public_body_helper.rb b/app/helpers/public_body_helper.rb index 2cd6f0cc4e..e061278d85 100644 --- a/app/helpers/public_body_helper.rb +++ b/app/helpers/public_body_helper.rb @@ -37,7 +37,7 @@ def public_body_not_requestable_reasons(public_body) # # Returns a String def type_of_authority(public_body) - categories = PublicBody.categories. + categories = PublicBody.category_list. where(category_tag: public_body.tag_string.split).order(:id) types = categories.each_with_index.map do |category, index| diff --git a/app/models/category.rb b/app/models/category.rb index 3ba777ae85..9b9ac796f3 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -53,6 +53,27 @@ def tree children.includes(:translations, children: [:translations]) end + def list + sql = <<~SQL.squish + WITH RECURSIVE nested_categories AS ( + SELECT child_id + FROM category_relationships + WHERE parent_id = :parent_id + + UNION ALL + + SELECT cr.child_id + FROM category_relationships cr + INNER JOIN nested_categories nc ON cr.parent_id = nc.child_id + ) + SELECT DISTINCT c.id FROM categories c + INNER JOIN nested_categories nc ON c.id = nc.child_id; + SQL + + ids = Category.find_by_sql([sql, { parent_id: id }]).map(&:id) + Category.where(id: ids).includes(:translations) + end + private def check_tag_assignments diff --git a/app/models/concerns/categorisable.rb b/app/models/concerns/categorisable.rb index 95b9ac96d0..18d8e635b2 100644 --- a/app/models/concerns/categorisable.rb +++ b/app/models/concerns/categorisable.rb @@ -18,5 +18,9 @@ def self.category_root def self.categories category_root.tree end + + def self.category_list + category_root.list + end end end diff --git a/spec/helpers/public_body_helper_spec.rb b/spec/helpers/public_body_helper_spec.rb index e3771f37f3..3ec6f3a062 100644 --- a/spec/helpers/public_body_helper_spec.rb +++ b/spec/helpers/public_body_helper_spec.rb @@ -82,10 +82,11 @@ expect(type_of_authority(public_body)).to eq(expected) end - context 'when associated with one category' do + context 'when categories are descendents of non-root parents' do it 'returns the description wrapped in an anchor tag' do FactoryBot.create( - :category, :public_body, + :category, + parents: [FactoryBot.build(:category, :public_body)], category_tag: 'spec', description: 'spec category' ) @@ -94,13 +95,12 @@ anchor = %Q(Spec category) expect(type_of_authority(public_body)).to eq(anchor) end - end - context 'when associated with several categories' do it 'joins the category descriptions and capitalizes the first letter' do 3.times do |i| FactoryBot.create( - :category, :public_body, + :category, + parents: [FactoryBot.build(:category, :public_body)], category_tag: "spec_#{i}", description: "spec category #{i}" ) diff --git a/spec/models/category_spec.rb b/spec/models/category_spec.rb index fd98830afe..4f0b12a7e5 100644 --- a/spec/models/category_spec.rb +++ b/spec/models/category_spec.rb @@ -130,6 +130,14 @@ FactoryBot.create(:category, title: 'Branch', parents: [trunk]) end + let!(:other_root) do + FactoryBot.create(:category, title: 'InfoRequest') + end + + let!(:other_trunk) do + FactoryBot.create(:category, title: 'Trunk', parents: [other_root]) + end + around do |example| @query_count = 0 subscription = ActiveSupport::Notifications.subscribe( @@ -141,7 +149,7 @@ ActiveSupport::Notifications.unsubscribe(subscription) end - it 'returns root category descendents' do + it 'returns root category descendents as tree' do expect(root.tree).to match_array([trunk]) expect(root.tree[0].children).to match_array([branch]) end @@ -159,4 +167,51 @@ }.to_not change { @query_count } end end + + describe '#list' do + subject { root.list } + + let!(:root) do + FactoryBot.create(:category, title: 'PublicBody') + end + + let!(:trunk) do + FactoryBot.create(:category, title: 'Trunk', parents: [root]) + end + + let!(:branch) do + FactoryBot.create(:category, title: 'Branch', parents: [trunk]) + end + + let!(:other_root) do + FactoryBot.create(:category, title: 'InfoRequest') + end + + let!(:other_trunk) do + FactoryBot.create(:category, title: 'Trunk', parents: [other_root]) + end + + around do |example| + @query_count = 0 + subscription = ActiveSupport::Notifications.subscribe( + 'sql.active_record' + ) { @query_count += 1 } + + example.call + + ActiveSupport::Notifications.unsubscribe(subscription) + end + + it 'returns root category descendents as list' do + expect(root.list).to match_array([trunk, branch]) + end + + it 'preload translations' do + # load list and perform all necessary DB queries + list = root.list.to_a + + # iterate through list and ensure translations have been preloaded + expect { list.each(&:title) }.to_not change { @query_count } + end + end end diff --git a/spec/models/concerns/categorisable.rb b/spec/models/concerns/categorisable.rb index e634630764..b5fe8c0f20 100644 --- a/spec/models/concerns/categorisable.rb +++ b/spec/models/concerns/categorisable.rb @@ -13,4 +13,11 @@ described_class.categories end end + + describe '.category_list' do + it 'calls category_root.list' do + expect(described_class).to receive_message_chain(:category_root, :list) + described_class.category_list + end + end end