diff --git a/app/models/filtered_column.rb b/app/models/filtered_column.rb new file mode 100644 index 000000000..c147e0cce --- /dev/null +++ b/app/models/filtered_column.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +class FilteredColumn + def initialize(column:, filter:) + @column = column + @filter = filter + end + + def ==(other) + other&.column == column && other.filter == filter + end +end diff --git a/app/models/procedure_presentation.rb b/app/models/procedure_presentation.rb index d1fb52ceb..86b227029 100644 --- a/app/models/procedure_presentation.rb +++ b/app/models/procedure_presentation.rb @@ -25,14 +25,14 @@ class ProcedurePresentation < ApplicationRecord attribute :sorted_column, :sorted_column def sorted_column = super || procedure.default_sorted_column # Dummy override to set default value - attribute :a_suivre_filters, :jsonb, array: true - attribute :suivis_filters, :jsonb, array: true - attribute :traites_filters, :jsonb, array: true - attribute :tous_filters, :jsonb, array: true - attribute :supprimes_filters, :jsonb, array: true - attribute :supprimes_recemment_filters, :jsonb, array: true - attribute :expirant_filters, :jsonb, array: true - attribute :archives_filters, :jsonb, array: true + attribute :a_suivre_filters, :filtered_column, array: true + attribute :suivis_filters, :filtered_column, array: true + attribute :traites_filters, :filtered_column, array: true + attribute :tous_filters, :filtered_column, array: true + attribute :supprimes_filters, :filtered_column, array: true + attribute :supprimes_recemment_filters, :filtered_column, array: true + attribute :expirant_filters, :filtered_column, array: true + attribute :archives_filters, :filtered_column, array: true def filters_for(statut) send(filters_name_for(statut)) diff --git a/app/types/filtered_column_type.rb b/app/types/filtered_column_type.rb new file mode 100644 index 000000000..ca097e4d5 --- /dev/null +++ b/app/types/filtered_column_type.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +class FilteredColumnType < ActiveRecord::Type::Value + # form_input or setter -> type + def cast(value) + value = value.deep_symbolize_keys if value.respond_to?(:deep_symbolize_keys) + + case value + in FilteredColumn + value + in NilClass # default value + nil + # from form (id is a string) or from db (id is a hash) + in { id: String|Hash, filter: String } => h + FilteredColumn.new(column: ColumnType.new.cast(h[:id]), filter: h[:filter]) + end + end + + # db -> ruby + def deserialize(value) = cast(value&.then { JSON.parse(_1) }) + + # ruby -> db + def serialize(value) + case value + in NilClass + nil + in FilteredColumn + JSON.generate({ + id: value.column.h_id, + filter: value.filter + }) + else + raise ArgumentError, "Invalid value for FilteredColumn serialization: #{value}" + end + end +end diff --git a/config/initializers/types.rb b/config/initializers/types.rb index 8ac4a38ed..46f40f05d 100644 --- a/config/initializers/types.rb +++ b/config/initializers/types.rb @@ -3,9 +3,11 @@ require Rails.root.join("app/types/column_type") require Rails.root.join("app/types/export_item_type") require Rails.root.join("app/types/sorted_column_type") +require Rails.root.join("app/types/filtered_column_type") ActiveSupport.on_load(:active_record) do ActiveRecord::Type.register(:column, ColumnType) ActiveRecord::Type.register(:export_item, ExportItemType) ActiveRecord::Type.register(:sorted_column, SortedColumnType) + ActiveRecord::Type.register(:filtered_column, FilteredColumnType) end diff --git a/spec/models/export_spec.rb b/spec/models/export_spec.rb index e960acb8f..880e3163a 100644 --- a/spec/models/export_spec.rb +++ b/spec/models/export_spec.rb @@ -94,7 +94,9 @@ RSpec.describe Export, type: :model do let(:instructeur) { create(:instructeur) } let!(:gi_1) { create(:groupe_instructeur, procedure: procedure, instructeurs: [instructeur]) } let!(:pp) { gi_1.instructeurs.first.procedure_presentation_and_errors_for_procedure_id(procedure.id).first } - before { pp.add_filter('tous', procedure.find_column(label: 'Créé le').id, '10/12/2021') } + let(:created_at_column) { FilteredColumn.new(column: procedure.find_column(label: 'Créé le'), filter: '10/12/2021') } + + before { pp.update(tous_filters: [created_at_column]) } context 'with procedure_presentation having different filters' do it 'works once' do @@ -105,7 +107,10 @@ RSpec.describe Export, type: :model do it 'works once, changes procedure_presentation, recreate a new' do expect { Export.find_or_create_fresh_export(:zip, [gi_1], instructeur, time_span_type: Export.time_span_types.fetch(:everything), statut: Export.statuts.fetch(:tous), procedure_presentation: pp) } .to change { Export.count }.by(1) - pp.add_filter('tous', procedure.find_column(label: 'Mis à jour le').id, '10/12/2021') + + update_at_column = FilteredColumn.new(column: procedure.find_column(label: 'Mis à jour le'), filter: '10/12/2021') + pp.update(tous_filters: [created_at_column, update_at_column]) + expect { Export.find_or_create_fresh_export(:zip, [gi_1], instructeur, time_span_type: Export.time_span_types.fetch(:everything), statut: Export.statuts.fetch(:tous), procedure_presentation: pp) } .to change { Export.count }.by(1) end diff --git a/spec/types/filtered_column_type_spec.rb b/spec/types/filtered_column_type_spec.rb new file mode 100644 index 000000000..05595135c --- /dev/null +++ b/spec/types/filtered_column_type_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +describe FilteredColumnType do + let(:type) { FilteredColumnType.new } + + describe 'cast' do + it 'from FilteredColumn' do + column = Column.new(procedure_id: 1, table: 'table', column: 'column') + filtered_column = FilteredColumn.new(column:, filter: 'filter') + expect(type.cast(filtered_column)).to eq(filtered_column) + end + + it 'from nil' do + expect(type.cast(nil)).to eq(nil) + end + + describe 'from form' do + it 'with valid column id' do + column = Column.new(procedure_id: 1, table: 'table', column: 'column') + h = { filter: 'filter', id: column.id } + + expect(Column).to receive(:find).with(column.h_id).and_return(column) + expect(type.cast(h)).to eq(FilteredColumn.new(column:, filter: 'filter')) + end + + it 'with invalid column id' do + h = { filter: 'filter', id: 'invalid' } + expect { type.cast(h) }.to raise_error(JSON::ParserError) + + h = { filter: 'filter', id: { procedure_id: 'invalid', column_id: 'nop' }.to_json } + expect { type.cast(h) }.to raise_error(ActiveRecord::RecordNotFound) + end + end + end + + describe 'deserialize' do + context 'with valid value' do + it 'works' do + column = Column.new(procedure_id: 1, table: 'table', column: 'column') + expect(Column).to receive(:find).with(column.h_id).and_return(column) + expect(type.deserialize({ id: column.h_id, filter: 'filter' }.to_json)).to eq(FilteredColumn.new(column: column, filter: 'filter')) + end + end + + context 'with nil' do + it { expect(type.deserialize(nil)).to eq(nil) } + end + end + + describe 'serialize' do + it 'with FilteredColumn' do + column = Column.new(procedure_id: 1, table: 'table', column: 'column') + sorted_column = FilteredColumn.new(column: column, filter: 'filter') + expect(type.serialize(sorted_column)).to eq({ id: column.h_id, filter: 'filter' }.to_json) + end + + it 'with nil' do + expect(type.serialize(nil)).to eq(nil) + end + + it 'with invalid value' do + expect { type.serialize('invalid') }.to raise_error(ArgumentError) + end + end +end