Nettstedet ditt går fremover og du vokser raskt. Ruby / Rails er det beste programmeringsalternativet. Teamet ditt er større, og du har allerede forlatt konseptet 'fettmodeller, tynne drivere' ( fete modeller, tynne kontrollere ) som designstil for dine Rails-applikasjoner. Imidlertid vil du fortsatt ikke slutte å bruke Rails.
Ikke noe problem. I dag skal vi diskutere hvordan du bruker OOP beste praksis for å gjøre koden renere, mer isolert og separat.
La oss begynne med å se på hvordan du skal bestemme om søknaden din er en god kandidat for refactoring.
Her er en liste over beregninger og spørsmål som jeg vanligvis stiller meg selv for å avgjøre om kodene mine trenger refactoring eller ikke.
Prøv å bruke noe slikt for å finne ut hvor mange linjer med Ruby-kildekoden du har:
find app -iname '*.rb' -type f -exec cat {} ;| wc -l
Denne kommandoen vil søke i alle filer med en .rb-utvidelse (ruby-filer) i / app-mappen, og deretter skrive ut antall linjer. Vær oppmerksom på at dette tallet bare er et estimat, da kommentarlinjer vil være inkludert i denne summen.
Et annet mer nøyaktig og informativt alternativ er å bruke oppgaven rake stats
Rails, som avslører et raskt sammendrag av kodelinjer, antall klasser, antall metoder, forholdet mellom metoder og klasser og forholdet mellom kodelinjer per metode:
*bundle exec rake stats* +----------------------+-------+-----+-------+---------+-----+-------+ | Nombre | Líneas | LOC | Clase | Método | M/C | LOC/M | +----------------------+-------+-----+-------+---------+-----+-------+ | Controladores | 195 | 153 | 6 | 18 | 3 | 6 | | Helpers | 14 | 13 | 0 | 2 | 0 | 4 | | Modelos | 120 | 84 | 5 | 12 | 2 | 5 | | Mailers | 0 | 0 | 0 | 0 | 0 | 0 | | Javascripts | 45 | 12 | 0 | 3 | 0 | 2 | | Bibliotecas | 0 | 0 | 0 | 0 | 0 | 0 | | Controlador specs | 106 | 75 | 0 | 0 | 0 | 0 | | Helper specs | 15 | 4 | 0 | 0 | 0 | 0 | | Modelo specs | 238 | 182 | 0 | 0 | 0 | 0 | | Petición specs | 699 | 489 | 0 | 14 | 0 | 32 | | Routing specs | 35 | 26 | 0 | 0 | 0 | 0 | | Vista specs | 5 | 4 | 0 | 0 | 0 | 0 | +----------------------+-------+-----+-------+---------+-----+-------+ | Total | 1472 |1042 | 11 | 49 | 4 | 19 | +----------------------+-------+-----+-------+---------+-----+-------+ Código LOC: 262 Prueba LOC: 780 Ratio Código a Prueba: 1:3.0
La oss starte med et eksempel fra det virkelige liv.
Anta at vi vil skrive en applikasjon som sporer været for folk som jogger; På hovedsiden kan brukeren se tidspunktene som er angitt.
Hver av tidsoppføringene har dato, avstand, varighet og ytterligere relevant statusinformasjon (f.eks. Vær, terrengtype osv.), Og en gjennomsnittshastighet som kan beregnes når det er nødvendig.
Vi trenger en rapport som viser gjennomsnittsfart og distanse per uke. Hvis gjennomsnittshastigheten ved inngangen er større enn gjennomsnittshastigheten totalt, vil vi varsle brukeren via SMS (for dette eksemplet vil vi bruke Nexmo RESTful API for å sende SMS).
Hovedsiden lar deg velge avstand, dato og klokkeslett du jogger for å lage en oppføring som ligner på denne:
Vi har også en estadísticas
side, som i utgangspunktet er en ukentlig rapport som inkluderer gjennomsnittsfart og distanse som er tilbakelagt per uke.
Katalogstrukturen til aplicación
ser ut som dette:
⇒ tree . ├── assets │ └── ... ├── controllers │ ├── application_controller.rb │ ├── entries_controller.rb │ └── statistics_controller.rb ├── helpers │ ├── application_helper.rb │ ├── entries_helper.rb │ └── statistics_helper.rb ├── mailers ├── models │ ├── entry.rb │ └── user.rb └── views ├── devise │ └── ... ├── entries │ ├── _entry.html.erb │ ├── _form.html.erb │ └── index.html.erb ├── layouts │ └── application.html.erb └── statistics └── index.html.erb
Jeg vil ikke diskutere modellen Usuario
siden det ikke er noe utenom det vanlige, siden vi bruker det med Motto for å implementere autentisering.
Når det gjelder Entrada
modellen, inneholder den forretningslogikken for søknaden vår.
Hver Entrada
tilhører en Usuario
.
Vi validerer tilstedeværelsen av følgende attributter for hver inngang distancia
, períodode_tiempo
, fecha_hora
og estatus
.
Hver gang vi oppretter en oppføring, sammenligner vi brukerens gjennomsnittshastighet med gjennomsnittet av alle brukere i systemet, og varsler brukeren via SMS ved hjelp av Nexmo (Vi vil ikke diskutere hvordan Nexmo-biblioteket brukes, selv om jeg ønsket å demonstrere et tilfelle der vi bruker et eksternt bibliotek).
Merk at Entrada
modellen inneholder mer enn forretningslogikk alene. Den håndterer også noen valideringer og samtaler.
entries_controller.rb
har aksjene GRUSOM hoved (men uten oppdatering). EntriesController#index
får oppføringene for den nåværende brukeren og sorterer postene etter opprettelsesdato, mens EntriesController#create
opprette en ny oppføring. Det er ikke nødvendig å diskutere det åpenbare eller ansvaret til EntriesController#destroy
:
Mens statistics_controller.rb
er ansvarlig for å beregne ukerapporten, StatisticsController#index
får oppføringene for den påloggede brukeren og grupperer dem etter uke, ved hjelp av #group_by
som er i klassen Utallige i Rails. Prøv deretter å dekorere resultatene ved å bruke noen private metoder.
Vi diskuterer ikke synspunkter mye her, da kildekoden er selvforklarende.
Nedenfor er visningen for å liste oppføringene for den påloggede brukeren (index.html.erb
). Dette er mønsteret som skal brukes til å vise resultatene av indekshandling (metode) i inngangshåndtereren:
Merk at vi bruker render @entries
Deler, for å bringe den delte koden i et delvis mønster _entry.html.erb
slik at vi kan beholde koden vår TØRKE og gjenbrukbar:
Det samme gjelder en _forma
delvis. I stedet for å bruke den samme koden med (nye og redigerte) handlinger, lager vi en gjenbrukbar delvis form:
Angående visningen av ukesrapportsiden, statistics/index.html.erb
viser litt statistikk, og rapporterer om brukerens ukentlige aktiviteter ved å gruppere noen oppføringer:
Og til slutt, hjelper for innganger, entries_helper.rb
, inkluderer to hjelpere readable_time_period
og readable_speed
som skal gjøre attributtene lettere å lese:
Ingenting for komplisert så langt.
De fleste av dere kan hevde at refactoring av dette er i strid med prinsippet KYSSE og det vil gjøre systemet mer komplisert.
Så trenger denne appen virkelig å bli omformet?
Absolutt Nei , men vi vil bare vurdere det for eksempler.
Tross alt, hvis du ser på følgende avsnitt og egenskapene som indikerer at en applikasjon trenger refactoring, blir det åpenbart at applikasjonen i vårt eksempel ikke er en gyldig kandidat for refactoring.
La oss starte med å forklare strukturmønsteret MVC i Rails.
Det starter vanligvis med søkemotoren ved å komme med en forespørsel som https://www.toptal.com/jogging/show/1
.
Webserveren mottar forespørselen og bruker rutas
for å definere hva controlador
bruk.
Kontrollere gjør arbeidet med å analysere brukerforespørsler, dataleveranser, informasjonskapsler, økter osv., Og spør deretter modelo
få dataene.
modelos
De er Ruby-klasser som snakker med databasen, lagrer og validerer dataene, utfører forretningslogikken og gjør tunge løft. Visninger er hva brukeren kan se: HTML, CSS, XML, Javascript, JSON.
Hvis vi vil vise sekvensen til en Rails livssyklusforespørsel, vil det se slik ut:
Det jeg ønsker å oppnå er å legge til mer abstraksjon ved å bruke POROer og lage mønsteret til noe som ligner på følgende, for handlingene create/update
:
Og noe lignende til dette for handlinger list/show
:
Ved å legge til POROs abstraksjoner sikrer vi en fullstendig skille mellom ansvar SEGEL , noe Rails ikke mestrer.
For å oppnå det nye designet vil jeg bruke retningslinjene nedenfor, men husk at dette ikke er regler du må følge til punkt og prikke. Tenk på dem som fleksible retningslinjer som gjør refactoring enklere.
ActiveRecord-modeller kan inneholde assosiasjoner og konstanter, men ingenting annet. Det betyr at det ikke blir noen samtaler (bruk serviceobjekter og legg til samtalene der) og ingen valideringer (bruk Form objekter for å inkludere navn og valideringer for modellen).
Hold kontrollere som tynne lag, og ring alltid serviceobjekter. Noen av dere lurer kanskje på hvorfor bruke kontrollere hvis vi vil fortsette å ringe serviceobjekter for å inneholde logikken? Kontrollere er et bra sted å ha ruting HTTP, parameter-parsing, autentisering, innholdsforhandling, å ringe riktig service- eller redigeringsobjekt, fange unntak, formatere svar og returnere riktig HTTP-koden.
Spørringer skal lages med gjenstander spørsmål . Objektmetoder Spørsmål må returnere et objekt, a hasj eller array , men ikke en ActiveRecord-forening.
Unngå å bruke Hjelpere , bruk bedre dekoratører. Hvorfor? En vanlig vanskelighetsgrad med hjelpere i Rails, er at de kan gjøres om til en haug med ikke- OO , som deler et mellomrom og overlapper hverandre. Men det er mye verre, ikke det faktum at du ikke kan bruke polymorfisme med hjelpere Rails - ved å tilby forskjellige implementeringer for forskjellige sammenhenger eller typer, og omgå eller underklassifisere hjelpere. Jeg tror hva slags hjelper i skinner skal de vanligvis brukes til verktøymetoder, ikke til spesifikke brukssaker; hvordan man formaterer modellattributter for presentasjonslogikk. Hold dem lette og enkle å følge. Dekoratører / delegater bedre. ** Hvorfor? Bekymringer ser ut til å være en del av Rails, og de kan tørke ( Tørke opp ) en kode når den deles mellom flere modeller. Det større problemet er imidlertid at bekymringer ikke gjør modellobjektet mer sammenhengende. Bare koden er bedre organisert. Med andre ord er det ingen reell endring i API-modellen.
Prøv å trekke ut Verdifulle gjenstander av modellene for å holde koden renere og gruppe-relaterte attributter.
Før jeg begynner vil jeg diskutere noe annet. Når refactoring starter, ender du som regel med å lure på: 'Er det en god refactoring?'
Hvis du føler at du gjør mer skille eller isolasjon mellom ansvar (selv om det betyr å legge til mer kode og nye filer), er dette en god ting. Når alt kommer til alt, er det veldig god praksis å løsne en applikasjon og gjør det lettere for oss å gjøre en ordentlig enhetstest.
Jeg kommer ikke til å diskutere ting, som å flytte logikk fra kontrollere til modeller, siden jeg antar at du gjør det og er komfortabel med å bruke Rails (vanligvis Skinny Controller og FAT-modell).
For å holde denne artikkelen kortfattet, skal jeg ikke diskutere hvordan jeg skal teste, men dette betyr ikke at du ikke skal teste.
Tvert imot, det burde du start alltid med en test for å sørge for at ting går bra, før du går videre. Dette er noe påkrevd, spesielt når du gjør refactoring.
Vi kan deretter implementere endringer og sørge for at testene går gjennom de aktuelle delene av koden.
For det første, hva er et verdigjenstand?
Martin Fowler Forklare:
Verdiobjekt er et lite objekt, for eksempel et penge- eller datointervallobjekt. Hovedtrekket deres er at de følger verdisemantikk, snarere enn referansesemantikk.
Noen ganger kan du komme i en situasjon der et konsept fortjener sin egen abstraksjon, og hvor dets likhet ikke er basert på verdier, men på identitet. Eksempler på dette kan være: Ruby's Date, URI og Pathname. Utvinning av et verdifullt objekt (eller domenemodell) er en stor bekvemmelighet.
Hvorfor bry seg?
En av de store fordelene med et verdiobjekt er at de hjelper til med å få uttrykksfullhet i koden din. Koden din har en tendens til å være tydeligere, eller i det minste kan det være hvis du har god navngivningspraksis. Siden Value Object er en abstraksjon, fører det til klarere koder og færre feil.
En annen gevinst er uforanderlighet . Objektenes uforanderlighet er veldig viktig. Når vi lagrer visse datasett, som kan brukes i et verdiobjekt, liker jeg vanligvis ikke at dataene blir manipulert.
Når er dette nyttig?
Det er ikke noe perfekt svar på dette spørsmålet. Gjør det som er best for deg og det som gir mest mening i en gitt situasjon.
Utover dette er det noen retningslinjer jeg bruker for å hjelpe meg med å ta den avgjørelsen.
Hvis du tror at en gruppe metoder er relatert, er verdisaker dyrere. Denne uttrykksevnen betyr at et verdiobjekt skal representere et særegent datasett, som kan trekkes av din gjennomsnittlige utvikler bare ved å se på objektets navn.
Hvordan gjør du dette?
Verdisaker bør følge visse regler:
I vårt eksempel oppretter jeg et verdiobjekt EntryStatus
for å abstrakte attributtene Entry#status_weather
og Entry#status_landform
til sin egen klasse, som ser slik ut:
Merk: Dette er bare et PORO (Plain Old Ruby Object), det arver ikke fra ActiveRecord::Base
. Vi har definert lesermetoder for attributtene våre og tildeler dem ved oppstart. Vi bruker også en sammenlignbar blanding for å matche objekter ved hjelp av () -metoden.
Vi kan endre modellen Entry
å bruke verdiobjektet vi har opprettet:
Vi kan også endre metoden EntryController#create
å bruke det nye verdiobjektet tilsvarende:
Hva er et serviceobjekt?
Jobben til et serviceobjekt er å oppbevare koden i et bestemt område av forretningslogikken. I motsetning til stilen 'Fat model' , hvor et lite antall objekter inneholder mange, mange metoder, for all nødvendig logikk, ved å bruke serviceobjekter resulterer i mange klasser, som hver tjener et unikt formål.
Hvorfor? Hva er fordelene?
Trekk fra hverandre. Serviceobjekter hjelper deg med å oppnå mer isolasjon mellom objekter.
Synlighet. Tjenesteobjekter (hvis de er riktig navngitt) viser hva en applikasjon gjør. Jeg kan se gjennom tjenestekatalogen for å se hvilke funksjoner et program gir.
TØRKE og godta endringen. Jeg holder servicevarer så enkle og små som mulig. Jeg komponerer serviceobjekter med andre serviceobjekter, og bruker dem på nytt.
Rengjør og øke testpakken. Tjenestene er raske og enkle å teste, ettersom de er små Ruby-objekter med et inngangspunkt (metoden som kalles). Komplekse tjenester er sammensatt med andre tjenester, slik at du enkelt kan skille testene dine. Ved å bruke serviceobjekter blir det også lettere å hente relaterte objekter uten å laste hele skinnemiljøet.
På den annen side er ingenting perfekt. En ulempe med Service-objekter er at de kan bli overkill for hver eneste lille handling. I disse tilfellene kan du ende opp med å komplisere og ikke forenkle koden din.
Når skal du trekke ut serviceobjekter?
Det er ingen fast regel her heller.
Vanligvis er serviceobjekter best for mellomstore til store systemer: de med en anstendig mengde logikk utover standard CRUD-operasjoner.
Så når du tenker at et stykke kode ikke hører hjemme i katalogen, der du skulle legge det til, er det en god ide å revurdere det og se om det ville være bedre om det gikk til et serviceobjekt.
Her er noen tips for når du skal bruke serviceobjekter:
Hvordan skal du designe serviceobjekter?
Å designe klassen for et serviceobjekt er relativt greit, siden du ikke trenger det perler du skal ikke lære a DLS nytt, men hvis du kan, mer eller mindre, stole på programvaren design ferdigheter du allerede har.
Vanligvis bruker jeg følgende retningslinjer og konvensjoner for å designe tjenesteobjektet:
app/services
. Jeg anbefaler deg å bruke underkataloger for sterke forretningslogiske domener. For eksempel filen app/services/report/generate_weekly.rb
vil definere Report::GenerateWeekly
mens app/services/report/publish_monthly.rb
vil definere Report::PublishMonthly
.ApproveTransaction
, SendTestNewsletter
, ImportUsersFromCsv
.Hvis du ser på StatisticsController#index
, vil du legge merke til en gruppe metoder (weeks_to_date_from
, weeks_to_date_to
, avg_distance
, etc.) gruppert til kontrolleren. Det er ikke bra. Tenk på konsekvensene, hvis du vil generere en ukentlig rapport utenfor statistics_controller
.
I vårt tilfelle skal vi lage Report::GenerateWeekly
og trekk ut logikkrapporten fra StatisticsController
:
Så StatisticsController#index
nå ser det renere ut:
Ved å bruke Service-objektmønsteret grupperer vi koden rundt en kompleks og spesifikk handling og fremmer etableringen av mindre og tydeligere metoder.
Hjemmelekser: vurdere å bruke Verdifullt objekt for WeeklyReport
i stedet for Struct
.
Hva er et objekt? Spørsmål ?
En gjenstand Spørsmål er en PORE, som representerer en spørringsdatabase. Den kan gjenbrukes forskjellige steder i applikasjonen, samtidig som spørringslogikken skjules. Det gir også en god isolert enhet for testing.
Du bør trekke ut komplekse SQL / NoSQL-spørsmål i sine egne klasser.
Hvert objekt Spørsmål er ansvarlig for å returnere et sett med resultater basert på kriteriene / forretningsreglene.
I dette eksemplet har vi ingen spørsmål ( spørsmål ) kompleks, så bruk objekt Spørsmål det ville ikke være effektivt. For demonstrasjonens skyld vil vi imidlertid trekke ut spørringen i Report::GenerateWeekly#call
og vi vil lage generate_entries_query.rb
:
Og i Report::GenerateWeekly#call
, la oss erstatte:
def call @user.entries.group_by(&:week).map do |week, entries| WeeklyReport.new( ... ) end end
med:
def call weekly_grouped_entries = GroupEntriesQuery.new(@user).call weekly_grouped_entries.map do |week, entries| WeeklyReport.new( ... ) end end
Objektmønsteret spørsmål (spørring) hjelper med å holde modelllogikken din strengt relatert til klasseatferd, samtidig som kontrollerne blir tynne. Fordi de ikke er noe annet enn vanlige gamle Ruby-klasser , gjenstandene spørsmål de trenger ikke arve fra ActiveRecord::Base
, og skal ikke være ansvarlige for annet enn spørringskjøring.
Nå skal vi trekke ut logikken med å opprette en ny oppføring til et nytt tjenesteobjekt. La oss bruke konvensjonen og lage CreateEntry
:
Og nå vår EntriesController#create
er som følgende:
def create begin CreateEntry.new(current_user, entry_params).call flash[:notice] = 'Entry was successfully created.' rescue Exception => e flash[:error] = e.message end redirect_to root_path end
Nå begynner ting å bli mer interessante.
Husk at vi i retningslinjene ble enige om at vi ønsket at modellene skulle ha assosiasjoner og konstanter, men ikke noe annet (ingen valideringer eller samtaler). Så la oss starte med å fjerne forklaringene og bruke et Shape-objekt i stedet.
Et formobjekt er et PORO (Plain Old Ruby Object). Ta kommandoen over kontrolleren / tjenesteobjektet når du trenger å snakke med databasen.
Hvorfor bruke Shape-objekter?
Når du trenger å omformidle søknaden din, er det alltid en god ide å huske, hovedansvaret ( SEGEL ).
SEGEL hjelper deg med å ta bedre designbeslutninger, angående ansvaret som en klasse skal ha.
Din databasetabellmodell (en ActiveRecord-modell i sammenheng med Rails) representerer for eksempel en unik databasepost i kode, så det er ingen grunn for deg å være opptatt av noe brukeren din gjør.
Det er her Shape-objektet kommer inn.
Et Shape-objekt er ansvarlig for å representere en form i applikasjonen din. Så hvert inndatafelt kan behandles som et attributt i klassen. Du kan validere at disse attributtene oppfyller noen valideringsregler, og du kan sende de 'rene' dataene dit de skal gå (f.eks. Databasemodellen din eller kanskje søkeoppbyggeren din).
Når skal du bruke et Shape-objekt?
Dette lar deg plassere all skjemalogikken din (navngivningskonvensjoner, valideringer og andre) på ett sted.
Hvordan lage et Shape-objekt?
ActiveModel::Model
(I skinner 3 må du inkludere navn, konvertering og validering, i stedet).Vær oppmerksom på at du kan bruke perle reform , men fortsetter med PORO, vil vi opprette entry_form.rb
som ser slik ut:
Og vi vil endre CreateEntry
for å begynne å bruke Format-objektet EntryForm
:
class CreateEntry ...... ...... def call @entry_form = ::EntryForm.new(@params) if @entry_form.valid? .... else .... end end end
Merk: Noen av dere vil si at det ikke er behov for å få tilgang til Shape-objektet fra Service-objektet, og at vi kan ringe Shape-objektet direkte fra kontrolleren, noe som er et gyldig argument. Imidlertid vil jeg heller ha en klar flyt, det er derfor jeg alltid kaller skjemaobjekt fra tjenesteobjekt.
Som vi avtalt tidligere, vil vi ikke at modellene våre skal inneholde valideringer og samtaler. Vi hentet ut valideringene ved hjelp av Shape-objekter. Men vi bruker fortsatt noen samtaler (after_create
i modell Entry
compare_speed_and_notify_user
).
Hvorfor vil vi fjerne samtalene fra modellene?
Rails utviklere de begynner vanligvis å merke smerter under samtalene under testene. Hvis du ikke tester ActiveRecord-modellene dine, vil du begynne å legge merke til smertene senere, ettersom applikasjonen din vokser og etter hvert som mer logikk er nødvendig for å ringe eller unngå samtaler.
después_*
samtaler brukes primært i forhold til å lagre eller fortsette med objektet.
Når objektet er lagret, er objektets formål (f.eks. Ansvar) oppfylt. Så hvis vi fremdeles ser at anrop blir påkalt, etter at objektet er lagret, er dette sannsynligvis samtaler som ønsker å komme seg ut av objektets ansvarsområde, og det er da vi får problemer.
I vårt tilfelle sender vi en SMS til brukeren, som ikke er relatert til Input-domenet.
En enkel måte å løse problemet på er å flytte samtalen til det relaterte serviceobjektet. Når alt kommer til alt, er det å sende en SMS til den tilsvarende brukeren relatert til tjenesteobjektet CreateEntry
og ikke inngangsmodellen, som sådan.
Ved å gjøre dette, trenger vi ikke lenger å slå av, compare_speed_and_notify_user
i testene våre. Vi har gjort dette til en enkel sak, ved å lage en oppføring uten behov for å sende en SMS, og vi følger en god objektorientert design, ved å sikre at klassene våre har et unikt ansvar ( SEGEL ).
Så nå CreateEntry
det er noe som ligner på dette:
Selv om vi enkelt kan bruke samlingen Draper av visningsmodeller og dekoratører, blir jeg hos PORO for denne artikkelen, slik jeg har gjort hittil.
Det jeg trenger er en klasse som kaller metoder på det dekorerte objektet.
Jeg kan bruke method_missing
for å implementere det, men jeg bruker standard Ruby-biblioteket, SimpleDelegator
. Følgende kode viser hvordan du bruker SimpleDelegator
for å implementere basedekoratøren vår:
% app/decorators/base_decorator.rb require 'delegate' class BaseDecorator Hvorfor _h
metoden?
Denne metoden fungerer som en proxy for visningskontekst. Som standard er visningskonteksten en forekomst av en visningsklasse, dette er ActionView::Base
. Du kan få tilgang til hjelpere av synspunkter som følger:
_h.content_tag :div, 'my-div', class: 'my-class'
For å gjøre det mer praktisk legger vi til en metode decorado
a ApplicationHelper
:
module ApplicationHelper # ..... def decorate(object, klass = nil) klass ||= '#{object.class}Decorator'.constantize decorator = klass.new(object, self) yield decorator if block_given? decorator end # ..... end
Nå kan vi flytte hjelpere EntriesHelper
til dekoratører:
# app/decorators/entry_decorator.rb class EntryDecorator Og vi kan bruke readable_time_period
og readable_speed
som følger:
# app/views/entries/_entry.html.erb - +
- +
Struktur etter refactoring
Vi endte med flere filer, men det er ikke nødvendigvis en dårlig ting (og husk dette, fra begynnelsen av, var vi klar over at dette eksemplet var for demonstrasjonsformål og Nei var nødvendigvis en god brukssak for refactoring):
app ├── assets │ └── ... ├── controllers │ ├── application_controller.rb │ ├── entries_controller.rb │ └── statistics_controller.rb ├── decorators │ ├── base_decorator.rb │ └── entry_decorator.rb ├── forms │ └── entry_form.rb ├── helpers │ └── application_helper.rb ├── mailers ├── models │ ├── entry.rb │ ├── entry_status.rb │ └── user.rb ├── queries │ └── group_entries_query.rb ├── services │ ├── create_entry.rb │ └── report │ └── generate_weekly.rb └── views ├── devise │ └── .. ├── entries │ ├── _entry.html.erb │ ├── _form.html.erb │ └── index.html.erb ├── layouts │ └── application.html.erb └── statistics └── index.html.erb
konklusjon
Selv om vi fokuserer på Rails i dette blogginnlegget, er ikke RoR (Ruby on Rails) en avhengighet av serviceobjekter eller andre POROer. Du kan bruke denne tilnærmingen med hvilken som helst rammeverk , mobil- eller konsollapp.
Når du bruker MVC Som en arkitektur henger alt sammen og bremser deg ned fordi de fleste endringer har innvirkning på andre deler av applikasjonen. Det tvinger deg også til å tenke på hvor du kan legge litt forretningslogikk - skal det gå i modellen, kontrolleren eller utsikten?
Ved å bruke en enkel PORO har vi flyttet forretningslogikken til modeller eller tjenester som ikke arver fra ActiveRecord
, som allerede er en gevinst, for ikke å nevne at vi har klarere kode, som støtter SEGEL og raskere enhetstesting.
En ren arkitektur prøver å sette bruksboksene i midten / toppen av strukturen din slik at du enkelt kan se hva applikasjonen din gjør. Det gjør det også lettere å vedta endringer, fordi det er mer modulært og isolert. Jeg håper jeg har vist hvordan Vanlige gamle rubinobjekter og mer abstraksjoner, det skiller bekymringer, forenkler testing og hjelper til med å produsere ren, vedlikeholdbar kode.
I slekt: Hva er fordelene med Ruby on Rails? Etter to tiår med programmering. Bruk skinner