Ezek azok a szabályok, amelyeket a Rails-kódunk követ. Két helyről jönnek: a Growing Rails Applications in Practice (makandra) könyvből, és abból a két alkalmazásból, amelyet már rájuk építve üzemeltetünk — a nyilvános oldalból a WarpEngine katalógusmotorral, és a Teletype Orbitból. Semmi itt nem stíluspreferencia. Ha itt Railst írsz, ezeket követed.
Egy mondat, amit érdemes megjegyezni: a nagy alkalmazások nagyok. 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.
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.
app/controllers/) — csak HTTP: paramétereket olvas, form objektumot vagy szolgáltatást hív, rendel vagy átirányít. Üzleti logika nincs benne.app/forms/) — 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 ActiveModel::Model validációkkal.app/services/) — egy használati eset, egy publikus metódus beszédes névvel (place, issue, revoke, publish). A tranzakciók, döntések és mellékhatások itt élnek. A metódust soha ne nevezd call-nak.app/queries/) — 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.app/presenters/) — formázás a nézetnek, hogy a sablonokban ne legyen logika.app/dtos/) — az az eredmény, amit egy szolgáltatás visszaad. Struct-szerű érték, validációk nélkül.app/serializers/) — Blueprinter, kizárólag a JSON API-hoz. A HTML-oldalak nézeteket használnak.app/models/) — asszociációk, validációk, scope-ok.app/jobs/) — aszinkron mellékhatások: levél, aggregálás, webhook-feldolgozás.Két szabály a rétegek között:
index, show, new, create, edit, update, destroy. Nincs approve, publish, add_to_cart, redeem.| Ehelyett | Ezt írd |
|---|---|
CartsController#add_item |
Store::CartItemsController#create |
SubmissionsController#approve |
Moderation::ApprovalsController#create |
ProductsController#publish |
Studio::PublicationsController#create |
ReviewsController#upvote |
Community::ReviewVotesController#create |
SessionsController#logout |
Accounts::SessionsController#destroy |
if egy modell állapotára.permit-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.constantize-t paraméterre. Előbb ellenőrizd egy engedélyezőlista-konstanshoz (már megvan a Support::Flag::FLAGGABLES és a Community::Follow::FOLLOWABLES).before_action törzsek az alapkontrollerbe valók, 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.Store::CheckoutService::Error, Library::DownloadService::Denied). A kontroller azokat kapja el, soha nem a StandardError-t.rescue_from blokkjában.invite.accept! metódust tucatnyi módon meg lehet kerülni (update!, create!, attribútum-beállító). Csak a validációkról és a callbackekről garantált, hogy lefutnak.update_column, az update_columns és az update_all megkerüli a saját szerződésedet. Csak házimunkára használd őket — egy last_used_at é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), save/update! hívásokon megy át.before_save-ben írt fájl vagy egy before_validation-ben mentett másik rekord árvákat hagy maga után, ha a mentés elbukik. Használj after_commit-et, vagy csináld egy szolgáltatásban, tranzakción belül.inclusion validációval. 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.default_scope a soft delete szűrőjére való (where(deleted_at: nil)), és semmi másra. Soha ne tegyél order-t default_scope-ba — átszivárog minden asszociációba és aggregátumba, és a végén unscope(:order) hívásokat fogsz írni, hogy kikerülj belőle.ransackable_attributes és ransackable_associations listát deklarál.Accounts, Store, Library, Studio, Community, Support, Finance, Ops.# app/models/store.rb
module Store
def self.table_name_prefix = "store_"
end
# Store::Product -> store_products
Ez nem dekoráció: a WarpEngine saját táblái (softwares, releases, images) előtag nélkül ülnek ugyanabban az adatbázisban, tehát az előtag az, ami szerkezetileg megakadályozza az ütközést.
Software, Release, ReleaseAsset, Download, SoftwareImage, ExternalLink, PlatformLink, ApplicationToken, Pipeline a kódunkban soha nem jelent mást. Ahol a bolt fogalma eltér, használj másik szót: az eladható dolog Store::Product, egy fórumszál Community::Topic (a Thread Ruby-osztály), egy tartalombejelentés Support::Flag (a „report" az értékesítési riport).Process, Set, Data, Method, File, Dir, Range, Signal, Random, Comparable.Software::Publish, Rss::BlogFeed, Store::CheckoutService — nem egy lapos mappa tizenhét _service.rb végű fájllal.spec/requests alatt él, egy model spec a spec/models alatt — még akkor is, ha a fájl máshonnan is futna.ActiveSupport::Notifications eseményekkel beszél, nem a hosztba visszahívó callbackekkel. A hoszt feliratkozik (warp_engine.publish, warp_engine.download).ActiveSupport.run_load_hooks(:warp_engine_software, self)), soha ne az osztály újranyitásával./api/software*, /api/builds, /api/download, /api/service, /api/auth/*, /api/ci/*, /build/*, /file/*. A saját JSON API-nk verziózott (/api/v1), tehát nem ütközhet. A mount WarpEngine::Engine mindig a routes.rb utolsó sora.update! 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.ENV-olvasás egy helyen történik, a config/application.rb fájlban, a config.x alá:config.x.downloads.grant_ttl = 15.minutes
config.x.images.container_path = ENV.fetch("IMAGE_CONTAINER_PATH", "/images")
Rails.configuration.x értékeit olvassa, soha nem közvetlenül az ENV-et. Egyetlen hely mutatja meg, mit vár az alkalmazás a környezetétől, és egy teszt felül tudja írni.ApplicationController újranyitása egy to_prepare blokkban elrejti a viselkedést az osztályt deklaráló fájl elől — tedd concernbe, és include-old.text-2xl font-semibold 81-szer szerepel, akkor az egy címsorosztály, amely meg akar születni.surface, line, muted, accent — így egy témaváltás egy fájl, nem 134 nézet.!important-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..sidebar .article {} kizárva; vagy az elem a blokkhoz tartozik (.sidebar__article), vagy a blokk kap egy módosítót (.article.is_summary).table, dl, ul), :before/:after, pszeudoszelektorok, médiaquery-k, markdown- vagy WYSIWYG-tartalom egyetlen konténeren belül (.wiki-content), és külső könyvtár által generált markup.includes 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.style: attribútum ActiveAdmin-oldalakon, ha a projektnek már van adminstíluslapja. Az ismétlődő markup osztályt kap; egy hosszú ikon-case presentert.show — az egy munkalap, és presentert vagy query objektumot kér.api, param, returns, error), felsorolva a válasz tulajdonságait. Az /api/docs és a swagger-oldal ebből épül.spec/models minden invariánsra: 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.spec/services a súlypont — minden ág, minden hibaút.spec/requests a jogosultságokra, a státuszkódokra és az átirányításokra. A letöltési kapunak kötelező regressziós spece van.branch: master gem azt jelenti, hogy két bundle update 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 ref: értéket, és szándékosan emeld.:latest konténerimage a CI-ban. Egy build eszközlánca nem változhat commit nélkül.LIKE a keresőszerver előtt, egy cron a sorkezelő előtt — és rejtsd a döntést egy kis API mögé (Article::Search.find), hogy a későbbi csere egyetlen osztályt érintsen.teletype-orbit repóban a miértet a nem nyilvánvaló kód mellé írjuk; a teletypegames 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.