Mange mennesker lærer Ruby ved at starte med Rails-frameworket, og det er desværre den værst tænkelige måde at lære dette sprog på. Misforstå mig ikke: Rails er fantastisk, det hjælper dig med at bygge webapplikationer hurtigt og effektivt uden at skulle sætte dig ind i mange tekniske detaljer.
Det er rart at møde dig!
Mange mennesker lærer Ruby ved at starte med Rails-frameworket, og det er desværre den værst tænkelige måde at lære dette sprog på. Misforstå mig ikke: Rails er fantastisk, det hjælper dig med at bygge webapplikationer hurtigt og effektivt uden at skulle sætte dig ind i mange tekniske detaljer. De leverer en masse "Rails-magi", som gør, at tingene bare fungerer. Og for en nybegynderprogrammør er det virkelig godt, for det mest behagelige øjeblik i processen er, når du kan sige "den lever!" og se, at alle dele passer sammen, og folk bruger din app. Vi kan godt lide at være "skabere" 🙂 Men der er én ting, der adskiller gode programmører fra gennemsnittet: De gode programmører forstår, hvordan de værktøjer, de bruger, fungerer. Og med "at forstå sine værktøjer" mener jeg ikke at kende alle de metoder og moduler, som et framework stiller til rådighed, men at forstå, hvordan det fungerer, at forstå, hvordan "Rails-magien" sker. Først da kan du føle dig tryg ved at bruge objekter og programmere med Rails. Fundamentet for den objektorienterede programmering og det hemmelige våben, som gør den komplicerede Rails-applikation lettere, er det allerede nævnte PORO, det vil sige Plain Old Ruby Object.
Hvad gemmer der sig egentlig bag dette navn? Hvad er dette store hemmelige våben? Det er en simpel Ruby-klasse, som ikke arver fra noget. Ja, netop det, og så meget mere.
klasse AwesomePoro
slut
Hvordan kan jeg hjælpe dig?
Du udvikler løbende din applikation og tilføjer nye funktioner i takt med, at antallet af brugere og deres forventninger vokser. Du kommer til et punkt, hvor du støder på flere og flere mørke steder med ekstremt snoet logik, de steder, som selv de modigste udviklere undgår som pesten. Jo flere sådanne steder, jo sværere er det at administrere og udvikle applikationen. Et standardeksempel er registreringen af en ny bruger, som udløser en hel gruppe af andre handlinger, der er forbundet med denne begivenhed:
- tjekker IP-adressen i en spam-database,
- sende en e-mail til den nye bruger,
- tilføje en bonus til en anbefalende brugers konto,
- oprettelse af konti i relaterede tjenester,
- og mange flere...
En prøve Kode der er ansvarlig for brugerregistrering, kan se sådan ud:
klasse RegistrationController < ApplicationController
def opret
user = User.new(registration_params)
if user.valid? && ip_valid?(registration_ip)
user.save!
user.add_bonuses
user.synchronize_related_accounts
user.send_email
end
slut
slut
Okay, du har kodet det, alt fungerer, men ... er al denne kode virkelig i orden? Måske kunne vi skrive den bedre? Først og fremmest bryder det med det grundlæggende princip for programmering - enkeltansvar, så vi kan helt sikkert skrive det bedre. Men hvordan? Det er her, den allerede nævnte PORO kommer til at hjælpe dig. Det er nok at adskille en klasse RegistrationService, som kun er ansvarlig for én ting: at underrette alle de relaterede tjenester. Med tjenester mener vi de individuelle handlinger, som vi allerede har udpeget ovenfor. I den samme controller skal du blot oprette et objekt RegistrationService og kalde metoden "fire!" på det. Koden er blevet meget klarere, vores controller fylder mindre, og hver af de nyoprettede klasser er nu kun ansvarlig for én handling, så vi kan nemt udskifte dem, hvis der skulle opstå behov for det.
klasse Registreringsservice
def fire!(params)
bruger = User.new(params)
if user.valid? && ip_validator.valid?(registration_ip)
user.save!
after_registered_events(user)
slut
bruger
slut
privat
def efter_registrerede_begivenheder(bruger)
BonusesCreator.new.fire!(bruger)
AccountsSynchronizator.fire!(bruger)
EmailSender.fire!(bruger)
slut
def ip_validator
@ip_validator ||= IpValidator.new
end
slut
klasse RegistrationController < ApplicationController
def create
user = RegistrationService.new.fire!(registration_params)
end
end
Men Plain Old Ruby Object kan vise sig at være nyttig, ikke kun til controllere. Forestil dig, at den applikation, du er ved at lave, bruger et månedligt faktureringssystem. Den nøjagtige dag for oprettelsen af en sådan fakturering er ikke vigtig for os, vi behøver kun at vide, at det drejer sig om en bestemt måned og et bestemt år. Du kan selvfølgelig indstille dagen til den første dag i hver måned og gemme denne information i et objekt af klassen "Date", men det er hverken en sand information eller noget, du har brug for i din applikation. Ved at bruge PORO kan du oprette en klasse "MonthOfYear", hvis objekter vil gemme præcis de oplysninger, du har brug for. Når du anvender modulet "Comparable" i den, vil det desuden være muligt at iterere og sammenligne dens objekter, ligesom når du bruger Date-klassen.
Klassen MonthOfYear
include Sammenlignelig
attr_reader :år, :måned
def initialize(måned, år)
raise ArgumentError medmindre month.between?(1, 12)
@year, @month = år, måned
slut
def (andet)
[år, måned] [andet.år, anden.måned]
end
end
Introducer mig til Rails.
I Rails-verdenen er vi vant til, at hver klasse er en model, en visning eller en controller. De har også deres præcise placering i mappestrukturen, så hvor kan du placere vores lille PORO-hær? Overvej et par muligheder. Den første tanke, der melder sig, er: Hvis de oprettede klasser hverken er modeller, visninger eller controllere, bør vi placere dem alle i biblioteket "/lib". Teoretisk set er det en god idé, men hvis alle dine PORO-filer lander i en mappe, og applikationen bliver stor, vil denne mappe hurtigt blive et mørkt sted, som du er bange for at åbne. Derfor er det utvivlsomt ikke en god idé.
AwesomeProject
├──app
│ ├─controllere
│ ├─modeller
│ └─visninger
│
└lib
└─tjenester
1TP61Høj poro her
Du kan også navngive nogle af dine klasser, som ikke er ActiveRecord-modeller, og lægge dem i mappen "app/models", og navngive dem, som er ansvarlige for at håndtere andre klasser, som tjenester og lægge dem i mappen "app/services". Det er en ret god løsning, men den har en ulempe: Når man opretter en ny PORO, skal man hver gang tage stilling til, om det er en model eller en service. På den måde kan du komme i en situation, hvor du har to mørke steder i din applikation, bare mindre. Der er endnu en tredje tilgang, nemlig at bruge namespaced klasser og moduler. Det eneste, du skal gøre, er at oprette en mappe med samme navn som en controller eller en model og lægge alle PORO-filer, der bruges af den givne controller eller model, deri.
AwesomeProject
├──app
│ ├─controllere
│ │ ├─registration_controller
│ │ └─registration_service.rb
│ │ └─registration_controller.rb
│ ├─modeller
│ │ ├─settlement
│ │ └─month_of_year.rb
│ │ └─settlement.rb
│ └─views
│
└─lib
Takket være dette arrangement behøver du ikke at sætte et namespace foran navnet på en klasse, når du bruger den. Du har fået kortere kode og en mere logisk organiseret mappestruktur.
Tjek mig ud!
Det er en behagelig overraskelse, at når du bruger PORO, er enhedstestene til din applikation hurtigere og lettere at skrive og senere mere tilbøjelige til at blive forstået af andre. Da hver klasse nu kun er ansvarlig for én ting, kan du hurtigere genkende grænsebetingelserne og nemt tilføje passende testscenarier til dem.
describe MonthOfYear do
subject { MonthOfYear.new(11, 2015) }
it { should be_kind_of Comparable }
describe "opretter ny instans" do
it "initialiseres med korrekt år og måned" do
expect { described_class.new(10, 2015) }.to_not raise_error
end
it "udløser fejl, når den givne måned er forkert" do
expect { described_class.new(0, 2015) }.to raise_error(ArgumentError)
expect { described_class.new(13, 2015) }.to raise_error(ArgumentError)
slut
slut
slut
Jeg håber, vi mødes igen!
De eksempler, vi præsenterede, viser tydeligt, at brugen af PORO forbedrer programmernes læsbarhed og gør dem mere modulære og dermed lettere at administrere og udvide. Princippet om det enkelte ansvar gør det lettere at udskifte bestemte klasser, hvis det er nødvendigt, og det sker uden at forstyrre andre elementer. Det gør også testning af dem til en enklere og hurtigere procedure. Desuden er det på denne måde meget nemmere at holde Rails-modeller og -controllere korte, og vi ved alle, at de har en tendens til at blive unødvendigt store i løbet af udviklingsprocessen.