diff --git a/app/models/columns/linked_drop_down_column.rb b/app/models/columns/linked_drop_down_column.rb index f750c1fa8..1d25d3eea 100644 --- a/app/models/columns/linked_drop_down_column.rb +++ b/app/models/columns/linked_drop_down_column.rb @@ -17,10 +17,28 @@ class Columns::LinkedDropDownColumn < Columns::ChampColumn ) end - def filtered_ids(dossiers, values) - dossiers.with_type_de_champ(@column) - .filter_ilike(:champs, :value, values) - .ids + def filtered_ids(dossiers, search_terms) + relation = dossiers.with_type_de_champ(@stable_id) + + case path + when :value + search_terms.flat_map do |search_term| + # when looking for "section 1 / option A", + # the value must contain both "section 1" and "option A" + primary, *secondary = search_term.split(%r{[[:space:]]*/[[:space:]]*}) + safe_terms = [primary, *secondary].map { "%#{safe_like(_1)}%" } + + relation.where("champs.value ILIKE ALL (ARRAY[?])", safe_terms).ids + end.uniq + when :primary + primary_terms = search_terms.map { |term| %{["#{safe_like(term)}","%"]} } + + relation.where("champs.value ILIKE ANY (array[?])", primary_terms).ids + when :secondary + secondary_terms = search_terms.map { |term| %{["%","#{safe_like(term)}"]} } + + relation.where("champs.value ILIKE ANY (array[?])", secondary_terms).ids + end end private @@ -44,4 +62,6 @@ class Columns::LinkedDropDownColumn < Columns::ChampColumn rescue JSON::ParserError [] end + + def safe_like(q) = ActiveRecord::Base.sanitize_sql_like(q) end diff --git a/spec/models/columns/linked_drop_down_column_spec.rb b/spec/models/columns/linked_drop_down_column_spec.rb new file mode 100644 index 000000000..0929090bb --- /dev/null +++ b/spec/models/columns/linked_drop_down_column_spec.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +describe Columns::LinkedDropDownColumn do + describe '#filtered_ids' do + let(:procedure) { create(:procedure, types_de_champ_public: [{ type: :linked_drop_down_list, libelle: 'linked' }]) } + let(:type_de_champ) { procedure.active_revision.types_de_champ_public.first } + let(:kept_dossier) { create(:dossier, procedure: procedure) } + let(:discarded_dossier) { create(:dossier, procedure: procedure) } + + subject { column.filtered_ids(Dossier.all, search_terms) } + + context 'when path is :value' do + let(:column) { procedure.find_column(label: 'linked') } + + before do + kept_dossier.champs.find_by(stable_id: type_de_champ.stable_id) + .update(value: %{["section 1","option A"]}) + + discarded_dossier.champs.find_by(stable_id: type_de_champ.stable_id) + .update(value: %{["section 1","option B"]}) + end + + describe 'when looking for a part' do + let(:search_terms) { ['option A'] } + + it { is_expected.to eq([kept_dossier.id]) } + end + + describe 'when looking for the aggregated value' do + let(:search_terms) { ['section 1 / option A'] } + + it { is_expected.to match_array([kept_dossier.id]) } + end + + describe 'when looking for the aggregated value or a common value' do + let(:search_terms) { ['section 1 / option A', 'section'] } + + it { is_expected.to match_array([kept_dossier.id, discarded_dossier.id]) } + end + + describe 'when looking for a shared string' do + let(:search_terms) { ['option'] } + + it { is_expected.to match_array([kept_dossier.id, discarded_dossier.id]) } + end + end + + context 'when path is not :value' do + before do + kept_dossier.champs.find_by(stable_id: type_de_champ.stable_id) + .update(value: %{["1","2"]}) + + discarded_dossier.champs.find_by(stable_id: type_de_champ.stable_id) + .update(value: %{["2","1"]}) + end + + context 'when path is :primary' do + let(:column) { procedure.find_column(label: 'linked (Primaire)') } + + describe 'when looking kept part' do + let(:search_terms) { ['1'] } + + it { is_expected.to eq([kept_dossier.id]) } + end + end + + context 'when path is :secondary' do + let(:column) { procedure.find_column(label: 'linked (Secondaire)') } + + describe 'when looking kept part' do + let(:search_terms) { ['2'] } + + it { is_expected.to eq([kept_dossier.id]) } + end + end + end + end +end