window.pipedriveLeadboosterConfig = { base : 'leadbooster-chat.pipedrive.com', companyId : 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version : 2, } ;(function () { var w = window if (w.LeadBooster) { console.warn('LeadBooster existe déjà') } else { w.LeadBooster = { q : [], on : function (n, h) { this.q.push({ t : 'o', n : n, h : h }) }, trigger : function (n) { this.q.push({ t : 't', n : n }) }, } } })() Optimisation du code avec Query Objects - The Codest
The Codest
  • A propos de nous
  • Services
    • Développement de logiciels
      • Développement frontal
      • Développement backend
    • Staff Augmentation
      • Développeurs frontaux
      • Développeurs backend
      • Ingénieurs des données
      • Ingénieurs en informatique dématérialisée
      • Ingénieurs AQ
      • Autres
    • Conseil consultatif
      • Audit et conseil
  • Industries
    • Fintech et banque
    • E-commerce
    • Adtech
    • Santé (Healthtech)
    • Fabrication
    • Logistique
    • Automobile
    • IOT
  • Valeur pour
    • CEO
    • CTO
    • Gestionnaire des livraisons
  • Notre équipe
  • Études de cas
  • Savoir comment
    • Blog
    • Rencontres
    • Webinaires
    • Ressources
Carrières Prendre contact
  • A propos de nous
  • Services
    • Développement de logiciels
      • Développement frontal
      • Développement backend
    • Staff Augmentation
      • Développeurs frontaux
      • Développeurs backend
      • Ingénieurs des données
      • Ingénieurs en informatique dématérialisée
      • Ingénieurs AQ
      • Autres
    • Conseil consultatif
      • Audit et conseil
  • Valeur pour
    • CEO
    • CTO
    • Gestionnaire des livraisons
  • Notre équipe
  • Études de cas
  • Savoir comment
    • Blog
    • Rencontres
    • Webinaires
    • Ressources
Carrières Prendre contact
Flèche arrière RETOUR
2019-03-08
Développement de logiciels

Optimiser le code avec les objets de requête

The Codest

Tomasz Szkaradek

Architecte de développement

Il est fort probable qu'au travail, vous ayez été confronté à des modèles surchargés et à un grand nombre d'appels dans les contrôleurs pendant un bon nombre de fois. En me basant sur mes connaissances de l'environnement Rails, je vais proposer dans cet article une solution simple à ce problème.

Un aspect très important de l'application Rails est de minimiser le nombre de dépendances redondantes, c'est pourquoi l'ensemble de l'environnement Rails a récemment promu l'approche des objets de service et l'utilisation de la méthode PORO (Pure Old Ruby Object). Vous trouverez une description de l'utilisation d'une telle solution dans les pages suivantes ici. Dans cet article, nous allons résoudre le concept étape par étape et l'adapter au problème.

Problème

Dans une application hypothétique, nous avons affaire à un système de transaction complexe. Notre modèle, qui représente chaque transaction, dispose d'un ensemble de champs d'application qui vous aident à obtenir des données. Cela facilite grandement le travail, car les données peuvent être trouvées en un seul endroit. Cependant, cela ne dure pas longtemps. Avec le développement de l'application, les projet devient de plus en plus complexe. Les champs d'application n'ont plus de références "où" simples, nous manquons de données et commençons à charger des relations. Au bout d'un moment, cela rappelle un système compliqué de miroirs. Et, pire encore, nous ne savons pas comment faire une lambda multiligne !

Vous trouverez ci-dessous un modèle d'application déjà développé. Les transactions du système de paiement sont stockées dans. Comme vous pouvez le voir dans l'exemple ci-dessous :

classe Transaction  { where(visible : true) }

  scope(:active, lambda do
    joins(<<-SQL
      LEFT OUTER JOIN source ON transactions.source_id = source.id
      AND source.accepted_at IS NOT NULL
    SQL
  end)
fin

Le modèle est une chose, mais à mesure que l'échelle de notre projet augmente, les contrôleurs commencent eux aussi à gonfler. Examinons l'exemple ci-dessous :

classe TransactionsController < ApplicationController
  def index
    @transactions = Transaction.for_publishers
                                   .active
                                   .visible
                                   .joins("LEFT JOIN withdrawal_items ON withdrawal_items.transaction_id = transactions.id")
                                   .joins("LEFT JOIN withdrawals ON withdrawals.id = withdrawal_items.withdrawal_id OR
 (withdrawals.id = source.resource_id AND source.resource_type = 'Withdrawal')")
                                   .order(:created_at)
                                   .page(params[:page])
                                   .per(params[:page])
    @transactions = apply_filters(@transactions)
  end
end

Ici, nous pouvons voir de nombreuses lignes de méthodes enchaînées ainsi que des jointures supplémentaires que nous ne voulons pas effectuer à de nombreux endroits, mais seulement à celui-ci. Les données jointes sont ensuite utilisées par la méthode apply_filters, qui ajoute le filtrage de données approprié, sur la base des paramètres GET. Bien sûr, nous pouvons transférer certaines de ces références à la portée, mais n'est-ce pas là le problème que nous essayons de résoudre ?

Solution

Puisque nous connaissons déjà un problème, nous devons le résoudre. Sur la base de la référence dans l'introduction, nous utiliserons ici l'approche PORO. Dans ce cas précis, cette approche s'appelle l'objet de requête, qui est un développement du concept d'objet de service.

Créons un nouveau répertoire nommé "services", situé dans le répertoire apps de notre projet. Nous y créerons une classe nommée TransactionsQuery.

classe TransactionsQuery
fin

L'étape suivante consiste à créer un initialisateur dans lequel un chemin d'appel par défaut sera créé pour notre objet.

classe TransactionsQuery
  def initialize(scope = Transaction.all)
    @scope = scope
  end
end

Grâce à cela, nous pourrons transférer la relation de l'enregistrement actif à notre installation. Nous pouvons maintenant transférer tous nos champs d'application à la classe, qui ne sont nécessaires que dans le contrôleur présenté.

classe TransactionsQuery
  def initialize(scope = Transaction.all)
    @scope = scope
  end

  private

  def active(scope)
    scope.joins(<<-SQL
      LEFT OUTER JOIN source ON transactions.source_id = source.id
      AND source.accepted_at IS NOT NULL
    SQL
  end

  def visible(scope)
    scope.where(visible : true)
  end

  def for_publishers(scope)
    scope.select("transactions.*")
         .joins(:account)
         .where("accounts.owner_type = 'Publisher'")
         .joins("JOIN publishers ON owner_id = publishers.id")
  fin
fin

Il nous manque encore la partie la plus importante, à savoir rassembler les données dans une chaîne de caractères et rendre l'interface publique. La méthode par laquelle nous rassemblerons toutes les données sera appelée "call".

Ce qui est vraiment important, c'est que nous utiliserons la variable d'instance @scope à cet endroit, où se trouve la portée de notre appel.

classe TransactionsQuery
  ...
  def call
    visible(@scope)
        .then(&method(:active))
        .then(&method(:for_publishers))
        .order(:created_at)
  end

  private
  ...
fin

L'ensemble de la classe se présente comme suit :

classe TransactionsQuery
  def initialize(scope = Transaction.all)
    @scope = scope
  end

  def call
    visible(@scope)
        .then(&method(:active))
        .then(&method(:for_publishers))
        .order(:created_at)
  end

  private

  def active(scope)
    scope.joins(<<-SQL
      LEFT OUTER JOIN source ON transactions.source_id = source.id
      AND source.accepted_at IS NOT NULL
    SQL
  end

  def visible(scope)
    scope.where(visible : true)
  end

  def for_publishers(scope)
    scope.select("transactions.*")
         .joins(:account)
         .where("accounts.owner_type = 'Publisher'")
         .joins("JOIN publishers ON owner_id = publishers.id")
  fin
fin

Après notre nettoyage, le modèle paraît nettement plus léger. Ici, nous nous concentrons uniquement sur la validation des données et les relations entre les autres modèles.

classe Transaction < ActiveRecord::Base
  belongs_to :account
  has_one :withdrawal_item
fin

Le contrôleur a déjà mis en œuvre notre solution ; nous avons déplacé toutes les requêtes supplémentaires dans une classe distincte. Cependant, les appels que nous n'avions pas dans le modèle restent un problème non résolu. Après quelques modifications, notre action d'indexation ressemble à ceci :

classe TransactionsController < ApplicationController
  def index
    @transactions = TransactionsQuery.new
                                     .call
                                     .joins("LEFT JOIN withdrawal_items ON withdrawal_items.accounting_event_id = transactions.id")
                                     .joins("LEFT JOIN withdrawals ON withdrawals.id = withdrawal_items.withdrawal_id OR
 (withdrawals.id = source.resource_id AND source.resource_type = 'Withdrawal')")
                                     .order(:created_at)
                                     .page(params[:page])
                                     .per(params[:page])
    @transactions = apply_filters(@transactions)
  end
end

Solution

Dans le cas de la mise en œuvre de bonnes pratiques et de conventions, une bonne idée peut consister à remplacer toutes les occurrences similaires d'un problème donné. Par conséquent, nous déplacerons la requête SQL de l'action d'index vers l'objet de requête séparé. Nous appellerons cela un TransactionsFilterableQuery classe. Le style dans lequel nous préparons le cours sera similaire à celui présenté dans TransactionsQuery. Dans le cadre de la code un enregistrement plus intuitif des grandes requêtes SQL sera introduit en contrebande, à l'aide de chaînes de caractères multilignes appelées heredoc. La solution disponible se trouve ci-dessous :

classe TransactionsFilterableQuery
  def initialize(scope = Transaction.all)
    @scope = scope
  end

  def call
    withdrawal(@scope).then(&method(:withdrawal_items))
  end

  private

  def retrait(scope)
    scope.joins(<<-SQL
      LEFT JOIN withdrawals ON withdrawals.id = withdrawal_items.withdrawal_id OR
      (withdrawals.id = source.resource_id AND source.resource_type = 'Withdrawal')
    SQL
  fin

  def withdrawal_items(scope)
    scope.joins(<<-SQL
      LEFT JOIN withdrawal_items ON withdrawal_items.accounting_event_id = transactions.id
    SQL
  end
end

En cas de modification du contrôleur, nous réduisons le nombre de lignes en ajoutant l'objet de requête. Il est important de tout séparer, sauf la partie responsable de la pagination.

classe TransactionsController < ApplicationController
  def index
    @transactions = TransactionsQuery.new.call.then do |scope|
      TransactionsFilterableQuery.new(scope).call
    end.page(params[:page]).per(params[:page])

    @transactions = apply_filters(@transactions)
  end
end

Résumé

L'objet de requête change beaucoup l'approche de l'écriture des requêtes SQL. Dans ActiveRecord, il est très facile de placer toute la logique de l'entreprise et de la base de données dans le modèle, puisque tout se trouve au même endroit. Cela fonctionne très bien pour les petites applications. Au fur et à mesure que la complexité du projet augmente, nous plaçons la logique à d'autres endroits. Le même objet de requête vous permet de regrouper les requêtes des membres dans un problème spécifique.

Grâce à cela, nous avons une possibilité facile d'héritage ultérieur du code et, en raison du typage de canard, vous pouvez également utiliser ces solutions dans d'autres modèles. L'inconvénient de cette solution est l'augmentation de la quantité de code et la fragmentation des responsabilités. Toutefois, la décision de relever ou non un tel défi dépend de nous et de notre degré d'inconfort vis-à-vis des modèles lourds.

Articles connexes

Développement de logiciels

Construire des applications web à l'épreuve du temps : les conseils de l'équipe d'experts de The Codest

Découvrez comment The Codest excelle dans la création d'applications web évolutives et interactives à l'aide de technologies de pointe, offrant une expérience utilisateur transparente sur toutes les plateformes. Découvrez comment notre expertise favorise la transformation numérique et la...

LE CODEST
Développement de logiciels

Les 10 premières entreprises de développement de logiciels basées en Lettonie

Découvrez les principales sociétés de développement de logiciels en Lettonie et leurs solutions innovantes dans notre dernier article. Découvrez comment ces leaders de la technologie peuvent vous aider à développer votre entreprise.

thecodest
Solutions pour les entreprises et les grandes entreprises

L'essentiel du développement de logiciels Java : Un guide pour une externalisation réussie

Explorez ce guide essentiel sur le développement réussi de logiciels Java outsourcing pour améliorer l'efficacité, accéder à l'expertise et assurer la réussite des projets avec The Codest.

thecodest
Développement de logiciels

Le guide ultime de l'externalisation en Pologne

L'essor de outsourcing en Pologne est dû aux progrès économiques, éducatifs et technologiques, qui favorisent la croissance des technologies de l'information et un climat propice aux entreprises.

TheCodest
Solutions pour les entreprises et les grandes entreprises

Le guide complet des outils et techniques d'audit informatique

Les audits informatiques garantissent la sécurité, l'efficacité et la conformité des systèmes. Pour en savoir plus sur leur importance, lisez l'article complet.

The Codest
Jakub Jakubowicz CTO & Co-Fondateur

Abonnez-vous à notre base de connaissances et restez au courant de l'expertise du secteur des technologies de l'information.

    A propos de nous

    The Codest - Entreprise internationale de développement de logiciels avec des centres technologiques en Pologne.

    Royaume-Uni - Siège

    • Bureau 303B, 182-184 High Street North E6 2JA
      Londres, Angleterre

    Pologne - Les pôles technologiques locaux

    • Parc de bureaux Fabryczna, Aleja
      Pokoju 18, 31-564 Kraków
    • Brain Embassy, Konstruktorska
      11, 02-673 Varsovie, Pologne

      The Codest

    • Accueil
    • A propos de nous
    • Services
    • Études de cas
    • Savoir comment
    • Carrières
    • Dictionnaire

      Services

    • Conseil consultatif
    • Développement de logiciels
    • Développement backend
    • Développement frontal
    • Staff Augmentation
    • Développeurs backend
    • Ingénieurs en informatique dématérialisée
    • Ingénieurs des données
    • Autres
    • Ingénieurs AQ

      Ressources

    • Faits et mythes concernant la coopération avec un partenaire externe de développement de logiciels
    • Des États-Unis à l'Europe : Pourquoi les startups américaines décident-elles de se délocaliser en Europe ?
    • Comparaison des pôles de développement Tech Offshore : Tech Offshore Europe (Pologne), ASEAN (Philippines), Eurasie (Turquie)
    • Quels sont les principaux défis des CTO et des DSI ?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • Conditions d'utilisation du site web

    Copyright © 2025 par The Codest. Tous droits réservés.

    fr_FRFrench
    en_USEnglish de_DEGerman sv_SESwedish da_DKDanish nb_NONorwegian fiFinnish pl_PLPolish arArabic it_ITItalian jaJapanese ko_KRKorean es_ESSpanish nl_NLDutch etEstonian elGreek fr_FRFrench