demarches-normaliennes/app/models/instructeur.rb
Nicolas Bouilleaud 4f0871dab0 Use a single query in dossiers_id_with_notifications (instead of four)
This is used in /procedures#show and /procedures#index, to display badges on the “suivis” and “traités” tabs of each procedure. Rails cache helps when it’s the exact same query, but it’s not the case for different tabs.

I’m not certain it’ll be a visible performance improvement but it shouldn’t hurt.
2019-09-20 09:45:59 +02:00

212 lines
7.1 KiB
Ruby

class Instructeur < ApplicationRecord
self.ignored_columns = ['features', 'encrypted_password', 'reset_password_token', 'reset_password_sent_at', 'remember_created_at', 'sign_in_count', 'current_sign_in_at', 'last_sign_in_at', 'current_sign_in_ip', 'last_sign_in_ip', 'failed_attempts', 'unlock_token', 'locked_at']
include EmailSanitizableConcern
has_and_belongs_to_many :administrateurs
before_validation -> { sanitize_email(:email) }
has_many :assign_to, dependent: :destroy
has_many :groupe_instructeurs, through: :assign_to
has_many :procedures, -> { distinct }, through: :groupe_instructeurs
has_many :assign_to_with_email_notifications, -> { with_email_notifications }, class_name: 'AssignTo', inverse_of: :instructeur
has_many :groupe_instructeur_with_email_notifications, through: :assign_to_with_email_notifications, source: :groupe_instructeur
has_many :dossiers, -> { state_not_brouillon }, through: :groupe_instructeurs
has_many :follows, -> { active }, inverse_of: :instructeur
has_many :previous_follows, -> { inactive }, class_name: 'Follow', inverse_of: :instructeur
has_many :followed_dossiers, through: :follows, source: :dossier
has_many :previously_followed_dossiers, -> { distinct }, through: :previous_follows, source: :dossier
has_many :avis
has_many :dossiers_from_avis, through: :avis, source: :dossier
has_many :trusted_device_tokens
has_one :user
def follow(dossier)
begin
followed_dossiers << dossier
# If the user tries to follow a dossier she already follows,
# we just fail silently: it means the goal is already reached.
rescue ActiveRecord::RecordNotUnique
# Database uniqueness constraint
rescue ActiveRecord::RecordInvalid => e
# ActiveRecord validation
raise unless e.record.errors.details.dig(:instructeur_id, 0, :error) == :taken
end
end
def unfollow(dossier)
f = follows.find_by(dossier: dossier)
if f.present?
f.update(unfollowed_at: Time.zone.now)
end
end
def follow?(dossier)
followed_dossiers.include?(dossier)
end
def assign_to_procedure(procedure)
begin
assign_to.create({
procedure: procedure,
groupe_instructeur: procedure.defaut_groupe_instructeur
})
true
rescue ActiveRecord::RecordNotUnique
false
end
end
def remove_from_procedure(procedure)
!!(procedure.defaut_groupe_instructeur.in?(groupe_instructeurs) && groupe_instructeurs.destroy(procedure.defaut_groupe_instructeur))
end
def last_week_overview
start_date = Time.zone.now.beginning_of_week
active_procedure_overviews = procedures
.publiees
.map { |procedure| procedure.procedure_overview(start_date) }
.filter(&:had_some_activities?)
if active_procedure_overviews.count == 0
nil
else
{
start_date: start_date,
procedure_overviews: active_procedure_overviews
}
end
end
def procedure_presentation_and_errors_for_procedure_id(procedure_id)
assign_to.joins(:groupe_instructeur).find_by(groupe_instructeurs: { procedure_id: procedure_id }).procedure_presentation_or_default_and_errors
end
def notifications_for_dossier(dossier)
follow = Follow
.includes(dossier: [:champs, :avis, :commentaires])
.find_by(instructeur: self, dossier: dossier)
if follow.present?
demande = follow.dossier.champs.updated_since?(follow.demande_seen_at).any?
annotations_privees = follow.dossier.champs_private.updated_since?(follow.annotations_privees_seen_at).any?
avis_notif = follow.dossier.avis.updated_since?(follow.avis_seen_at).any?
messagerie = dossier.commentaires
.where.not(email: OLD_CONTACT_EMAIL)
.where.not(email: CONTACT_EMAIL)
.updated_since?(follow.messagerie_seen_at).any?
annotations_hash(demande, annotations_privees, avis_notif, messagerie)
else
annotations_hash(false, false, false, false)
end
end
def notifications_for_procedure(procedure, state = :en_cours)
dossiers = case state
when :termine
procedure.defaut_groupe_instructeur.dossiers.termine
when :not_archived
procedure.defaut_groupe_instructeur.dossiers.not_archived
when :all
procedure.defaut_groupe_instructeur.dossiers
else
procedure.defaut_groupe_instructeur.dossiers.en_cours
end
dossiers_id_with_notifications(dossiers)
end
def notifications_per_procedure(state = :en_cours)
dossiers = case state
when :termine
Dossier.termine
when :not_archived
Dossier.not_archived
else
Dossier.en_cours
end
Dossier.joins(:groupe_instructeur).where(id: dossiers_id_with_notifications(dossiers)).group('groupe_instructeurs.procedure_id').count
end
def create_trusted_device_token
trusted_device_token = trusted_device_tokens.create
trusted_device_token.token
end
def dossiers_id_with_notifications(dossiers)
dossiers = dossiers.followed_by(self)
# Relations passed to #or must be “structurally compatible”, i.e. query the same tables.
joined_dossiers = dossiers
.left_outer_joins(:champs, :champs_private, :avis, :commentaires)
updated_demandes = joined_dossiers
.where('champs.updated_at > follows.demande_seen_at')
# We join `:champs` twice, the second time with `has_many :champs_privates`. ActiveRecord generates the SQL: 'LEFT OUTER JOIN "champs" "champs_privates_dossiers" ON …'. We can then use this `champs_privates_dossiers` alias to disambiguate the table in this WHERE clause.
updated_annotations = joined_dossiers
.where('champs_privates_dossiers.updated_at > follows.annotations_privees_seen_at')
updated_avis = joined_dossiers
.where('avis.updated_at > follows.avis_seen_at')
updated_messagerie = joined_dossiers
.where('commentaires.updated_at > follows.messagerie_seen_at')
.where.not(commentaires: { email: OLD_CONTACT_EMAIL })
.where.not(commentaires: { email: CONTACT_EMAIL })
updated_demandes.or(updated_annotations).or(updated_avis).or(updated_messagerie).ids
end
def mark_tab_as_seen(dossier, tab)
attributes = {}
attributes["#{tab}_seen_at"] = Time.zone.now
Follow.where(instructeur: self, dossier: dossier).update_all(attributes)
end
def young_login_token?
trusted_device_token = trusted_device_tokens.order(created_at: :desc).first
trusted_device_token&.token_young?
end
def email_notification_data
groupe_instructeur_with_email_notifications
.reduce([]) do |acc, groupe|
procedure = groupe.procedure
h = {
nb_en_construction: groupe.dossiers.en_construction.count,
nb_notification: notifications_for_procedure(procedure, :all).count
}
if h[:nb_en_construction] > 0 || h[:nb_notification] > 0
h[:procedure_id] = procedure.id
h[:procedure_libelle] = procedure.libelle
acc << h
end
acc
end
end
private
def annotations_hash(demande, annotations_privees, avis, messagerie)
{
demande: demande,
annotations_privees: annotations_privees,
avis: avis,
messagerie: messagerie
}
end
end