window.pipedriveLeadboosterConfig = { bas: 'leadbooster-chat.pipedrive.com', företagId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2, } ;(funktion () { var w = fönster if (w.LeadBooster) { console.warn('LeadBooster finns redan') } annars { w.LeadBooster = { q: [], on: funktion (n, h) { this.q.push({ t: "o", n: n, h: h }) }, trigger: funktion (n) { this.q.push({ t: 't', n: n }) }, } } })() Optimera kod med Query Objects - The Codest
Codest
  • Om oss
  • Tjänster
    • Utveckling av programvara
      • Frontend-utveckling
      • Backend-utveckling
    • Staff Augmentation
      • Frontend-utvecklare
      • Backend-utvecklare
      • Dataingenjörer
      • Ingenjörer inom molntjänster
      • QA-ingenjörer
      • Övriga
    • Det rådgivande
      • Revision och rådgivning
  • Industrier
    • Fintech & bankverksamhet
    • E-commerce
    • Adtech
    • Hälsoteknik
    • Tillverkning
    • Logistik
    • Fordon
    • IOT
  • Värde för
    • VD OCH KONCERNCHEF
    • CTO
    • Leveranschef
  • Vårt team
  • Fallstudier
  • Vet hur
    • Blogg
    • Möten
    • Webbinarier
    • Resurser
Karriär Ta kontakt med oss
  • Om oss
  • Tjänster
    • Utveckling av programvara
      • Frontend-utveckling
      • Backend-utveckling
    • Staff Augmentation
      • Frontend-utvecklare
      • Backend-utvecklare
      • Dataingenjörer
      • Ingenjörer inom molntjänster
      • QA-ingenjörer
      • Övriga
    • Det rådgivande
      • Revision och rådgivning
  • Värde för
    • VD OCH KONCERNCHEF
    • CTO
    • Leveranschef
  • Vårt team
  • Fallstudier
  • Vet hur
    • Blogg
    • Möten
    • Webbinarier
    • Resurser
Karriär Ta kontakt med oss
Pil tillbaka GÅ TILLBAKA
2019-03-08
Utveckling av programvara

Optimera kod med Query Objects

Codest

Tomasz Szkaradek

Utvecklingsarkitekt

Det är ganska troligt att du på jobbet har stött på överbelastade modeller och ett stort antal anrop i styrenheterna ganska många gånger. Baserat på kunskapen i Rails-miljön kommer jag i den här artikeln att föreslå en enkel lösning på detta problem.

En mycket viktig aspekt av Rails-applikationen är att minimera antalet redundanta beroenden, vilket är anledningen till att hela Rails-miljön nyligen har främjat serviceobjektmetoden och användningen av PORO-metoden (Pure Old Ruby Object). En beskrivning av hur man använder en sådan lösning hittar du här. I den här artikeln kommer vi att lösa konceptet steg för steg och anpassa det till problemet.

Problem

I en hypotetisk applikation har vi att göra med ett komplicerat transaktionssystem. Vår modell, som representerar varje transaktion, har en uppsättning scopes som hjälper dig att hämta data. Det underlättar arbetet eftersom det finns på ett och samma ställe. Detta varar dock inte länge. I och med utvecklingen av applikationen projekt blir mer och mer komplicerat. Scopes har inte längre enkla "var"-referenser, vi saknar data och börjar ladda relationer. Efter ett tag påminner det om ett komplicerat system av speglar. Och, vad värre är, vi vet inte hur man gör en lambda med flera rader!

Nedan hittar du en redan utökad applikationsmodell. Betalningssystemets transaktioner lagras i. Som du kan se i exemplet nedan:

klass Transaktion  { where(visible: true) }

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

Modellen är en sak, men när skalan på vårt projekt ökar börjar även controllers att svälla. Låt oss titta på exemplet nedan:

klass TransactionsController < ApplikationsController
  def index
    @transactions = Transaction.for_publishers
                                   .aktiv
                                   .synlig
                                   .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])
    @transaktioner = tillämpa_filter(@transaktioner)
  slut
slut

Här kan vi se många rader av kedjade metoder tillsammans med ytterligare sammanfogningar som vi inte vill utföra på många ställen, bara på just detta. De bifogade uppgifterna används senare av metoden apply_filters, som lägger till lämplig datafiltrering, baserat på GET-parametrarna. Naturligtvis kan vi överföra några av dessa referenser till scope, men är det inte det här problemet som vi faktiskt försöker lösa?

Lösning

Eftersom vi redan vet att vi har ett problem måste vi lösa det. Baserat på referensen i inledningen kommer vi här att använda PORO-metoden. I just det här fallet kallas metoden för query object, vilket är en utveckling av konceptet service objects.

Låt oss skapa en ny katalog med namnet "services", som ligger i appskatalogen i vårt projekt. Där kommer vi att skapa en klass med namnet TransactionsQuery.

klass TransactionsQuery
slut

Som ett nästa steg måste vi skapa en initialiserare där en standardanropsväg för vårt objekt kommer att skapas

klass TransactionsQuery
  def initialize(scope = Transaktion.all)
    @scope = omfattning
  slut
slut

Tack vare detta kommer vi att kunna överföra relationen från den aktiva posten till vår anläggning. Nu kan vi överföra alla våra scopes till klassen, som bara behövs i den presenterade styrenheten.

klass TransactionsQuery
  def initialize(scope = Transaktion.all)
    @scope = omfattning
  slut

  privat

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

  def synlig(omfattning)
    scope.where(synlig: true)
  slut

  def for_publishers(omfattning)
    scope.select("transaktioner.*")
         .joins(:konto)
         .where("accounts.owner_type = 'Publisher'")
         .joins("JOIN publishers ON owner_id = publishers.id")
  slut
slut

Vi saknar fortfarande den viktigaste delen, dvs. att samla data i en sträng och göra gränssnittet publikt. Metoden där vi sammanfogar allt kommer att kallas "call".

Det som verkligen är viktigt är att vi kommer att använda instansvariabeln @scope där, där omfattningen av vårt anrop finns.

klass TransactionsQuery
  ...
  def anrop
    synlig(@scope)
        .then(&metod(:aktiv))
        .then(&method(:for_publishers))
        .order(:created_at)
  slut

  privat
  ...
slut

Hela klassen presenterar sig som följande:

klass TransactionsQuery
  def initialize(scope = Transaktion.all)
    @scope = omfattning
  slut

  def anrop
    synlig(@scope)
        .then(&metod(:aktiv))
        .then(&method(:for_publishers))
        .order(:created_at)
  slut

  privat

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

  def synlig(omfattning)
    scope.where(synlig: true)
  slut

  def for_publishers(omfattning)
    scope.select("transaktioner.*")
         .joins(:konto)
         .where("accounts.owner_type = 'Publisher'")
         .joins("JOIN publishers ON owner_id = publishers.id")
  slut
slut

Efter vår upprensning ser modellen definitivt lättare ut. Där fokuserar vi bara på datavalideringen och relationerna mellan andra modeller.

klass Transaktion < ActiveRecord::Bas
  tillhör_till :konto
  har_ett :uttag_objekt
slut

Styrenheten har redan implementerat vår lösning; vi har flyttat alla ytterligare frågor till en separat klass. De anrop som vi inte hade i modellen är dock fortfarande ett olöst problem. Efter några ändringar ser vår indexåtgärd ut så här:

klass TransactionsController < ApplikationsController
  def index
    @transaktioner = 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])
    @transaktioner = tillämpa_filter(@transaktioner)
  slut
slut

Lösning

När det gäller att implementera god praxis och konventioner kan det vara en bra idé att ersätta alla liknande förekomster av ett visst problem. Därför kommer vi att flytta SQL-frågan från indexåtgärden till det separata frågeobjektet. Vi kommer att kalla detta för en TransactionsFilterableQuery klass. Stilen, som vi förbereder klassen i, kommer att likna den som presenteras i TransactionsQuery. Som en del av kod kommer en mer intuitiv registrering av stora SQL-frågor att smugglas in, med hjälp av flerradiga teckensträngar som kallas heredoc. Den lösning som finns tillgänglig hittar du nedan:

klass TransactionsFilterableQuery
  def initialize(scope = Transaktion.all)
    @scope = omfattning
  slut

  def anrop
    withdrawal(@scope).then(&method(:withdrawal_items))
  slut

  privat

  def uttag(omfattning)
    scope.joins(<<-SQL
      LEFT JOIN withdrawals ON withdrawals.id = withdrawal_items.withdrawal_id OR
      (withdrawals.id = source.resource_id AND source.resource_type = 'Uttag')
    SQL
  slut

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

Vid ändringar i styrenheten minskar vi mängden rader genom att lägga till frågeobjektet. Det är viktigt att vi separerar allt utom den del som ansvarar för paginering.

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

    @transaktioner = tillämpa_filter(@transaktioner)
  slut
slut

Sammanfattning

Query object förändrar mycket i sättet att skriva SQL-frågor. I ActiveRecord är det mycket enkelt att placera all affärs- och databaslogik i modellen eftersom allt finns på ett och samma ställe. Detta kommer att fungera bra för mindre applikationer. När komplexiteten i projektet ökar lägger vi logiken på andra ställen. Samma frågeobjekt gör att du kan gruppera medlemsfrågor till ett specifikt problem.

Tack vare detta har vi en enkel möjlighet till senare nedärvning av koden och på grund av duck typing kan du även använda dessa lösningar i andra modeller. Nackdelen med denna lösning är en större mängd kod och fragmentering av ansvaret. Men om vi vill ta oss an en sådan utmaning eller inte beror på oss själva och hur mycket vi störs av feta modeller.

Relaterade artiklar

Utveckling av programvara

Bygg framtidssäkrade webbappar: Insikter från The Codest:s expertteam

Upptäck hur The Codest utmärker sig genom att skapa skalbara, interaktiva webbapplikationer med banbrytande teknik som ger sömlösa användarupplevelser på alla plattformar. Läs om hur vår expertis driver digital omvandling och affärsutveckling...

DEKODEST
Utveckling av programvara

Topp 10 Lettlandsbaserade mjukvaruutvecklingsföretag

Läs mer om Lettlands främsta mjukvaruutvecklingsföretag och deras innovativa lösningar i vår senaste artikel. Upptäck hur dessa teknikledare kan hjälpa till att lyfta ditt företag.

thecodest
Lösningar för företag och uppskalningsföretag

Java Software Development Essentials: En guide till framgångsrik outsourcing

Utforska denna viktiga guide om framgångsrik outsourcing av Java-programvaruutveckling för att förbättra effektiviteten, få tillgång till expertis och driva projektframgång med The Codest.

thecodest
Utveckling av programvara

Den ultimata guiden till outsourcing i Polen

Den kraftiga ökningen av outsourcing i Polen drivs av ekonomiska, utbildningsmässiga och tekniska framsteg, vilket främjar IT-tillväxt och ett företagsvänligt klimat.

TheCodest
Lösningar för företag och uppskalningsföretag

Den kompletta guiden till verktyg och tekniker för IT-revision

IT-revisioner säkerställer säkra, effektiva och kompatibla system. Läs mer om hur viktiga de är genom att läsa hela artikeln.

Codest
Jakub Jakubowicz CTO och medgrundare

Prenumerera på vår kunskapsbas och håll dig uppdaterad om expertisen från IT-sektorn.

    Om oss

    The Codest - Internationellt mjukvaruutvecklingsföretag med teknikhubbar i Polen.

    Förenade kungariket - Huvudkontor

    • Kontor 303B, 182-184 High Street North E6 2JA
      London, England

    Polen - Lokala tekniknav

    • Fabryczna Office Park, Aleja
      Pokoju 18, 31-564 Kraków
    • Brain Embassy, Konstruktorska
      11, 02-673 Warszawa, Polen

      Codest

    • Hem
    • Om oss
    • Tjänster
    • Fallstudier
    • Vet hur
    • Karriär
    • Ordbok

      Tjänster

    • Det rådgivande
    • Utveckling av programvara
    • Backend-utveckling
    • Frontend-utveckling
    • Staff Augmentation
    • Backend-utvecklare
    • Ingenjörer inom molntjänster
    • Dataingenjörer
    • Övriga
    • QA-ingenjörer

      Resurser

    • Fakta och myter om att samarbeta med en extern partner för mjukvaruutveckling
    • Från USA till Europa: Varför väljer amerikanska startup-företag att flytta till Europa?
    • Jämförelse av Tech Offshore Development Hubs: Tech Offshore Europa (Polen), ASEAN (Filippinerna), Eurasien (Turkiet)
    • Vilka är de största utmaningarna för CTO:er och CIO:er?
    • Codest
    • Codest
    • Codest
    • Privacy policy
    • Användarvillkor för webbplatsen

    Copyright © 2025 av The Codest. Alla rättigheter reserverade.

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