add DossierProjectionService
This commit is contained in:
parent
0376ad9564
commit
5bfd4ecbbf
2 changed files with 245 additions and 0 deletions
88
app/services/dossier_projection_service.rb
Normal file
88
app/services/dossier_projection_service.rb
Normal file
|
@ -0,0 +1,88 @@
|
|||
class DossierProjectionService
|
||||
class DossierProjection < Struct.new(:dossier, :columns)
|
||||
end
|
||||
|
||||
TABLE = 'table'
|
||||
COLUMN = 'column'
|
||||
|
||||
# Returns [DossierProjection(dossier, columns)] ordered by dossiers_ids
|
||||
# and the columns orderd by fields.
|
||||
#
|
||||
# It tries to be fast by using `pluck` (or at least `select`)
|
||||
# to avoid deserializing entire records.
|
||||
#
|
||||
# It stores its intermediary queries results in an hash in the corresponding field.
|
||||
# ex: field_email[:id_value_h] = { dossier_id_1: email_1, dossier_id_3: email_3 }
|
||||
#
|
||||
# Those hashes are needed because:
|
||||
# - the order of the intermediary query results are unknown
|
||||
# - some values can be missing (if a revision added or removed them)
|
||||
def self.project(dossiers_ids, fields)
|
||||
champ_fields, other_fields = fields
|
||||
.partition { |f| ['type_de_champ', 'type_de_champ_private'].include?(f[TABLE]) }
|
||||
|
||||
if champ_fields.present?
|
||||
Champ
|
||||
.includes(:type_de_champ)
|
||||
.where(
|
||||
# as querying the champs table is costly
|
||||
# we fetch all the requested champs at once
|
||||
types_de_champ: { stable_id: champ_fields.map { |f| f[COLUMN] } },
|
||||
dossier_id: dossiers_ids
|
||||
)
|
||||
.select(:dossier_id, :value, :type_de_champ_id, :stable_id) # we cannot pluck :value, as we need the champ.to_s method
|
||||
.group_by(&:stable_id) # the champs are redispatched to their respective fields
|
||||
.map do |stable_id, champs|
|
||||
field = champ_fields.find { |f| f[COLUMN] == stable_id.to_s }
|
||||
field[:id_value_h] = champs.to_h { |c| [c.dossier_id, c.to_s] }
|
||||
end
|
||||
end
|
||||
|
||||
other_fields.each do |field|
|
||||
field[:id_value_h] = case field[TABLE]
|
||||
when 'self'
|
||||
Dossier
|
||||
.where(id: dossiers_ids)
|
||||
.pluck(:id, field[COLUMN].to_sym)
|
||||
.to_h { |id, col| [id, col&.strftime('%d/%m/%Y')] }
|
||||
when 'user'
|
||||
Dossier
|
||||
.joins(:user)
|
||||
.where(id: dossiers_ids)
|
||||
.pluck('dossiers.id, users.email')
|
||||
.to_h
|
||||
when 'individual'
|
||||
Individual
|
||||
.where(dossier_id: dossiers_ids)
|
||||
.pluck(:dossier_id, field[COLUMN].to_sym)
|
||||
.to_h
|
||||
when 'etablissement'
|
||||
Etablissement
|
||||
.where(dossier_id: dossiers_ids)
|
||||
.pluck(:dossier_id, field[COLUMN].to_sym)
|
||||
.to_h
|
||||
when 'groupe_instructeur'
|
||||
Dossier
|
||||
.joins(:groupe_instructeur)
|
||||
.where(id: dossiers_ids)
|
||||
.pluck('dossiers.id, groupe_instructeurs.label')
|
||||
.to_h
|
||||
when 'followers_instructeurs'
|
||||
Follow
|
||||
.active
|
||||
.joins(instructeur: :user)
|
||||
.where(dossier_id: dossiers_ids)
|
||||
.pluck('dossier_id, users.email')
|
||||
.group_by { |dossier_id, _| dossier_id }
|
||||
.to_h { |dossier_id, dossier_id_emails| [dossier_id, dossier_id_emails.map { |_, email| email }&.join(', ')] }
|
||||
end
|
||||
end
|
||||
|
||||
Dossier
|
||||
.select(:id, :state, :archived) # the dossier object is needed in the view
|
||||
.find(dossiers_ids) # keeps dossiers_ids order and raise exception if one is missing
|
||||
.map do |dossier|
|
||||
DossierProjection.new(dossier, fields.map { |f| f[:id_value_h][dossier.id] })
|
||||
end
|
||||
end
|
||||
end
|
157
spec/services/dossier_projection_service_spec.rb
Normal file
157
spec/services/dossier_projection_service_spec.rb
Normal file
|
@ -0,0 +1,157 @@
|
|||
describe DossierProjectionService do
|
||||
describe '#project' do
|
||||
subject { described_class.project(dossiers_ids, fields) }
|
||||
|
||||
context 'with multiple dossier' do
|
||||
let!(:procedure) { create(:procedure, :with_type_de_champ) }
|
||||
let!(:dossier_1) { create(:dossier, procedure: procedure) }
|
||||
let!(:dossier_2) { create(:dossier, procedure: procedure) }
|
||||
let!(:dossier_3) { create(:dossier, procedure: procedure) }
|
||||
|
||||
let(:dossiers_ids) { [dossier_3.id, dossier_1.id, dossier_2.id] }
|
||||
let(:fields) do
|
||||
[
|
||||
{
|
||||
"table" => "type_de_champ",
|
||||
"column" => procedure.types_de_champ[0].stable_id.to_s
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
before do
|
||||
dossier_1.champs.first.update(value: 'champ_1')
|
||||
dossier_2.champs.first.update(value: 'champ_2')
|
||||
dossier_3.champs.first.destroy
|
||||
end
|
||||
|
||||
let(:result) { subject }
|
||||
|
||||
it 'respects the dossiers_ids order and returns nil for empty result' do
|
||||
expect(result.length).to eq(3)
|
||||
expect(result[0].dossier.id).to eq(dossier_3.id)
|
||||
expect(result[1].dossier.id).to eq(dossier_1.id)
|
||||
expect(result[2].dossier.id).to eq(dossier_2.id)
|
||||
|
||||
expect(result[0].columns[0]).to be nil
|
||||
expect(result[1].columns[0]).to eq('champ_1')
|
||||
expect(result[2].columns[0]).to eq('champ_2')
|
||||
end
|
||||
end
|
||||
|
||||
context 'attributes by attributes' do
|
||||
let(:fields) { [{ "table" => table, "column" => column }] }
|
||||
let(:dossiers_ids) { [dossier.id] }
|
||||
|
||||
subject { super()[0].columns[0] }
|
||||
|
||||
context 'for self table' do
|
||||
let(:table) { 'self' }
|
||||
|
||||
context 'for created_at column' do
|
||||
let(:column) { 'created_at' }
|
||||
let(:dossier) { Timecop.freeze(Time.zone.local(1992, 3, 22)) { create(:dossier) } }
|
||||
|
||||
it { is_expected.to eq('22/03/1992') }
|
||||
end
|
||||
|
||||
context 'for en_construction_at column' do
|
||||
let(:column) { 'en_construction_at' }
|
||||
let(:dossier) { create(:dossier, :en_construction, en_construction_at: Time.zone.local(2018, 10, 17)) }
|
||||
|
||||
it { is_expected.to eq('17/10/2018') }
|
||||
end
|
||||
|
||||
context 'for updated_at column' do
|
||||
let(:column) { 'updated_at' }
|
||||
let(:dossier) { create(:dossier) }
|
||||
|
||||
before { dossier.touch(time: Time.zone.local(2018, 9, 25)) }
|
||||
|
||||
it { is_expected.to eq('25/09/2018') }
|
||||
end
|
||||
end
|
||||
|
||||
context 'for user table' do
|
||||
let(:table) { 'user' }
|
||||
let(:column) { 'email' }
|
||||
|
||||
let(:dossier) { create(:dossier, user: create(:user, email: 'bla@yopmail.com')) }
|
||||
|
||||
it { is_expected.to eq('bla@yopmail.com') }
|
||||
end
|
||||
|
||||
context 'for individual table' do
|
||||
let(:table) { 'individual' }
|
||||
let(:procedure) { create(:procedure, :for_individual, :with_type_de_champ, :with_type_de_champ_private) }
|
||||
let(:dossier) { create(:dossier, procedure: procedure, individual: create(:individual, nom: 'Martin', prenom: 'Jacques', gender: 'M.')) }
|
||||
|
||||
context 'for prenom column' do
|
||||
let(:column) { 'prenom' }
|
||||
|
||||
it { is_expected.to eq('Jacques') }
|
||||
end
|
||||
|
||||
context 'for nom column' do
|
||||
let(:column) { 'nom' }
|
||||
|
||||
it { is_expected.to eq('Martin') }
|
||||
end
|
||||
|
||||
context 'for gender column' do
|
||||
let(:column) { 'gender' }
|
||||
|
||||
it { is_expected.to eq('M.') }
|
||||
end
|
||||
end
|
||||
|
||||
context 'for etablissement table' do
|
||||
let(:table) { 'etablissement' }
|
||||
let(:column) { 'code_postal' } # All other columns work the same, no extra test required
|
||||
|
||||
let!(:dossier) { create(:dossier, etablissement: create(:etablissement, code_postal: '75008')) }
|
||||
|
||||
it { is_expected.to eq('75008') }
|
||||
end
|
||||
|
||||
context 'for groupe_instructeur table' do
|
||||
let(:table) { 'groupe_instructeur' }
|
||||
let(:column) { 'label' }
|
||||
|
||||
let!(:dossier) { create(:dossier) }
|
||||
|
||||
it { is_expected.to eq('défaut') }
|
||||
end
|
||||
|
||||
context 'for followers_instructeurs table' do
|
||||
let(:table) { 'followers_instructeurs' }
|
||||
let(:column) { 'email' }
|
||||
|
||||
let(:dossier) { create(:dossier) }
|
||||
let!(:follow1) { create(:follow, dossier: dossier, instructeur: create(:instructeur, email: 'user1@host')) }
|
||||
let!(:follow2) { create(:follow, dossier: dossier, instructeur: create(:instructeur, email: 'user2@host')) }
|
||||
|
||||
it { is_expected.to eq "user1@host, user2@host" }
|
||||
end
|
||||
|
||||
context 'for type_de_champ table' do
|
||||
let(:table) { 'type_de_champ' }
|
||||
let(:dossier) { create(:dossier) }
|
||||
let(:column) { dossier.procedure.types_de_champ.first.stable_id.to_s }
|
||||
|
||||
before { dossier.champs.first.update(value: 'kale') }
|
||||
|
||||
it { is_expected.to eq('kale') }
|
||||
end
|
||||
|
||||
context 'for type_de_champ_private table' do
|
||||
let(:table) { 'type_de_champ_private' }
|
||||
let(:dossier) { create(:dossier) }
|
||||
let(:column) { dossier.procedure.types_de_champ_private.first.stable_id.to_s }
|
||||
|
||||
before { dossier.champs_private.first.update(value: 'quinoa') }
|
||||
|
||||
it { is_expected.to eq('quinoa') }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue