def spectre_member_name

Anti- wat? Het klinkt waarschijnlijk een stuk ingewikkelder dan het is. In de afgelopen decennia konden programmeurs een nuttige selectie van "ontwerp" -patronen identificeren die vaak in hun code-oplossingen voorkwamen. Bij het oplossen van vergelijkbare problemen waren ze in staat oplossingen te "classificeren" waardoor ze niet steeds opnieuw het wiel uitvonden. Het is belangrijk op te merken dat deze patronen meer als ontdekkingen moeten worden gezien dan de uitvindingen van een groep geavanceerde ontwikkelaars.

Als dit nogal nieuw voor je is en je jezelf ziet als meer aan de beginner kant van alle dingen Ruby / Rails, dan is deze precies voor jou geschreven. Ik denk dat het het beste is als je het ziet als een snelle skinny-dip naar een veel dieper onderwerp waarvan de beheersing niet van de ene op de andere dag zal gebeuren. Desalniettemin ben ik ervan overtuigd dat beginnende beginners al vroeg hun voordeel zullen doen bij beginners en hun mentoren.

AntiPatterns - zoals de naam al aangeeft - vertegenwoordigen vrijwel het tegenovergestelde van patronen. Het zijn ontdekkingen van oplossingen voor problemen die je absoluut moet vermijden. Ze vertegenwoordigen vaak het werk van onervaren programmeurs die niet weten wat ze nog niet weten. Erger nog, ze kunnen de uitvoer zijn van een lui persoon die best practices en gereedschapskaders negeert zonder goede reden - of ze denken dat ze ze niet nodig hebben. Wat ze in het begin hoopten te winnen in tijdswinst door snelle, luie of smerige oplossingen te hameren, zal hen achtervolgen of een spijtige opvolger later in de levenscyclus van het project.

Onderschat de implicaties van deze slechte beslissingen niet, ze zullen je pesten, wat er ook gebeurt.

Onderwerpen

  • Vetmodellen
  • Ontbrekende testsuite
  • Voyeuristische modellen
  • Wet van Demeter
  • Spaghetti SQL

Vetmodellen

Ik weet zeker dat je de "Fat-modellen, magere controllers" talloze keren hebt horen zingen toen je voor het eerst met Rails begon. OK, vergeet dat nu! Natuurlijk moet de bedrijfslogica in de modellaag worden opgelost, maar u moet niet geneigd zijn om alles er ongemerkt in te stoppen, alleen maar om te voorkomen dat u de grenzen overschrijdt naar het controlegebied.

Hier is een nieuw doel waarop u moet streven: "Magere modellen, magere controllers". Je zou je kunnen afvragen: "Wel, hoe moeten we de code regelen om dat te bereiken - het is tenslotte een zero-sum-spel?" Goed punt! De naam van het spel is compositie en Ruby is goed uitgerust om je veel opties te geven om modelobesitas te voorkomen.

In de meeste (Rails) webtoepassingen die door een database worden ondersteund, zal het grootste deel van uw aandacht en werk worden geconcentreerd rond de modellaag, gegeven het feit dat u werkt met competente ontwerpers die in staat zijn hun eigen spullen in de weergave te implementeren, bedoel ik. Uw modellen hebben inherent meer 'zwaartekracht' en trekken meer complexiteit aan.

De vraag is alleen hoe u die complexiteit wilt beheren. Active Record geeft je zeker veel touw om jezelf mee op te hangen terwijl je je leven ongelooflijk gemakkelijk maakt. Het is een verleidelijke benadering om uw modellaag te ontwerpen door gewoon het pad van het hoogste onmiddellijke gemak te volgen. Niettemin heeft een toekomstbestendige architectuur veel meer aandacht nodig dan het cultiveren van enorme klassen en alles in te vullen in Active Record-objecten.

Het echte probleem waarmee je hier te maken hebt, is complexiteit - onnodig dus, zou ik zeggen. Klassen die tonnen code verzamelen, worden alleen al door hun grootte complex. Ze zijn moeilijker te onderhouden, moeilijk te ontleden en te begrijpen, en worden steeds moeilijker te veranderen omdat hun samenstelling waarschijnlijk geen ontkoppeling vertoont. Deze modellen overschrijden dikwijls hun aanbevolen capaciteit om een ​​enkele verantwoordelijkheid te dragen en zijn nogal over de plaats verspreid. In het ergste geval worden ze als vuilniswagens, die omgaan met al het afval dat hen lui wordt toegeworpen.

We kunnen het beter doen! Als je denkt dat complexiteit geen groot probleem is, dan ben je toch weer speciaal, slim en denk opnieuw! Complexiteit is de meest beruchte seriemoordenaar die er is - niet je vriendelijke wijk "Dark Defender".

"Skinnier-modellen" bereiken één ding, geavanceerde mensen in de codeersector (waarschijnlijk veel meer beroepen dan code en ontwerp) waarderen en waar we allemaal absoluut naar moeten streven - eenvoud! Of op zijn minst meer, een redelijk compromis als complexiteit moeilijk uit te roeien is.

Welke hulpmiddelen biedt Ruby om ons leven wat dat betreft gemakkelijker te maken en om het vet uit onze modellen te verwijderen? Eenvoudig, andere klassen en modules. U identificeert coherente code die u in een ander object kunt extraheren en bouwt daarmee een modellaag op die bestaat uit redelijk grote agents met hun eigen unieke, onderscheidende verantwoordelijkheden.

Denk er eens over in termen van een getalenteerde artiest. In het echte leven kan zo'n persoon in staat zijn om te rappen, te breken, teksten te schrijven en haar eigen deuntjes te produceren. Tijdens het programmeren geef je de voorkeur aan de dynamiek van een band - hier met ten minste vier onderscheidende leden - waarbij elke persoon de leiding heeft over zo min mogelijk dingen. Je wilt een orkest bouwen van klassen die de complexiteit van de componist aankunnen - niet een geniale maestro-klasse voor micromanaging allerhande.

Laten we eens kijken naar een voorbeeld van een vet model en spelen met een paar opties om zijn zwaarlijvigheid aan te pakken. Het voorbeeld is natuurlijk een dummy en door dit gemene verhaaltje te vertellen hoop ik dat het makkelijker te verteren en te volgen is voor nieuwkomers..

We hebben een Spectre-klas die te veel verantwoordelijkheden heeft en daarom onnodig is gegroeid. Naast deze methoden denk ik dat het gemakkelijk is om je voor te stellen dat een dergelijk exemplaar al veel andere dingen heeft verzameld, goed vertegenwoordigd door de drie kleine puntjes. Spectre is goed op weg om een ​​godklasse te worden. (De kans is redelijk laag om een ​​dergelijke zin binnenkort opnieuw zinvol te formuleren!)

"ruby class Spectre < ActiveRecord::Base has_many :spectre_members has_many :spectre_agents has_many :enemy_agents has_many :operations

...

def turn_mi6_agent (enemy_agent) zet "MI6 agent # enemy_agent.name overgedragen aan Spectre" einde

def turn_cia_agent (enemy_agent) plaatst "CIA agent # enemy_agent.name overgedragen aan Spectre" einde

def turn_mossad_agent (enemy_agent) zet "Mossad agent # enemy_agent.name overgedragen aan Spectre" einde

def kill_double_o_seven (spectre_agent) spectre_agent.kill_james_bond einde

def dispose_of_cabinet_member (number) spectre_member = SpectreMember.find_by_id (nummer)

stelt: "Een zekere schuldige heeft gefaald in de absolute integriteit van deze broederschap.De juiste handeling is om nummer # number in zijn stoel te roken." Zijn diensten zullen niet erg worden gemist "spectre_member.die end 

def print_assignment (operation) puts "Operation # operation.name 's doel is # operation.objective." einde

privaat

def vijand_agent #clever code einde

def spectre_agent #clever code einde

def operatie #clever code einde

...

einde

"

Spectre verandert verschillende soorten vijandelijke agenten, gedelegeerden vermoorden 007, grillen Spectre's kabinetsleden wanneer ze falen, en drukt ook operationele opdrachten uit. Een duidelijk geval van micromanagement en absoluut een schending van het "Single Responsibility Principle". Privé-methoden stapelen zich ook snel op.

Deze klasse hoeft niet de meeste dingen te weten die er momenteel in zitten. We zullen deze functionaliteit opsplitsen in een aantal klassen en kijken of de complexiteit van het hebben van een paar meer klassen / objecten de liposuctie waard is.

"robijn

klas Spectre < ActiveRecord::Base has_many :spectre_members has_many :spectre_agents has_many :enemy_agents has_many :operations

...

def turn_enemy_agent Interrogator.new (enemy_agent). einde einde

privaat

def vijand_agent self.enemy_agents.last end end

class Interrogator attr_reader: enemy_agent

def initialize (enemy_agent) @enemy_agent = enemy_agent end

def draai vijand_agent.turn einde

klasse EnemyAgent < ActiveRecord::Base belongs_to :spectre belongs_to :agency

def turn puts 'Na uitgebreid hersenspoelen, martelen en schatten van contanten ...' einde

klasse MI6Agent < EnemyAgent def turn super puts “MI6 agent #name turned over to Spectre” end end

klasse CiaAgent < EnemyAgent def turn super puts “CIA agent #name turned over to Spectre” end end

klasse MossadAgent < EnemyAgent def turn super puts “Mossad agent #name turned over to Spectre” end end

klasse NumberOne < ActiveRecord::Base def dispose_of_cabinet_member(number) spectre_member = SpectreMember.find_by_id(number)

stelt: "Een zekere schuldige heeft gefaald in de absolute integriteit van deze broederschap.De juiste handeling is om nummer # number in zijn stoel te roken." Zijn diensten zullen niet erg worden gemist "spectre_member.die end end 

klasse operatie < ActiveRecord::Base has_many :spectre_agents belongs_to :spectre

def print_assignment zet "Operation # name 's doel is # objective." end end

klas SpectreAgent < ActiveRecord::Base belongs_to :operation belongs_to :spectre

def kill_james_bond zet "Mr. Bond, ik verwacht dat je sterft! "Einde

klas SpectreMember < ActiveRecord::Base belongs_to :spectre

def die zet zet "Nooo, nee, het was niet meeeeeeeee! ZCHUNK! "Einde

"

Ik denk dat het belangrijkste onderdeel waar je op moet letten, is hoe we een eenvoudige Ruby-klasse hebben gebruikt Interrogator om het draaien van agenten van verschillende instanties af te handelen. Praktijkvoorbeelden kunnen een converter vertegenwoordigen die bijvoorbeeld een HTML-document omzet in een pdf en omgekeerd. Als u niet de volledige functionaliteit van Active Record-klassen nodig hebt, waarom zou u ze gebruiken als een eenvoudige Ruby-klasse ook de truc kan uithalen? Een beetje minder touw om ons mee op te hangen.

De Spectre-klasse laat de nare zaken van het draaien van agenten over aan de Interrogator klasse en delegeert er gewoon naar. Deze heeft nu de enige verantwoordelijkheid voor het martelen en hersenspoelen van gevangen genomen agenten.

Tot zover goed. Maar waarom hebben we voor elke agent afzonderlijke klassen gemaakt? Eenvoudig. In plaats van gewoon direct de verschillende draai-methoden te extraheren zoals turn_mi6_agent naar Interrogator, we gaven ze een beter thuis in hun eigen klas.

Dientengevolge kunnen we effectief gebruik maken van polymorfisme en geven we niet om individuele gevallen waarin agenten worden gedraaid. We vertellen deze verschillende agentobjecten om te keren en elk van hen weet wat te doen. De Interrogator hoeft niet de details te weten over hoe elke agent verandert.

Aangezien al deze agents Active Record-objecten zijn, hebben we een generieke agent gemaakt, EnemyAgent, dat heeft een algemeen idee van wat een agent betekent, en we kapselen die bit in voor alle agents op één plek door deze te subclasseren. We maken gebruik van deze erfenis door het leveren van de beurt methoden van de verschillende agenten met super, en daarom krijgen we toegang tot de afdeling hersenspoeling en foltering, zonder duplicatie. Enkele verantwoordelijkheden en geen duplicatie zijn een goed uitgangspunt om verder te gaan.

De andere Active Record-klassen nemen verschillende verantwoordelijkheden op zich waar Specter geen rekening mee hoeft te houden. "Number One" doet het grillen van falende Spectre-kabinetsleden meestal zelf, dus waarom zou een specifiek object geen elektrocutie behandelen? Aan de andere kant weten falende Spectre-leden hoe ze zelf moeten sterven als ze in hun stoel worden gerookt Nummer een. Operatie drukt nu zelf ook zijn opdrachten af ​​- het is niet nodig om de tijd van Spectre met pinda's als dat te verspillen.

Als laatste, maar daarom niet minder belangrijk, wordt het doden van James Bond meestal geprobeerd door een agent in het veld, dus kill_james_bond is nu een methode aan SpectreAgent. Goldfinger zou dat anders hebben aangepakt, natuurlijk - moet ik met dat lasergedoen spelen, als je er een hebt, denk ik.

Zoals je duidelijk kunt zien, hebben we nu tien klassen waar we eerder slechts één hadden. Is dat niet teveel? Het kan zeker zijn. Het is een kwestie waar je het grootste deel van de tijd mee moet worstelen wanneer je dergelijke verantwoordelijkheden opsplitst. Je kunt dit zeker overdrijven. Maar dit vanuit een andere hoek bekijken kan helpen:

  • Hebben we zorgen weggenomen? Absoluut!
  • Hebben we lichte, magere klassen die beter geschikt zijn voor het behandelen van unieke verantwoordelijkheden? Vrij zeker!
  • Vertellen we een "verhaal", schilderen we een duidelijker beeld van wie erbij betrokken is en wie heeft de leiding over bepaalde acties? ik hoop het!
  • Is het makkelijker om te verteren wat elke klas aan het doen is? Zeker!
  • Hebben we het aantal privémethoden verminderd? JEP!
  • Betekent dit een betere kwaliteit van objectgeoriënteerd programmeren? Omdat we compositie hebben gebruikt en vererving alleen hebben verwezen waar nodig voor het instellen van deze objecten, kun je erop rekenen!
  • Voelt het schoner aan? Ja!
  • Zijn we beter uitgerust om onze code te veranderen zonder er een puinhoop van te maken? Natuurlijk!
  • Was het het waard? Wat denk je?

Ik wil niet suggereren dat deze vragen telkens opnieuw moeten worden gecontroleerd, maar dit zijn de dingen die je jezelf waarschijnlijk zou moeten afvragen terwijl je je modellen afmestert.

Het ontwerpen van magere modellen kan moeilijk zijn, maar het is een essentiële maatregel om uw applicaties gezond en wendbaar te houden. Dit zijn ook niet de enige constructieve manieren om met dikke modellen om te gaan, maar ze zijn een goed begin, vooral voor beginners.

Ontbrekende testsuite

Dit is waarschijnlijk het meest voor de hand liggende AntiPattern. Als je uit de testgedreven kant van de dingen komt, is het aanraken van een volwassen app die geen testverslag heeft een van de meest pijnlijke ervaringen die je tegenkomt. Als je de wereld en je eigen beroep meer dan wat dan ook wilt haten, breng dan zes maanden door voor een dergelijk project en je zult leren hoeveel van een misantroop mogelijk in jou zit. Een grapje natuurlijk, maar ik betwijfel of het je gelukkiger zal maken en dat je het nog een keer wilt doen. Misschien is een week ook zo goed. Ik ben er vrij zeker van dat het woord marteling vaker in je opkomt dan je denkt.

Als testen tot nu toe geen deel uitmaakte van je proces en dat soort pijn normaal aanvoelt voor je werk, moet je misschien bedenken dat testen niet zo erg is, en het is ook niet je vijand. Wanneer uw code-gerelateerde joy-niveaus min of meer constant boven nul zijn en u uw code onbevreesd kunt veranderen, zal de algehele kwaliteit van uw werk een stuk hoger zijn in vergelijking met output die is bezoedeld door angst en lijden.

Overschat ik? Ik denk het echt niet! U wilt een zeer uitgebreide testverslaggeving, niet alleen omdat het een geweldig ontwerpprogramma is om alleen code te schrijven die u echt nodig hebt, maar ook omdat u uw code op een bepaald moment in de toekomst moet wijzigen. Je zult een stuk beter uitgerust zijn om je codebase te gebruiken - en nog veel meer zelfvertrouwen - als je een testtuig hebt dat refactoren, onderhoud en uitbreidingen ondersteunt en begeleidt. Ze zullen zeker langs de weg plaatsvinden, zonder enige twijfel daarover.

Dit is ook het punt waarop een testsuite de tweede ronde van dividenden begint af te betalen, omdat de verhoogde snelheid waarmee je deze kwaliteitswijzigingen veilig kunt maken, niet kan worden bereikt door een lange opname in apps die zijn gemaakt door mensen die schrijven testen is onzin of neemt te veel tijd in beslag.

Voyeuristische modellen

Dit zijn modellen die super nieuwsgierig zijn en te veel informatie willen verzamelen over andere objecten of modellen. Dat staat in schril contrast met een van de meest fundamentele ideeën in Object-Oriented Programming-encapsulation. We willen liever streven naar zelfstandige klassen en modellen die hun interne zaken zo goed mogelijk beheren. In termen van programmeerconcepten schenden deze voyeuristische modellen in feite het "Principe van de minste kennis", ook wel de "Wet van Demeter" genoemd - hoe je het ook wilt uitspreken.

Wet van Demeter

Waarom is dit een probleem? Het is een vorm van duplicatie - een subtiele - en leidt ook tot code die veel brosser is dan verwacht.

De wet van Demeter is vrijwel de meest betrouwbare codegeur die je altijd kunt aanvallen zonder je zorgen te maken over de mogelijke nadelen.

Ik denk dat het noemen van deze een "wet" niet zo pretentieus was als het in eerste instantie zou kunnen klinken. Graaf deze geur op, want je hebt het veel nodig in je projecten. Het stelt in feite dat je in termen van objecten methoden kunt aanroepen bij de vriend van je object, maar niet bij de vriend van je vriend.

Dit is een gebruikelijke manier om het uit te leggen, en het komt er allemaal op neer dat je niet meer dan één punt gebruikt voor je methodeaanroepen. Overigens is het prima om meer stippen of methodeaanroepen te gebruiken als je met een enkel object omgaat dat niet verder probeert te reiken dan dat. Zoiets als @ weapons.find_by_name ('Poison dart'). formule is prima. Finders kunnen soms behoorlijk wat punten stapelen. Het inkapselen van hen in speciale methoden is niettemin een goed idee.

Wet van Demeter-schendingen

Laten we een paar slechte voorbeelden uit de bovenstaande klassen bekijken:

"robijn

@ operation.spectre_agents.first.kill_james_bond

@ spectre.operations.last.spectre_agents.first.name

@ spectre.enemy_agents.last.agency.name

"

Om het onder de knie te krijgen, volgen hier nog enkele fictieve:

"robijn

@ quartermaster.gizmos.non_lethal.favorite

@ mi6.operation.agent.favorite_weapon

@ mission.agent.name

"

Bananen, toch? Ziet er niet goed uit, toch? Zoals je kunt zien, kijken deze methode-aanroepen te veel naar het werk van andere objecten. De belangrijkste en meest voor de hand liggende negatieve consequentie is het veranderen van een aantal van deze methodeaanvragen overal als de structuur van deze objecten moet veranderen - wat ze uiteindelijk zullen zijn, omdat de enige constante in softwareontwikkeling verandering is. Het ziet er ook heel smerig uit, helemaal niet gemakkelijk voor de ogen. Als je niet weet dat dit een problematische aanpak is, kun je met Rails hoe dan ook heel ver gaan, zonder naar je te schreeuwen. Veel touw, weet je nog?

Dus wat kunnen we hieraan doen? We willen tenslotte die informatie op de een of andere manier krijgen. Om te beginnen kunnen we onze objecten samenstellen om aan onze behoeften te voldoen, en we kunnen slim gebruik maken van delegatie om onze modellen tegelijkertijd slank te houden. Laten we in een code duiken om te laten zien wat ik bedoel.

"robijn

klas SpectreMember < ActiveRecord::Base has_many :operations has_many :spectre_agents

...

einde

klasse operatie < ActiveRecord::Base belongs_to :spectre_member

...

einde

klas SpectreAgent < ActiveRecord::Base belongs_to :spectre_member

...

einde

@ spectre_member.spectre_agents.all @ spectre_member.operations.last.print_assignment @ spectre_member.spectre_agents.find_by_id (1) .name

@ operation.spectre_member.name @ operation.spectre_member.number @ operation.spectre_member.spectre_agents.first.name

@ spectre_agent.spectre_member.number

"

"robijn

klas SpectreMember < ActiveRecord::Base has_many :operations has_many :spectre_agents

...

def list_of_agents spectre_agents.all end

def print_operation_details operation = Operation.last operation.print_operation_details end end

klasse operatie < ActiveRecord::Base belongs_to :spectre_member

...

def spectre_member_name spectre_member.name end

def spectre_member_number spectre_member.number end

def print_operation_details puts "Het doel van deze operatie is # objective. Het doel is het # end-einde # target

klas SpectreAgent < ActiveRecord::Base belongs_to :spectre_member

...

def superior_in_charge zet "Mijn baas is nummer # spectre_member.number" einde

@ spectre_member.list_of_agents @ spectre_member.print_operation_details

@ operation.spectre_member_name @ operation.spectre_member_number

@ spectre_agent.superior_in_charge

"

Dit is absoluut een stap in de goede richting. Zoals je ziet, hebben we de informatie die we wilden verzamelen verpakt in een aantal omhullende methoden. In plaats van rechtstreeks over veel objecten te reiken, hebben we deze bruggen geabstraheerd en overgelaten aan de respectieve modellen om met hun vrienden te praten over de informatie die we nodig hebben.

Het nadeel van deze aanpak is dat al deze extra omhullende methoden rondslingeren. Soms is het prima, maar we willen echt voorkomen dat deze methoden op een aantal plaatsen worden onderhouden als een object verandert.

Als dat mogelijk is, staat de plaats die ze moeten veranderen op hun object - en alleen op hun object. Objecten vervuilen met methoden die weinig te maken hebben met hun eigen model, is ook iets om op te letten, omdat dit altijd een potentieel gevaar is voor het afzwakken van afzonderlijke verantwoordelijkheden.

We kunnen het beter dan dat. Waar mogelijk, laten we methode calls direct delegeren aan hun verantwoordelijke objecten en proberen we zoveel mogelijk omver te werpen op wrapper-methoden. Rails weet wat we nodig hebben en biedt ons het handige delegeren klassenmethode om de vrienden van ons object te vertellen welke methoden we nodig hebben genoemd.

Laten we inzoomen op iets uit het vorige codevoorbeeld en kijken waar we delegatie goed kunnen gebruiken.

"robijn

klasse operatie < ActiveRecord::Base belongs_to :spectre_member

delegate: name,: number, to:: spectre_member, prefix: true

...

def spectre_member_name

# spectre_member.name # end

def spectre_member_number

# spectre_member.number # end

...

einde

@ operation.spectre_member_name @ operation.spectre_member_number

klas SpectreAgent < ActiveRecord::Base belongs_to :spectre_member

delegate: number, to:: spectre_member, prefix: true

...

def superior_in_charge zet "Mijn baas is nummer # spectre_member_number" einde

...

einde

"

Zoals u kunt zien, kunnen we de zaken wat vereenvoudigen met behulp van methode delegatie. We zijn kwijtgeraakt Operation # spectre_member_name en Operation # spectre_member_number volledig, en SpectreAgent hoeft niet te bellen aantal op spectre_member meer-aantal wordt direct teruggestuurd naar zijn "oorsprong" -klasse SpectreMember.

Als dit in het begin een beetje verwarrend is, hoe werkt dit dan precies? U vertelt afgevaardigde welke : method_name het zou moeten delegeren naar: welke :naam van de klasse (meerdere namen van de methoden zijn ook goed). De voorvoegsel: waar deel is optioneel.

In ons geval is de klasse van de klasse van de ontvangende klasse met slangomhulling voorafgegaan aan de naam van de methode en hebben we kunnen bellen operation.spectre_member_name in plaats van de potentieel dubbelzinnige operation.name-als we de prefixoptie niet hadden gebruikt. Dit werkt heel goed met hoort bij en heeft een verenigingen.

Op de heeft veel Maar de muziek zal stoppen en je zult in moeilijkheden komen. Deze associaties bieden u een verzamelproxy die NameErrors of NoMethodErrors naar u zal gooien wanneer u methoden delegeert aan deze "collecties".

Spaghetti SQL

Om dit hoofdstuk over het model AntiPatterns in Rails af te ronden, wil ik graag een beetje tijd besteden aan wat te vermijden als het om SQL gaat. Active Record-associaties bieden opties die uw leven aanzienlijk eenvoudiger maken als u weet waar u vandaan moet blijven. Finder-methoden zijn een heel apart onderwerp - en we zullen ze niet volledig behandelen - maar ik wilde een paar algemene technieken noemen die je helpen, zelfs wanneer je heel eenvoudige schrijft..

Dingen waar we ons zorgen over moeten maken, weerspiegelen het grootste deel van wat we tot nu toe hebben geleerd. We willen intentie-onthullende, eenvoudige en redelijk benoemde methoden hebben om dingen in onze modellen te vinden. Laten we direct in de code duiken.

"robijn

klasse operatie < ActiveRecord::Base

has_many: agents

...

einde

class Agent < ActiveRecord::Base

behoort_naar: operatie

...

einde

class OperationsController < ApplicationController

def index @operation = Operation.find (params [: id]) @agents = Agent.where (operation_id: @ operation.id, licence_to_kill: true) end end

"

Ziet er ongevaarlijk uit, toch? We zijn gewoon op zoek naar een aantal agenten die de licentie hebben om te doden voor onze ops-pagina. Denk nog eens na. Waarom zou de OperationsController graven in de binnenkant van Middel? Ook is dit echt het beste wat we kunnen doen om een ​​vinder in te kapselen Middel?

Als je denkt dat je een klassemethode zoals zou kunnen toevoegen Agent.find_licence_to_kill_agents die de zoeklogica inkapselt, je bent zeker een stap in de goede richting aan het zetten - maar dat is nog lang niet genoeg.

"robijn

class Agent < ActiveRecord::Base

behoort_naar: operatie

def self.find_licence_to_kill_agents (operatie) waarbij (operating_id: operation.id, licence_to_kill: true) end ...

einde

class OperationsController < ApplicationController

def index @operation = Operation.find (params [: id]) @agents = Agent.find_licence_to_kill_agents (@operation) end end

"

We moeten een beetje meer betrokken zijn dan dat. Allereerst gebruiken we de associaties niet in ons voordeel en is inkapseling ook niet optimaal. Verenigingen zoals heeft veel komen met het voordeel dat we kunnen toevoegen aan de proxy-array die we terug krijgen. In plaats daarvan hadden we dit kunnen doen:

"robijn

klasse operatie < ActiveRecord::Base

has_many: agents

def find_licence_to_kill_agents self.agents.where (licence_to_kill: true) end ...

einde

class OperationsController < ApplicationController

def index @operation = Operation.find (params [: id]) @agents = @ operation.find_licence_to_kill_agents end end

"

Dit werkt zeker, maar het is ook gewoon weer een kleine stap in de goede richting. Ja, de controller is een beetje beter, en we maken goed gebruik van modelassociaties, maar je moet toch wantrouwig blijven over waarom Operatie houdt zich bezig met de implementatie van het vinden van een bepaald type Middel. Deze verantwoordelijkheid behoort terug tot de Middel model zelf.

Benoemde scopes komen daar goed van pas. Scopes definiëren methoden met ketenbare methoden van zeer belangrijke klasse voor uw modellen en bieden u daarmee de mogelijkheid om nuttige zoekopdrachten op te geven die u kunt gebruiken als extra methode-aanroepen bovenop uw modelassociaties. De volgende twee benaderingen voor scoping Middel zijn onverschillig.

"robijn

class Agent < ActiveRecord::Base belongs_to :operation

scope: licenced_to_kill, -> where (licence_to_kill: true) einde

class Agent < ActiveRecord::Base belongs_to :operation

def self.licenced_to_kill waar (licence_to_kill: true) einde

class OperationsController < ApplicationController

def index @operation = Operation.find (params [: id]) @agents = @ operation.agents.licenced_to_kill end end

"

Dat is veel beter. In het geval dat de syntaxis van scopes nieuw voor je is, zijn ze gewoon (stabby) lambda's - niet erg belangrijk om ze meteen in te kijken - en ze zijn de juiste manier om scopes te bellen sinds Rails 4. Middel is nu verantwoordelijk voor het beheren van zijn eigen zoekparameters, en associaties kunnen gewoon stoppen met wat ze moeten vinden.

Met deze methode kunt u query's als enkele SQL-aanroepen uitvoeren. Ik persoonlijk vind het leuk om te gebruiken strekking voor zijn expliciteit. Scopes zijn ook erg handig om binnen bekende vindermethoden met elkaar te ketenen - op die manier vergroten ze de mogelijkheid om code en DRY-ing-code opnieuw te gebruiken. Laten we zeggen dat we iets meer betrokken hebben:

"robijn

class Agent < ActiveRecord::Base belongs_to :operation

scope: licenced_to_kill, -> where (licence_to_kill: true) scope: womanizer, -> where (womaniser: true) scope: bond, -> where (name: 'James Bond') scope: gambler, - > waar (gokker: waar) eindigt

"

We kunnen nu al deze scopes gebruiken om op maat gemaakte, complexere query's te maken.

"robijn

class OperationsController < ApplicationController

def index @operation = Operation.find (params [: id]) @double_o_agents = @ operation.agents.licenced_to_kill end

def show @operation = Operation.find (params [: id]) @bond = @ operation.agents.womanizer.gambler.licenced_to_kill end

... einde

"

Natuurlijk werkt dat, maar ik zou willen voorstellen om nog een stap verder te gaan.

"robijn

class Agent < ActiveRecord::Base belongs_to :operation

scope: licenced_to_kill, -> where (licence_to_kill: true) scope: womanizer, -> where (womaniser: true) scope: bond, -> where (name: 'James Bond') scope: gambler, - > waar (gokker: waar)

def self.find_licenced_to_kill licenced_to_kill end

Def self.find_licenced_to_kill_womanizer womanizer.licenced_to_kill end

def self.find_gambling_womanizer gambler.womanizer end

...

einde

class OperationsController < ApplicationController

def index @operation = Operation.find (params [: id]) @double_o_agents = @ operation.agents.find_licenced_to_kill end

def show @operation = Operation.find (params [: id]) @bond = @ operation.agents.find_licenced_to_kill_womanizer #of @bond = @ operation.agents.bond end

...

einde

"

Zoals u kunt zien, plukken we via deze aanpak de vruchten van goede inkapseling, modelassociaties, hergebruik van code en expressieve naamgeving van methoden - en dat terwijl we allemaal enkele SQL-query's uitvoeren. Geen spaghetti-code meer, geweldig!

Als je je zorgen maakt over het overtreden van de wet van Demeter, zul je blij zijn om dat te horen, omdat we geen stippen toevoegen door in het bijbehorende model te komen, maar ze alleen op hun eigen voorwerp te ketenen. We plegen dus geen Demeter-misdaden.

Laatste gedachten

Vanuit het perspectief van een beginneling, denk ik dat je veel hebt geleerd over een betere afhandeling van Rails-modellen en hoe je ze robuuster kunt modelleren zonder een beul op te eisen.

Laat je echter niet misleiden door te denken dat er niet veel meer te leren valt over dit specifieke onderwerp. Ik heb je een paar AntiPatterns gepresenteerd waarvan ik denk dat beginners ze gemakkelijk kunnen begrijpen en verwerken om zichzelf vroeg te beschermen. Als je niet weet wat je niet weet, is er veel t