window.pipedriveLeadboosterConfig = { base: 'leadbooster-chat.pipedrive.com', companyId: 11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', versjon: 2, } ;(function () { var w = vindu if (w.LeadBooster) { console.warn('LeadBooster finnes allerede') } 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 }) }, } } })() Optimalisering av kode med Query Objects - The Codest
The Codest
  • Om oss
  • Tjenester
    • Programvareutvikling
      • Frontend-utvikling
      • Backend-utvikling
    • Staff Augmentation
      • Frontend-utviklere
      • Backend-utviklere
      • Dataingeniører
      • Ingeniører i skyen
      • QA-ingeniører
      • Annet
    • Det rådgivende
      • Revisjon og rådgivning
  • Industrier
    • Fintech og bankvirksomhet
    • E-commerce
    • Adtech
    • Helseteknologi
    • Produksjon
    • Logistikk
    • Bilindustrien
    • IOT
  • Verdi for
    • ADMINISTRERENDE DIREKTØR
    • CTO
    • Leveransesjef
  • Vårt team
  • Casestudier
  • Vet hvordan
    • Blogg
    • Møter
    • Webinarer
    • Ressurser
Karriere Ta kontakt med oss
  • Om oss
  • Tjenester
    • Programvareutvikling
      • Frontend-utvikling
      • Backend-utvikling
    • Staff Augmentation
      • Frontend-utviklere
      • Backend-utviklere
      • Dataingeniører
      • Ingeniører i skyen
      • QA-ingeniører
      • Annet
    • Det rådgivende
      • Revisjon og rådgivning
  • Verdi for
    • ADMINISTRERENDE DIREKTØR
    • CTO
    • Leveransesjef
  • Vårt team
  • Casestudier
  • Vet hvordan
    • Blogg
    • Møter
    • Webinarer
    • Ressurser
Karriere Ta kontakt med oss
Pil tilbake GÅ TILBAKE
2019-03-08
Programvareutvikling

Optimalisering av kode med Query Objects

The Codest

Tomasz Szkaradek

Utviklingsarkitekt

Det er ganske sannsynlig at du på jobb har støtt på overbelastede modeller og et stort antall anrop i kontrollerne ganske mange ganger. Basert på kunnskapen i Rails-miljøet, skal jeg i denne artikkelen foreslå en enkel løsning på dette problemet.

Et svært viktig aspekt ved Rails-applikasjonen er å minimere antallet overflødige avhengigheter, og derfor har hele Rails-miljøet i det siste fremmet serviceobjekttilnærmingen og bruken av PORO-metoden (Pure Old Ruby Object). En beskrivelse av hvordan du bruker en slik løsning finner du her. I denne artikkelen vil vi løse konseptet trinn for trinn og tilpasse det til problemet.

Problem

I en hypotetisk applikasjon har vi å gjøre med et komplisert transaksjonssystem. Modellen vår, som representerer hver transaksjon, har et sett med scopes som hjelper deg med å hente data. Det er en stor jobbforbedring, siden det finnes på ett sted. Dette varer imidlertid ikke lenge. Med utviklingen av applikasjonen blir prosjekt blir mer og mer komplisert. Scopes har ikke lenger enkle "hvor"-referanser, vi mangler data og begynner å laste inn relasjoner. Etter en stund minner det om et komplisert system av speil. Og, hva verre er, vi vet ikke hvordan vi skal gjøre en flerlinjers lambda!

Nedenfor finner du en allerede utvidet applikasjonsmodell. Transaksjonene i betalingssystemet er lagret i. Som du kan se i eksempelet nedenfor:

class 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)
end

Modellen er én ting, men etter hvert som prosjektet øker i omfang, begynner også kontrollerne å svulme opp. La oss se på eksempelet nedenfor:

class TransactionsController < ApplikasjonsController
  def index
    @transaksjoner = Transaksjon.for_utgivere
                                   .active
                                   .visible
                                   .joins("LEFT JOIN withdrawal_items ON withdrawal_items.transaction_id = transactions.id")
                                   .joins("LEFT JOIN uttak ON uttak.id = uttaksposter.uttak_id OR
 (uttak.id = kilde.ressurs_id AND kilde.ressurstype = 'Uttak')")
                                   .order(:created_at)
                                   .side(params[:side])
                                   .per(params[:side])
    @transaksjoner = apply_filters(@transaksjoner)
  end
end

Her kan vi se mange linjer med kjedede metoder sammen med ekstra sammenføyninger som vi ikke ønsker å utføre mange steder, bare i akkurat denne. De vedlagte dataene brukes senere av apply_filters-metoden, som legger til riktig datafiltrering, basert på GET-parametrene. Vi kan selvfølgelig overføre noen av disse referansene til scope, men er det ikke dette problemet vi egentlig prøver å løse?

Løsning

Siden vi allerede vet om et problem vi har, må vi løse dette. Basert på referansen i innledningen vil vi her bruke PORO-tilnærmingen. I dette tilfellet kalles denne tilnærmingen spørringsobjektet, som er en videreutvikling av tjenesteobjektkonseptet.

La oss opprette en ny katalog med navnet "services", som ligger i app-katalogen i prosjektet vårt. Der oppretter vi en klasse med navnet TransactionsQuery.

class TransactionsQuery
slutt

Som et neste trinn må vi opprette en initialisator der en standard anropsbane for objektet vårt vil bli opprettet

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

Takket være dette vil vi kunne overføre forholdet fra den aktive posten til anlegget vårt. Nå kan vi overføre alle våre scopes til klassen, som bare er nødvendig i den presenterte kontrolleren.

klasse 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("transaksjoner.*")
         .joins(:konto)
         .where("accounts.owner_type = 'utgiver'")
         .joins("JOIN publishers ON owner_id = publishers.id")
  end
end

Vi mangler fortsatt den viktigste delen, nemlig å samle data i en streng og gjøre grensesnittet offentlig. Metoden der vi samler alt sammen, får navnet "call".

Det som virkelig er viktig, er at vi bruker instansevariabelen @scope der, der omfanget av kallet vårt befinner seg.

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

  private
  ...
end

Hele klassen presenterer seg som følger:

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

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

  privat

  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("transaksjoner.*")
         .joins(:konto)
         .where("accounts.owner_type = 'utgiver'")
         .joins("JOIN publishers ON owner_id = publishers.id")
  end
end

Etter oppryddingen vår ser modellen definitivt lettere ut. Nå fokuserer vi kun på datavalidering og relasjoner mellom andre modeller.

class Transaction < ActiveRecord::Base
  tilhører_til :konto
  has_one :uttak_item
end

Kontrolleren har allerede implementert løsningen vår, og vi har flyttet alle tilleggsspørringer til en egen klasse. Anropene som vi ikke hadde i modellen, er imidlertid fortsatt et uløst problem. Etter noen endringer ser indekshandlingen vår slik ut:

class TransactionsController < ApplikasjonsController
  def index
    @transaksjoner = TransactionsQuery.new
                                     .call
                                     .joins("LEFT JOIN withdrawal_items ON withdrawal_items.accounting_event_id = transactions.id")
                                     .joins("LEFT JOIN uttak ON uttak.id = uttaksposter.uttak_id OR
 (uttak.id = kilde.ressurs_id AND kilde.ressurstype = 'Uttak')")
                                     .order(:created_at)
                                     .side(params[:side])
                                     .per(params[:side])
    @transaksjoner = apply_filters(@transaksjoner)
  end
end

Løsning

Når det gjelder implementering av god praksis og konvensjoner, kan det være en god idé å erstatte alle lignende forekomster av et gitt problem. Derfor flytter vi SQL-spørringen fra indekshandlingen til det separate spørringsobjektet. Vi vil kalle dette en TransactionsFilterableQuery klasse. Stilen som vi forbereder klassen i, vil være lik den som presenteres i TransactionsQuery. Som en del av kode endringer, vil en mer intuitiv registrering av store SQL-spørringer bli smuglet inn ved hjelp av flerlinjers tegnstrenger kalt Heredoc. Den tilgjengelige løsningen finner du nedenfor:

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

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

  private

  def uttak(scope)
    scope.joins(<<-SQL
      LEFT JOIN uttak ON uttak.id = uttak_items.uttak_id OR
      (uttak.id = kilde.ressurs_id AND kilde.ressurstype = 'Uttak')
    SQL
  end

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

Ved endringer i kontrolleren reduserer vi mengden linjer ved å legge til spørringsobjektet. Det er viktig at vi skiller alt unntatt den delen som er ansvarlig for paginering.

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

    @transaksjoner = apply_filters(@transaksjoner)
  end
end

Sammendrag

Query Object endrer mye i tilnærmingen til å skrive SQL-spørringer. I ActiveRecord er det veldig enkelt å plassere all forretnings- og databaselogikk i modellen, siden alt er samlet på ett sted. Dette fungerer ganske bra for mindre applikasjoner. Etter hvert som kompleksiteten i prosjektet øker, legger vi logikken andre steder. Det samme spørringsobjektet gjør det mulig å gruppere spørringer til et bestemt problem.

Takket være dette har vi en enkel mulighet for senere nedarving av koden, og på grunn av duck typing kan du også bruke disse løsningene i andre modeller. Ulempen med denne løsningen er en større mengde kode og fragmentering av ansvar. Men om vi ønsker å ta en slik utfordring eller ikke, avhenger av oss selv og hvor mye vi plages av fete modeller.

Relaterte artikler

Programvareutvikling

Bygg fremtidssikre webapper: Innsikt fra The Codests ekspertteam

Oppdag hvordan The Codest utmerker seg når det gjelder å skape skalerbare, interaktive webapplikasjoner med banebrytende teknologi som gir sømløse brukeropplevelser på tvers av alle plattformer. Finn ut hvordan ekspertisen vår driver digital transformasjon og...

THECODEST
Programvareutvikling

Topp 10 Latvia-baserte programvareutviklingsselskaper

I vår nyeste artikkel kan du lese mer om Latvias beste programvareutviklingsselskaper og deres innovative løsninger. Oppdag hvordan disse teknologilederne kan bidra til å løfte virksomheten din.

thecodest
Løsninger for bedrifter og oppskalering

Grunnleggende om Java-programvareutvikling: En guide til vellykket outsourcing

Utforsk denne viktige veiledningen om vellykket outsourcing av Java-programvareutvikling for å øke effektiviteten, få tilgang til ekspertise og drive frem prosjektsuksess med The Codest.

thecodest
Programvareutvikling

Den ultimate guiden til outsourcing i Polen

Den kraftige økningen i outsourcing i Polen er drevet av økonomiske, utdanningsmessige og teknologiske fremskritt, noe som fremmer IT-vekst og et forretningsvennlig klima.

TheCodest
Løsninger for bedrifter og oppskalering

Den komplette guiden til verktøy og teknikker for IT-revisjon

IT-revisjoner sørger for sikre, effektive og kompatible systemer. Les hele artikkelen for å lære mer om viktigheten av dem.

The Codest
Jakub Jakubowicz CTO og medgrunnlegger

Abonner på vår kunnskapsbase og hold deg oppdatert på ekspertisen fra IT-sektoren.

    Om oss

    The Codest - Internasjonalt programvareutviklingsselskap med teknologisentre i Polen.

    Storbritannia - Hovedkvarter

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

    Polen - Lokale teknologisentre

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

      The Codest

    • Hjem
    • Om oss
    • Tjenester
    • Casestudier
    • Vet hvordan
    • Karriere
    • Ordbok

      Tjenester

    • Det rådgivende
    • Programvareutvikling
    • Backend-utvikling
    • Frontend-utvikling
    • Staff Augmentation
    • Backend-utviklere
    • Ingeniører i skyen
    • Dataingeniører
    • Annet
    • QA-ingeniører

      Ressurser

    • Fakta og myter om samarbeid med en ekstern programvareutviklingspartner
    • Fra USA til Europa: Hvorfor velger amerikanske oppstartsbedrifter å flytte til Europa?
    • Sammenligning av Tech Offshore Development Hubs: Tech Offshore Europa (Polen), ASEAN (Filippinene), Eurasia (Tyrkia)
    • Hva er de største utfordringene for CTO-er og CIO-er?
    • The Codest
    • The Codest
    • The Codest
    • Retningslinjer for personver
    • Vilkår for bruk av nettstedet

    Opphavsrett © 2025 av The Codest. Alle rettigheter forbeholdt.

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