Många människor lär sig Ruby genom att börja med Rails-ramverket och tyvärr är detta det värsta möjliga sättet att lära sig detta språk. Missförstå mig inte nu: Rails är bra, det hjälper dig att bygga webbapplikationer snabbt och effektivt utan att behöva gå in på många tekniska detaljer.
Det är trevligt att träffa dig!
Många människor lär sig Ruby genom att börja med Rails-ramverket och tyvärr är detta det värsta möjliga sättet att lära sig detta språk. Missförstå mig inte nu: Rails är fantastiskt, det hjälper dig att bygga webbapplikationer snabbt och effektivt utan att behöva gå in på många tekniska detaljer. De tillhandahåller en hel del "Rails-magi" som gör att saker helt enkelt fungerar. Och för en nybörjarprogrammerare är detta verkligen bra, eftersom det trevligaste ögonblicket i processen är när du kan säga "det lever!" och se att alla delar passar ihop och människor använder din app. Vi gillar att vara "skapare" 🙂 Men det finns en sak som skiljer bra programmerare från genomsnittet: de bra förstår hur de verktyg de använder fungerar. Och med "förstå dina verktyg" menar jag inte att känna till alla metoder och moduler som tillhandahålls av ett ramverk, utan att förstå hur det fungerar, förstå hur "Rails-magin" händer. Först då kan du känna dig bekväm med att använda objekt och programmera med Rails. Grunden för den objektorienterade programmeringen, och det hemliga vapnet som gör komplicerade Rails-applikationer enklare, är det som redan nämns i titeln PORO, det vill säga Plain Old Ruby Object
Vad döljer sig egentligen bakom detta namn? Vad är detta stora hemliga vapen? Det är en enkel Ruby-klass som inte ärver från någonting. Ja, just det, och så mycket.
klass AwesomePoro
slut
Hur kan jag hjälpa dig?
Du utvecklar kontinuerligt din applikation och lägger till nya funktioner i takt med att antalet användare och deras förväntningar växer. Du kommer till en punkt där du stöter på fler och fler mörka platser med extremt skruvad logik, platser som undviks som pesten av även de modigaste utvecklarna. Ju fler sådana platser, desto svårare att hantera och utveckla applikationen. Ett standardexempel är åtgärden att registrera en ny användare, vilket utlöser en hel grupp andra åtgärder som är associerade med denna händelse:
- kontroll av IP-adressen i en skräppostdatabas,
- skicka ett e-postmeddelande till den nya användaren,
- lägga till en bonus på ett konto för en rekommenderande användare,
- skapa konton i relaterade tjänster,
- och många fler...
Ett urval kod som ansvarar för användarregistrering kan se ut så här:
klass RegistreringsController < ApplikationsController
def skapa
user = Användare.new(registrering_parametrar)
if user.valid? && ip_valid?(registrering_ip)
användare.spara!
user.add_bonuses
user.synchronize_related_accounts
user.send_email
slut
slut
slut
Okej, du har kodat det, allt fungerar, men ... är all den här koden verkligen okej? Vi kanske kan skriva den bättre? Först och främst bryter den mot den grundläggande principen för programmering - Single Responsibility, så visst kan vi skriva den bättre. Men hur då? Det är här den redan nämnda PORO kommer till hjälp. Det räcker med att separera en klass RegistrationService, som bara ansvarar för en sak: att meddela alla relaterade tjänster. Med tjänster menar vi de enskilda åtgärder som vi redan har pekat ut ovan. I samma controller är allt du behöver göra att skapa ett objekt RegistrationService och anropa metoden "fire!". Koden har blivit mycket tydligare, vår controller tar mindre plats och var och en av de nyskapade klasserna ansvarar nu bara för en åtgärd, så vi kan enkelt byta ut dem om det skulle behövas.
klass Registreringsservice
def fire!(params)
user = Användare.new(params)
if user.valid? && ip_validator.valid?(registrering_ip)
användare.spara!
efter_registrerade_händelser(användare)
slut
användare
slut
privat
def efter_registrerade_händelser(användare)
BonusesCreator.new.fire!(användare)
AccountsSynchronizator.fire!(användare)
EmailSender.fire!(användare)
slut
def ip_validator
@ip_validator ||= IpValidator.new
end
slut
klass RegistreringsController < ApplikationsController
def skapa
user = RegistrationService.new.fire!(registrering_params)
slut
slut
Men Plain Old Ruby Object kan visa sig vara användbart inte bara för styrenheter. Föreställ dig att applikationen du skapar använder ett månatligt faktureringssystem. Den exakta dagen för att skapa en sådan fakturering är inte viktig för oss, vi behöver bara veta att det gäller en viss månad och ett visst år. Naturligtvis kan du ställa in dagen för den första dagen i varje månad och lagra denna information i objektet i klassen "Date", men det är inte en riktig information och du behöver inte heller den i din applikation. Genom att använda PORO kan du skapa en klass "MonthOfYear", vars objekt kommer att lagra exakt den information du behöver. Dessutom, när du använder modulen "Comparable" i den, kommer det att vara möjligt att iterera och jämföra dess objekt, precis som när du använder Date-klassen.
klass MånadÅr
include Jämförbar
attr_reader :år, :månad
def initialize(månad, år)
raise ArgumentError om inte månad.mellan?(1, 12)
@year, @month = år, månad
slut
def (andra)
[år, månad] [annan.år, annan.månad]
end
slut
Presentera mig för Rails.
I Rails-världen är vi vana vid att varje klass är en modell, en vy eller en kontroller. De har också sin exakta plats i katalogstrukturen, så var kan du placera vår lilla PORO-armé? Överväg några alternativ. Den första tanken som dyker upp är: om de skapade klasserna varken är modeller, vyer eller styrenheter, bör vi lägga dem alla i katalogen "/lib". Teoretiskt sett är det en bra idé, men om alla dina PORO-filer kommer att landa i en katalog och applikationen kommer att vara stor, kommer denna katalog snabbt att bli en mörk plats som du är rädd för att öppna. Därför är det utan tvekan inte en bra idé.
AwesomeProject
App
│ ├─kontroller
│ ├─modeller
│ └─vyer
│
└lib
└─tjänster
1TP61Stor poro här
Du kan också namnge några av dina klasser som inte är ActiveRecord-modeller och lägga dem i katalogen "app/models", och namnge de som är ansvariga för att hantera andra klasser som tjänster och lägga dem i katalogen "app/services". Det här är en ganska bra lösning, men den har en nackdel: när du skapar en ny PORO måste du varje gång bestämma om det är mer av en modell eller en tjänst. På det här sättet kan du nå en situation där du har två mörka platser i din applikation, bara mindre. Det finns ännu ett tredje tillvägagångssätt, nämligen att använda namespaced classes och moduler. Allt du behöver göra är att skapa en katalog som har samma namn som en controller eller en modell, och lägga alla PORO-filer som används av den givna controllern eller modellen inuti.
AwesomeProject
App
│ ├─kontroller
│ │ ├─registrerings_controller
│ │ │ └─registration_service.rb
│ │ └─registration_controller.rb
│ ├─modeller
│ │ ├─settlement
│ │ │ └─månad_av_år.rb
│ │ └─settlement.rb
│ └─vyer
│
└lib
Tack vare detta arrangemang behöver du inte föregå namnet på en klass med en namnrymd när du använder den. Du har fått kortare kod och en mer logiskt organiserad katalogstruktur.
Kolla in mig!
Det är en trevlig överraskning att enhetstesterna för din applikation blir snabbare och enklare att skriva när du använder PORO och att det senare är mer sannolikt att de förstås av andra. Eftersom varje klass nu bara ansvarar för en enda sak kan du känna igen gränsvillkoren tidigare och enkelt lägga till lämpliga testscenarier för dem.
describe Månadsår do
subject { MånadÅr.ny(11, 2015) }
it { bör vara_kind_of Jämförbar }
describe "skapar ny instans" do
it "initialiseras med korrekt år och månad" do
expect { beskriven_klass.ny(10, 2015) }.to_not raise_error
end
it "ger fel när angiven månad är felaktig" do
expect { described_class.new(0, 2015) }.to raise_error(ArgumentError)
expect { described_class.new(13, 2015) }.to raise_error(ArgumentError)
slut
slut
slut
Jag hoppas att vi träffas igen!
De exempel vi presenterat visar tydligt att användningen av PORO förbättrar läsbarheten i applikationer och gör dem mer modulära, och därmed lättare att hantera och utöka. Principen om "Single Responsibility" gör det möjligt att vid behov byta ut vissa klasser utan att störa andra element. Det gör också testningen av dem till en enklare och snabbare procedur. Dessutom är det mycket lättare att hålla Rails-modeller och -kontrollanter korta på det här sättet, och vi vet alla att de tenderar att bli onödigt stora under utvecklingsprocessen.