TITLE=RoR stílusútmutató ROUTE=/development/ror-styleguide LOCALE=hu MODIFIED=1792409344 URL=https://wiki.teletypegames.org/hu/development/ror-styleguide L=1 #68 L=2 #22 L=3 #70 q│ Ezek azok a szabályok, amelyeket a Rails-kódunk követ. Két helyről jönnek: a q│ iGrowing Rails Applications in Practiceq (makandra) könyvből, és abból a két q│ alkalmazásból, amelyet már rájuk építve üzemeltetünk — a 1nyilvános oldalból q│ a 2WarpEngine katalógusmotorral, és a 3Teletype Orbitból. Semmi itt nem q│ stíluspreferencia. Ha itt Railst írsz, ezeket követed. Egy mondat, amit érdemes megjegyezni: ba nagy alkalmazások nagyok0. Ezt nem tudjuk eltüntetni. Amit tehetünk: úgy rendezzük a kódot, hogy a kétszer annyi modell ne jelentsen kétszer annyi problémát. 11. Hova kerül a kód 1═══════════════════ Minden kódrésznek egy otthona van. Ha nem tudod megnevezni az otthonát, akkor az osztály, amire szükséged van, még nem létezik — hozd létre. • bKontroller0 (capp/controllers/0) — csak HTTP: paramétereket olvas, form objektumot vagy szolgáltatást hív, rendel vagy átirányít. Üzleti logika nincs benne. • bForm objektum0 (capp/forms/0) — minden olyan űrlap, amely egynél több modellbe ír, vagy amely mögött egyáltalán nincs modell (belépés, pénztár, feltöltés). Sima cActiveModel::Model0 validációkkal. • bSzolgáltatás0 (capp/services/0) — egy használati eset, egy publikus metódus beszédes névvel (cplace0, cissue0, crevoke0, cpublish0). A tranzakciók, döntések és mellékhatások itt élnek. A metódust soha ne nevezd ccall0-nak. • bQuery objektum0 (capp/queries/0) — bármi, ami bonyolultabb egy scope-nál: keresés, szűrők, diagramok, riportok. Relációt vagy sorok tömbjét adja vissza. • bPresenter0 (capp/presenters/0) — formázás a nézetnek, hogy a sablonokban ne legyen logika. • bDTO0 (capp/dtos/0) — az az eredmény, amit egy szolgáltatás visszaad. cStruct0-szerű érték, validációk nélkül. • bSzerializáló0 (capp/serializers/0) — Blueprinter, kizárólag a JSON API-hoz. A HTML-oldalak nézeteket használnak. • bModell0 (capp/models/0) — asszociációk, validációk, scope-ok. • bJob0 (capp/jobs/0) — aszinkron mellékhatások: levél, aggregálás, webhook-feldolgozás. Két szabály a rétegek között: • Egy szolgáltatás hívhat másik szolgáltatást. bEgy modell soha nem hív bszolgáltatást. • Ha egy modell-callback levelet küldene vagy jogosultságot adna, akkor rossz helyen van. Tedd át egy szolgáltatásba. 12. Kontrollerek 1═══════════════ • bCsak a hét szabványos akció létezik0: cindex0, cshow0, cnew0, ccreate0, cedit0, cupdate0, cdestroy0. Nincs capprove0, cpublish0, cadd_to_cart0, credeem0. • Egy új interakció búj kontrollert jelent, amelynek az erőforrása maga az binterakció0: t┌───────────────────────────────┬─────────────────────────────────────────┐ t│h Ehelyett t│h Ezt írd t│ t├───────────────────────────────┼─────────────────────────────────────────┤ t│0 cCartsController#add_item0 t│0 cStore::CartItemsController#create0 t│ t│0 cSubmissionsController#approve0 t│0 cModeration::ApprovalsController#create0 t│ t│0 cProductsController#publish0 t│0 cStudio::PublicationsController#create0 t│ t│0 cReviewsController#upvote0 t│0 cCommunity::ReviewVotesController#create0 t│ t│0 cSessionsController#logout0 t│0 cAccounts::SessionsController#destroy0 t│ t└───────────────────────────────┴─────────────────────────────────────────┘ • Egy akció három-hat sor. Nulla üzleti logika, és bnulla cifb egy modell bállapotára0. • bMinden bemenetet engedélyezőlistázz.0 A modellattribútumok cpermit0-en mennek át. Azok a szolgáltatásbemenetek, amelyek nem modellattribútumok, form objektumba valók — a „csak két mező" mondattal kezdődik a következő pokoli kontroller. • bSoha ne hívj cconstantizeb-t paraméterre.0 Előbb ellenőrizd egy engedélyezőlista-konstanshoz (már megvan a cSupport::Flag::FLAGGABLES0 és a cCommunity::Follow::FOLLOWABLES0). • bEgyetlen privát metódus az egyetlen ajtó a modellhez.0 Ahol egy erőforrás felhasználóhoz, kiadóhoz vagy tokenhez van szűkítve, azon a szűkítésen keresztül töltsd be, ne előbb keresd meg és utána ellenőrizd. • bAz ismétlődő cbefore_actionb törzsek az alapkontrollerbe valók0, lehetőleg kis osztálymakróként. Ugyanannak a „keresd meg a kiadót és engedélyezz" párnak öt másolata öt esély az elcsúszásra. • A szolgáltatások saját hibaosztályokat dobnak (cStore::CheckoutService::Error0, cLibrary::DownloadService::Denied0). A kontroller bazokat0 kapja el, soha nem a cStandardError0-t. • A közös válaszformálás — 404, 403, a JSON hibatörzs — egyszer él, az alapkontroller crescue_from0 blokkjában. 13. Modellek és ActiveRecord 1═══════════════════════════ • bEgy invariánst validációban és callbackben mondj ki, ne segédmetódusban.0 Egy cinvite.accept!0 metódust tucatnyi módon meg lehet kerülni (cupdate!0, ccreate!0, attribútum-beállító). Csak a validációkról és a callbackekről garantált, hogy lefutnak. • A kényelmi metódusok jöhetnek. Csak soha ne támaszkodj arra, hogy más kód meghívja őket. • bEgy callback konzisztensen tartja az adatot. Soha nem csinál üzletet.0 Slug vagy token generálása, státusz tükrözése, csak olvasható sor őrzése: igen. Levélküldés, jogosultság adása, webhook hívása: nem. • bAz cupdate_columnb, az cupdate_columnsb és az cupdate_allb megkerüli a saját bszerződésedet.0 Csak házimunkára használd őket — egy clast_used_at0 érintés, egy számláló, egy kötegelt söprés. Bármi, amit egy ember ténynek nevezne (kié ez, listázva van-e, melyik a fő kép), csave0/cupdate!0 hívásokon megy át. • bSoha ne nyúlj a külvilághoz, mielőtt a sor commitolódott.0 Egy cbefore_save0-ben írt fájl vagy egy cbefore_validation0-ben mentett másik rekord árvákat hagy maga után, ha a mentés elbukik. Használj cafter_commit0-et, vagy csináld egy szolgáltatásban, tranzakción belül. • bEgy értékkészlet a modellen konstansként él, mellette cinclusion bvalidációval.0 A validáció nélküli konstans javaslat, nem szabály — és soha ne másold be az értékeket egy ActiveAdmin-szűrőbe, hivatkozz a konstansra. • bAz interakcióspecifikus kód nem a mag-modellbe való.0 Egy validáció, amire csak egy űrlapnak van szüksége, egy virtuális attribútum egyetlen képernyőhöz, egy callback egyetlen használati esethez: az form objektum vagy szolgáltatás. • bA modulok és concernek nem karcsúsítanak egy modellt.0 Ezek fájlszervezés; a callbackek és a metódusok továbbra is betöltődnek az osztályba. • A cdefault_scope0 a soft delete szűrőjére való (cwhere(deleted_at: nil)0), és semmi másra. bSoha ne tegyél corderb-t cdefault_scopeb-ba0 — átszivárog minden asszociációba és aggregátumba, és a végén cunscope(:order)0 hívásokat fogsz írni, hogy kikerülj belőle. • Minden modell, amelyhez adminoldal tartozik, explicit cransackable_attributes és cransackable_associations0 listát deklarál. 14. Nevek és névterek 1════════════════════ • bMinden domain egy Ruby-modul, és nincsenek legfelső szintű modellek. cAccounts0, cStore0, cLibrary0, cStudio0, cCommunity0, cSupport0, cFinance0, cOps0. • A modul viszi a tábla-előtagot, így a leképezés automatikus: p # app/models/store.rb p module Store p def self.table_name_prefix = "store_" p end p # Store::Product -> store_products Ez nem dekoráció: a WarpEngine saját táblái (csoftwares0, creleases0, cimages0) előtag nélkül ülnek bugyanabban az adatbázisban0, tehát az előtag az, ami szerkezetileg megakadályozza az ütközést. • bA motor szavai foglaltak.0 A cSoftware0, cRelease0, cReleaseAsset0, cDownload0, cSoftwareImage0, cExternalLink0, cPlatformLink0, cApplicationToken0, cPipeline0 a kódunkban soha nem jelent mást. Ahol a bolt fogalma eltér, használj másik szót: az eladható dolog cStore::Product0, egy fórumszál cCommunity::Topic0 (a cThread0 Ruby-osztály), egy tartalombejelentés cSupport::Flag0 (a „report" az értékesítési riport). • Kerülendő továbbá: cProcess0, cSet0, cData0, cMethod0, cFile0, cDir0, cRange0, cSignal0, cRandom0, cComparable0. • bNévterezz agresszíven, és a szolgáltatásokat fogalom szerint névterezd0, ne utótag szerint: cSoftware::Publish0, cRss::BlogFeed0, cStore::CheckoutService0 — nem egy lapos mappa tizenhét c_service.rb0 végű fájllal. • bHasználj mindenhol ugyanazt a szerkezetet0: a modellek, kontrollerek, nézetek, helperek és specek tükrözik egymást. Egy request spec a cspec/requests0 alatt él, egy model spec a cspec/models0 alatt — még akkor is, ha a fájl máshonnan is futna. • A névterezés kétszer térül meg: olvashatóvá teszi a mappát, és nyilvánvaló helyet ad az új osztályoknak — pontosan ez akadályozza meg, hogy a modellek elhízzanak. 15. A WarpEngine határa 1══════════════════════ • bMinden hoszt-specifikus rész egy adapter alapértékkel, és az alapérték a mai bviselkedés.0 Így marad a motor önmagában is használható, miközben a bolt beteszi a saját szabályait: tárolás, hozzáférési szabályzat, képek, CI, tokenforrás, alanyfeloldás. • bBolt-specifikus kód soha nem kerül a motorba.0 A motor közöl és konfigurál; a hoszt dönt. • bA motor kifelé cActiveSupport::Notificationsb eseményekkel beszél0, nem a hosztba visszahívó callbackekkel. A hoszt feliratkozik (cwarp_engine.publish0, cwarp_engine.download0). • bEgy motormodellt a load hookján keresztül bővíts (cActiveSupport.run_load_hooks(:warp_engine_software, self)0), soha ne az osztály újranyitásával. • bA motor URL-jei foglaltak0: c/api/software*0, c/api/builds0, c/api/download0, c/api/service0, c/api/auth/*0, c/api/ci/*0, c/build/*0, c/file/*0. A saját JSON API-nk verziózott (c/api/v10), tehát nem ütközhet. A cmount WarpEngine::Engine0 bmindig ba croutes.rbb utolsó sora0. • Ha szándékosan árnyékolsz egy motorútvonalat, az útvonalhoz komment kell arról, hogy miért, bés0 annak a regressziós specnek a neve, amely védi. • bEgy motormodellbe úgy írj, ahogy a motor várja0: cupdate!0 hívással, hogy a validációi és callbackjei lefussanak. A motor verziózott függőség — egy oszlopírás, ami ma működik, némán kihagyja azt a callbacket, amit a következő kiadásban kap. • bAmikor egy seam leváltja a régi kódot, töröld a régi kódot.0 Miután a tárolóadapter beérkezett, még négy másik hely számolta a fájlok alapútvonalát; kettő közülük már halott volt. 16. Konfiguráció 1═══════════════ • bMinden cENVb-olvasás egy helyen történik0, a cconfig/application.rb0 fájlban, a cconfig.x0 alá: p config.x.downloads.grant_ttl = 15.minutes p config.x.images.container_path = ENV.fetch("IMAGE_CONTAINER_PATH", "/images"» p ) • A kód a cRails.configuration.x0 értékeit olvassa, soha nem közvetlenül az cENV0-et. Egyetlen hely mutatja meg, mit vár az alkalmazás a környezetétől, és egy teszt felül tudja írni. • bEgy dolognak egy neve.0 Két környezeti változó ugyanarra a wikire, amelyek közül az egyiket a compose-fájl soha nem állítja be, olyan hiba, amely a péntekre vár. • A motorbeállítások a motor saját konfigurációs objektumába kerülnek alapértékekkel, a hoszt pedig egyetlen initializerben állítja be őket. • bSemmi monkey patch initializerből.0 Az cApplicationController0 újranyitása egy cto_prepare0 blokkban elrejti a viselkedést az osztályt deklaráló fájl elől — tedd concernbe, és include-old. 17. Nézetek és stílusok 1══════════════════════ • Tailwind, és belnevezett osztály mindenre, ami kétszernél többször előfordul0. Ha a ctext-2xl font-semibold0 81-szer szerepel, akkor az egy címsorosztály, amely meg akar születni. • bSzemantikus színtokenek, nem nyers palettanevek.0 csurface0, cline0, cmuted0, caccent0 — így egy témaváltás egy fájl, nem 134 nézet. • bEgy téma tokencsere.0 Más blokkok c!important0-tal való felülírásától válik karbantarthatatlanná egy stíluslap; egy blokk a saját módosítóosztályán keresztül dönti el, hogyan néz ki egy témában. • bEgy blokk soha nem stílusoz egy másikat.0 A c.sidebar .article {}0 kizárva; vagy az elem a blokkhoz tartozik (c.sidebar__article0), vagy a blokk kap egy módosítót (c.article.is_summary0). • Megengedett pragmatikus kivételek: összetett HTML gyerekei (ctable0, cdl0, cul0), c:before0/c:after0, pszeudoszelektorok, médiaquery-k, markdown- vagy WYSIWYG-tartalom egyetlen konténeren belül (c.wiki-content0), és külső könyvtár által generált markup. • bNézetben nincs lekérdezés0, és cincludes0 sem — az adatot a kontroller vagy a szolgáltatás készíti elő. Egy lekérdezés egy partial ciklusában N+1 probléma, extra lépésekkel. • bNincs soron belüli cstyle:b attribútum ActiveAdmin-oldalakon0, ha a projektnek már van adminstíluslapja. Az ismétlődő markup osztályt kap; egy hosszú ikon-ccase0 presentert. • Az a nézet, amelynek hat példányváltozó kell öt szolgáltatásból, nem cshow0 — az egy munkalap, és presentert vagy query objektumot kér. 18. JSON API 1═══════════ • Blueprinter-szerializálók, bcamelCase kulcsok0, és szerializálók kizárólag az API-hoz — soha nem HTML-oldalakhoz. • bA nyilvános végpontokat apipie-jal annotáld0 (capi0, cparam0, creturns0, cerror0), felsorolva a válasz tulajdonságait. Az c/api/docs0 és a swagger-oldal ebből épül. • bÍrd le, miért nyilvános egy végpont.0 A „egy kliensnek nincs kinek belépnie" a végpont leírásába való, nem egy commitüzenetbe. • A saját API-nk verziózott. Az a válasz, amelyre egy kliens támaszkodik, verziófejlécet visz, egy szolgáltatásleíró végpont pedig még bármilyen hitelesítő adat előtt megmondja a kliensnek, mit kínál ez a telepítés. • Ha a projektben vannak szerializálók, egy kontrollerben kézzel összerakott hash az az út, amelynek a végén ugyanannak a mezőnek két neve lesz egy válaszban. 19. Tesztek 1══════════ • bAz egységtesztek és a teljes vertikumú tesztek térülnek meg.0 Ne adj hozzá kontroller- és nézetspecet ahhoz, amit ez a kettő már lefed. • A sötét ház szabálya: az alkalmazás egy sötét ház, az integrációs tesztek a háttérvilágítás, az egységtesztek a reflektorok. Nem kell mindenhova sebészi fény — arra van szükség, hogy biztos lehess benne: egy sötét sarokban sem nő semmi. • cspec/modelsb minden invariánsra0: az állapotgép, az utolsó tulajdonos szabálya, a csak olvasható sor, az egyediség, ami számít. Ezek a legolcsóbb és leghosszabb életű tesztek, amiket írni fogsz. • bA cspec/servicesb a súlypont0 — minden ág, minden hibaút. • cspec/requestsb a jogosultságokra, a státuszkódokra és az átirányításokra.0 A letöltési kapunak kötelező regressziós spece van. • bMinden képernyőt megérint legalább egy teljes vertikumú teszt.0 Egy-két út rajta keresztül, nem minden peremeset. Hotwire mellett ez nem opcionális. • bEgy parancs futtat mindent0, külön beállítás nélkül, a tesztadatot pedig maguk a tesztek hozzák létre. • bSemmi nem kerül commitba bukó vagy függőben lévő teszttel.0 Amint a tesztkészlet nem zöld, nem tudja többé megmondani, hogy elrontott-e valamit a változtatásod, a léc pedig egyre lejjebb csúszik. • A factory-k minimálisak maradnak, traitekkel. Nincs istenfactory, ami az adatbázis felét behúzza. • Tartsd unalmasan a teszteket: az okosság a tesztelt kódba való. 110. Gemek és frissítések 1════════════════════════ • bRögzíts minden gemet0, és írd oda az indoklást minden kockázatos választás mellé. A Gemfile jó hely egy mondatnak, ami megspórol egy délutánt. • bNincs git-ágas függőség.0 Egy cbranch: master0 gem azt jelenti, hogy két cbundle cupdate0 között bármi megérkezhet, a lockfile pedig revíziót nevez meg, nem verziót. Ha még nincs kiadás, rögzíts egy cref:0 értéket, és szándékosan emeld. • bNincs c:latestb konténerimage a CI-ban.0 Egy build eszközlánca nem változhat commit nélkül. • Egy gem hozzáadása azt jelenti, hogy ba tiéd lesz0: a viselkedése terhelés alatt, a biztonsági frissítései, a frissítése, amikor a Rails továbblép, és a függőségei. Válassz inkább alacsony szintű könyvtárat, mint mini keretrendszert, amely mélyen beleakaszkodik a Rails belsőségeibe. • bNe élj a vérző élen.0 Várd meg, amíg egy főverzió elér néhány javítószintet. • bHasználd ki a meglévő eszközeidet.0 Egy újabb tábla a Redis előtt, cLIKE0 a keresőszerver előtt, egy cron a sorkezelő előtt — és rejtsd a döntést egy kis API mögé (cArticle::Search.find0), hogy a későbbi csere egyetlen osztályt érintsen. • bA monkey patchekért frissítéskor fizetsz.0 Forkolj, javítsd teszttel, és nyiss pull requestet. 111. Hogyan dolgozunk 1════════════════════ • *iA terv a repó gyökerében lévő `NOTES_0.md` fájlban él** (gitignore-olva), nem commitüzenetek között szétszórva: döntések, alternatívák és a miértek. A kód szakaszra hivatkozva utalhat rá. • bRepónkénti kommentkonvenció.0 A cteletype-orbit0 repóban a miértet a nem nyilvánvaló kód mellé írjuk; a cteletypegames0 monorepóban nem használunk magyarázó kommentet, a miért a commitba megy. Mindkettő szándékos — kövesd azt a repót, amelyikben vagy. • bA kiadói műveleteket táblába auditáld0, ne naplófájlba: ki, mit, mikor, melyik rekorddal. • bA seedek két szinten jönnek0: alapadatok, amelyek élesben is lefutnak, és példaadatok fejlesztéshez, amelyek a bvalódi0 úton mennek végig (studio, beküldés, jóváhagyás, kosár, fizetési webhook), így a seed a domaint gyakorolja, nem sorokat tol be oldalról. • bAz új mintákat megítéljük, nem átvesszük.0 Írj át egy kis részt az új stílusban, hasonlítsd össze a kettőt, és mondd ki hangosan, mi a kompromisszum. A következetesség többet ér egy félúton átvett, marginálisan jobb technikánál.