def spectre_member_name

Anti- quoi? Cela semble probablement beaucoup plus compliqué qu'il ne l'est. Au cours des deux dernières décennies, les programmeurs ont pu identifier une sélection utile de modèles de «conception» fréquemment utilisés dans leurs solutions de code. Tout en résolvant des problèmes similaires, ils ont pu «classer» les solutions qui les empêchaient de réinventer la roue tout le temps. Il est important de noter que ces modèles doivent être considérés davantage comme des découvertes que par les inventions d'un groupe de développeurs avancés..

Si ceci est plutôt nouveau pour vous et que vous vous considérez plus comme un débutant en Ruby / Rails, celui-ci est écrit pour vous. Je pense que c'est mieux si vous le considérez comme une rapide plongée dans un sujet beaucoup plus profond dont la maîtrise ne se fera pas du jour au lendemain. Néanmoins, je crois fermement que commencer à se mettre au travail tôt profitera énormément aux débutants et à leurs mentors..

Les anti-modèles, comme leur nom l'indique, représentent à peu près le contraire des modèles. Ce sont des découvertes de solutions à des problèmes que vous devez absolument éviter. Ils représentent souvent le travail de codeurs inexpérimentés qui ne savent pas encore ce qu’ils ne savent pas. Pire encore, ils pourraient être le produit d'une personne paresseuse qui ignore tout simplement les meilleures pratiques et les cadres d'outils sans raison valable - ou pense qu'ils n'en ont pas besoin. Ce qu’ils pourraient espérer gagner au début en gagner du temps en élaborant des solutions rapides, paresseuses ou sales, va les hanter ou leur succéder par la suite, plus tard dans le cycle de vie du projet..

Ne sous-estimez pas les conséquences de ces mauvaises décisions - elles vous tourmenteront, peu importe les circonstances..

Les sujets

  • Gros modèles
  • Suite de test manquante
  • Modèles Voyeuristes
  • Loi de Demeter
  • Spaghetti SQL

Gros modèles

Je suis sûr que vous avez entendu les chansons des «modèles Fat, contrôleurs maigres» chanter des tonnes de chansons quand vous avez commencé à jouer avec Rails. OK, maintenant oublie ça! Bien sûr, la logique métier doit être résolue dans la couche modèle, mais vous ne devez pas vous sentir enclin à tout y insérer sans raison, simplement pour éviter de franchir les lignes dans le territoire du contrôleur..

Voici une nouvelle cible que vous devriez viser: «Modèles maigres, contrôleurs maigres». Vous pourriez demander: «Eh bien, comment devrions-nous organiser le code pour y parvenir? Après tout, c'est un jeu à somme nulle?» Bon point! Le nom du jeu est composition, et Ruby est bien équipé pour vous donner beaucoup d'options pour éviter l'obésité modèle.

Dans la plupart des applications Web (Rails) qui s'appuient sur une base de données, la majeure partie de votre attention et de votre travail sera centrée sur la couche modèle, car vous travaillez avec des concepteurs compétents capables d'implémenter leurs propres éléments dans la vue, je veux dire. Vos modèles auront intrinsèquement plus de «gravité» et attireront plus de complexité.

La question est simplement de savoir comment vous comptez gérer cette complexité. Active Record vous donne certainement beaucoup de corde pour vous accrocher tout en vous rendant la vie incroyablement facile. Il est tentant de concevoir votre couche de modèle en suivant simplement le chemin de la plus grande commodité immédiate. Néanmoins, une architecture à l'épreuve du temps prend beaucoup plus en compte que de cultiver d'énormes classes et de tout ranger dans des objets Active Record..

Le vrai problème que vous traitez ici est la complexité - inutilement donc, je dirais. Les classes qui amassent des tonnes de code deviennent complexes du seul fait de leur taille. Ils sont plus difficiles à maintenir, à analyser et à comprendre et de plus en plus difficiles à modifier car leur composition manque probablement de découplage. Ces modèles dépassent souvent la capacité recommandée pour traiter une seule responsabilité et sont plutôt répandus. Dans le pire des cas, ils deviennent comme des camions à ordures, manipulant toutes les ordures qu'on leur jette paresseusement.

On peut faire mieux! Si vous pensez que la complexité n’est pas un problème, après tout, vous êtes spécial, intelligent et réfléchissez à nouveau! La complexité est le tueur de projet en série le plus notoire qui soit, pas votre quartier sympathique «Dark Defender»..

Les «modèles plus minces» réalisent une chose que les spécialistes du codage (probablement beaucoup plus que le code et le design) apprécient, et ce que nous devons tous rechercher - la simplicité! Ou du moins plus, ce qui est un compromis juste si la complexité est difficile à éradiquer.

Quels outils Ruby propose-t-il pour faciliter notre vie à cet égard et nous permettre de réduire le poids de nos modèles? Simple, d'autres classes et modules. Vous identifiez un code cohérent que vous pouvez extraire dans un autre objet et construisez ainsi une couche de modèle composée d'agents de taille raisonnable ayant leurs propres responsabilités distinctives..

Pensez-y en termes d'interprète talentueux. Dans la vraie vie, une telle personne pourrait être capable de rap, casser, écrire des paroles et produire ses propres airs. En programmation, vous préférez la dynamique d’un groupe, ici composé d’au moins quatre membres distincts, où chaque personne est responsable du moins de choses possible. Vous souhaitez créer un orchestre de classes capable de gérer la complexité du compositeur - pas une classe de génie maestro en microgestion de tous les métiers.

Regardons un exemple d'un modèle Fat et jouons avec quelques options pour gérer son obésité. Cet exemple est fictif, bien sûr, et en racontant cette petite histoire loufoque, j'espère qu'il sera plus facile à digérer et à suivre pour les débutants..

Nous avons une classe Spectre qui a trop de responsabilités et qui a donc grandi inutilement. En plus de ces méthodes, je pense qu’il est facile d’imaginer qu’un tel spécimen ait déjà accumulé de nombreuses autres choses aussi bien représentées par les trois petits points. Spectre est en passe de devenir une classe de dieu. (Les chances sont assez faibles pour formuler raisonnablement une telle phrase à nouveau de si tôt!)

"classe de rubis Spectre < ActiveRecord::Base has_many :spectre_members has_many :spectre_agents has_many :enemy_agents has_many :operations

def turn_mi6_agent (ennemie_agent) met «l'agent MI6 # ennemi_agent.nom remis à Spectre»

def turn_cia_agent (ennemis_agent) met «l'agent CIA # ennemi_agent.nom renvoyé à Spectre»

def turn_mossad_agent (ennemis_agent) met «l'agent Mossad # ennemi_agent.nom renvoyé à Spectre»

def kill_double_o_seven (spectre_agent) spectre_agent.kill_james_bond end

def dispose_of_cabinet_member (nombre) spectre_member = SpectreMember.find_by_id (nombre)

met "Un certain coupable a manqué à l'intégrité absolue de cette fraternité. L'acte approprié est de fumer le numéro # numéro dans son fauteuil. Ses services ne seront pas trop manqués" spectre_member.die end 

def print_assignment (operation) définit l'objectif de «Operation # operation.name»: # operation.objective. »end

privé

def ennemi_agent #clever code end

def spectre_agent #clever code end

opération def #clever code end

fin

"

Spectre transforme divers types d’agents ennemis, des délégués tuant 007, grillade les membres du cabinet de Spectre en cas de défaillance et imprime également des missions opérationnelles. Un cas évident de microgestion et une violation du «principe de responsabilité unique». Les méthodes privées s'accumulent aussi rapidement.

Cette classe n'a pas besoin de connaître la plupart des éléments qu'elle contient actuellement. Nous allons scinder cette fonctionnalité en quelques classes et voir si la complexité d'avoir deux classes / objets supplémentaires vaut la liposuccion.

"rubis

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

def turn_enemy_agent Interrogator.new (ennemi_agent) .turn end

privé

def ennemis_agents self.enemy_agents.last end end

Interrogateur de classe attr_reader: ennemi_agent

def initialise (ennemi_agent) @enemy_agent = ennemi_agent fin

def turn ennemi_agent.turn end end

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

def turn met "Après un lavage de cerveau intensif, la torture et des réserves d'argent…" end end

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

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

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

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

met "Un certain coupable a manqué à l'intégrité absolue de cette fraternité. L'acte approprié est de fumer le numéro # numéro dans son fauteuil. Ses services ne seront pas beaucoup manqués" spectre_member.die end end 

classe Operation < ActiveRecord::Base has_many :spectre_agents belongs_to :spectre

def print_assignment définit l'objectif de l'opération # name: # objectif. "end end

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

def kill_james_bond met “M. Bond, j'espère que tu mourras!

classe SpectreMember < ActiveRecord::Base belongs_to :spectre

def die met “Nooo, nooo, ce n'était pas meeeeeeeee! ZCHUNK! ”End end

"

Je pense que la partie la plus importante à laquelle vous devriez prêter attention est la façon dont nous avons utilisé une classe Ruby comme Interrogateur pour gérer le tournage des agents de différentes agences. Des exemples concrets pourraient représenter un convertisseur qui, par exemple, transforme un document HTML en PDF et inversement. Si vous n'avez pas besoin de toutes les fonctionnalités des classes Active Record, pourquoi les utiliser si une simple classe Ruby peut également faire l'affaire? Un peu moins de corde pour se pendre.

La classe Spectre laisse la mauvaise affaire des agents de tournage à la Interrogateur classe et juste des délégués à elle. Celui-ci a désormais la seule responsabilité de torturer et de laver le cerveau des agents capturés.

Jusqu'ici tout va bien. Mais pourquoi avons-nous créé des classes séparées pour chaque agent? Simple. Au lieu de simplement extraire directement les différentes méthodes de tournage comme turn_mi6_agent vers Interrogateur, nous leur avons donné une meilleure maison dans leur classe respective.

En conséquence, nous pouvons utiliser efficacement le polymorphisme et ne pas nous soucier des cas individuels des agents de retournement. Nous disons simplement à ces différents agents de tourner, et chacun d’entre eux sait quoi faire. le Interrogateur n'a pas besoin de connaître les détails de la transformation de chaque agent.

Tous ces agents étant des objets Active Record, nous en avons créé un générique., EnemyAgent, cela donne une idée générale de ce que signifie tourner un agent, et nous encapsulons ce bit pour tous les agents à un endroit en le sous-classant. Nous utilisons ce patrimoine en fournissant le tour méthodes des différents agents avec super, et donc nous avons accès au secteur du lavage de cerveau et de la torture, sans double emploi. Les responsabilités uniques et l'absence de duplication sont un bon point de départ pour aller de l'avant..

Les autres classes Active Record assument diverses responsabilités dont Specter n'a pas à se soucier. Le «numéro un» consiste généralement à griller lui-même les membres du cabinet Specter défaillants, alors pourquoi ne pas laisser un objet dédié gérer l'électrocution? En revanche, les membres de Spectre qui échouent savent comment mourir quand ils sont fumés dans leur fauteuil par Numéro un. Opération imprime maintenant ses tâches elle-même, pas besoin de perdre le temps de Spectre avec des cacahuètes comme ça.

Enfin et surtout, le meurtre de James Bond est généralement tenté par un agent sur le terrain. kill_james_bond est maintenant une méthode sur SpectreAgent. Goldfinger aurait géré cela différemment, bien sûr, je dois jouer avec ce truc laser si vous en avez un..

Comme vous pouvez le voir clairement, nous avons maintenant dix classes pour lesquelles nous n’en avions auparavant qu’une. N'est-ce pas trop? Cela peut être, à coup sûr. C'est un problème que vous devrez résoudre la plupart du temps lorsque vous vous séparez de telles responsabilités. Vous pouvez certainement en faire trop. Mais regarder cela sous un autre angle pourrait aider:

  • Avons-nous séparé les préoccupations? Absolument!
  • Avons-nous des classes légères et minces qui conviennent mieux à la gestion de responsabilités particulières? Plutôt sûr!
  • Est-ce que nous racontons une «histoire», est-ce que nous brossons un tableau plus clair de qui est impliqué et est responsable de certaines actions? j'espere!
  • Est-il plus facile de digérer ce que chaque classe fait? Pour sûr!
  • Avons-nous réduit le nombre de méthodes privées? Ouaip!
  • Cela représente-t-il une meilleure qualité de la programmation orientée objet? Puisque nous n’utilisions la composition et ne parlions d’héritage que lorsque cela était nécessaire pour configurer ces objets, vous pariez!
  • Vous sentez-vous plus propre? Oui!
  • Sommes-nous mieux outillés pour changer notre code sans faire de bêtises? Chose sûre!
  • Est-ce que ça valait le coup? Qu'est-ce que tu penses?

Je n'insinue pas que ces questions doivent être vérifiées à chaque fois sur votre liste, mais vous devriez probablement commencer à vous poser ces questions tout en amincissant vos modèles..

Concevoir des modèles minces peut s'avérer difficile, mais il s'agit d'une mesure essentielle pour que vos applications restent saines et agiles. Ce ne sont pas non plus les seuls moyens constructifs de traiter les gros modèles, mais ce sont un bon début, en particulier pour les débutants..

Suite de test manquante

C'est probablement l'anti-motif le plus évident. Du point de vue des tests, toucher une application mature qui n’a aucune couverture de test peut être l’une des expériences les plus pénibles à vivre. Si vous voulez détester le monde et votre propre profession plus que tout autre chose, consacrez six mois à un tel projet et vous saurez à quel point le misanthrope est potentiellement en vous. Je plaisante, bien sûr, mais je doute que cela vous rende plus heureux et que vous souhaitiez le refaire, jamais. Peut-être qu'une semaine fera aussi bien. Je suis à peu près sûr que le mot torture viendra plus souvent dans votre esprit que vous ne le pensez.

Si les tests ne faisaient pas partie de votre processus jusqu'à présent et que ce type de douleur vous semble normal, vous devriez peut-être considérer que les tests ne sont pas si mauvais, ni votre ennemi. Lorsque vos niveaux de joie liés au code sont plus ou moins constamment au-dessus de zéro et que vous pouvez modifier votre code sans crainte, la qualité globale de votre travail sera bien supérieure à celle d'un résultat teinté d'anxiété et de souffrance..

Est-ce que je surestime? Je ne pense vraiment pas! Vous voulez avoir une couverture de test très étendue, non seulement parce que c'est un excellent outil de conception pour écrire uniquement le code dont vous avez réellement besoin, mais aussi parce que vous devrez changer votre code à un moment donné. Vous serez beaucoup mieux équipé pour utiliser votre base de code - et beaucoup plus confiant - si vous avez un harnais de test qui aide et guide les refactorisations, la maintenance et les extensions. Ils se produiront à coup sûr sur la route, zéro doute à ce sujet.

C’est aussi le point où une suite de tests commence à porter ses fruits, car la vitesse accrue avec laquelle vous pouvez effectuer ces changements de qualité en toute sécurité ne peut pas être obtenue par une longue tentative dans les applications conçues par des personnes qui pensent écrire des tests. est absurde ou prend trop de temps.

Modèles Voyeuristes

Ce sont des modèles très curieux qui veulent collecter trop d'informations sur d'autres objets ou modèles. Cela contraste vivement avec l’une des idées les plus fondamentales de l’encapsulation par programmation orientée objet. Nous voulons plutôt lutter pour des classes et des modèles autonomes qui gèrent autant que possible leurs affaires intérieures. En termes de concepts de programmation, ces modèles voyeuristes violent fondamentalement le «principe de moindre connaissance», autrement dit la «loi de Demeter», peu importe la façon dont vous voulez le prononcer..

Loi de Demeter

Pourquoi c'est un problème? C'est une forme de duplication, subtile, qui conduit également à un code beaucoup plus fragile que prévu..

La loi de Demeter est à peu près l'odeur de code la plus fiable que l'on puisse toujours attaquer sans s'inquiéter des inconvénients possibles.

J'imagine que qualifier celle-ci de «loi» n'était pas aussi prétentieux que cela puisse paraître au premier abord. Creusez dans cette odeur, car vous en aurez beaucoup besoin dans vos projets. En gros, en termes d'objets, vous pouvez appeler des méthodes sur l'ami de votre objet mais pas sur l'ami de votre ami..

C'est une façon courante de l'expliquer, et tout se résume à ne pas utiliser plus d'un point pour vos appels de méthode. Soit dit en passant, il est tout à fait correct d’utiliser plus de points ou d’appels de méthode lorsque vous traitez avec un seul objet qui n’essaie pas d’atteindre autre chose que cela. Quelque chose comme @ arms.find_by_name ('Dart de poison'). formule est très bien. Les chercheurs peuvent parfois accumuler quelques points. Les encapsuler dans des méthodes dédiées est néanmoins une bonne idée.

Loi des violations de Demeter

Regardons quelques mauvais exemples des classes ci-dessus:

"rubis

@ operation.spectre_agents.first.kill_james_bond

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

@ spectre.enemy_agents.last.agency.name

"

Pour bien comprendre, voici quelques exemples plus fictifs:

"rubis

@ quartermaster.gizmos.non_lethal.favorite

@ mi6.operation.agent.favorite_weapon

@ mission.agent.name

"

Les bananes, non? Ça n'a pas l'air bien, n'est-ce pas? Comme vous pouvez le constater, ces appels de méthodes jettent un regard excessif sur l’activité d’autres objets. La conséquence négative la plus importante et la plus évidente est de changer un tas de ces appels de méthodes si la structure de ces objets doit être modifiée - ce qui sera finalement le cas, car la seule constante dans le développement logiciel est le changement. En outre, cela a l'air vraiment méchant, pas facile pour les yeux du tout. Lorsque vous ne savez pas qu'il s'agit d'une approche problématique, Rails vous permet d'aller très loin, de toute façon, sans vous hurler dessus. Beaucoup de corde, rappelez-vous?

Alors, que pouvons-nous faire à ce sujet? Après tout, nous voulons obtenir cette information d’une manière ou d’une autre. D'une part, nous pouvons composer nos objets pour répondre à nos besoins, et nous pouvons utiliser intelligemment la délégation pour garder nos modèles minces en même temps. Ouvrons un peu de code pour vous montrer ce que je veux dire.

"rubis

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

fin

classe Operation < ActiveRecord::Base belongs_to :spectre_member

fin

classe SpectreAgent < ActiveRecord::Base belongs_to :spectre_member

fin

@ 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

"

"rubis

classe 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

classe Operation < 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 met “L'objectif de cette opération est # objectif. La cible est # target ”end end

classe SpectreAgent < ActiveRecord::Base belongs_to :spectre_member

def superior_in_charge met “Mon patron est le numéro # spectre_member.number” end end

@ spectre_member.list_of_agents @ spectre_member.print_operation_details

@ operation.spectre_member_name @ operation.spectre_member_number

@ spectre_agent.superior_in_charge

"

C’est définitivement un pas dans la bonne direction. Comme vous pouvez le constater, nous avons regroupé les informations que nous souhaitions acquérir dans un ensemble de méthodes d’emballage. Au lieu de toucher directement de nombreux objets, nous avons résumé ces ponts et laissé le soin aux modèles respectifs de parler à leurs amis des informations dont ils ont besoin..

L'inconvénient de cette approche est d'avoir toutes ces méthodes d'emballage supplémentaires qui traînent. Parfois ça va, mais nous voulons vraiment éviter de maintenir ces méthodes dans un tas d'endroits si un objet change.

Si possible, l'endroit où ils doivent changer est sur leur objet et uniquement sur leur objet. Il faut également se méfier des objets polluants utilisant des méthodes qui ont peu à voir avec leur propre modèle, car cela représente toujours un risque potentiel de dilution des responsabilités individuelles..

Nous pouvons faire mieux que ça. Dans la mesure du possible, déléguons les appels de méthodes directement aux objets en charge et essayons de réduire le plus possible les méthodes d'encapsidation. Rails sait ce dont nous avons besoin et nous fournit le plus pratique déléguer méthode de classe pour dire aux amis de notre objet quelles méthodes nous avons besoin appelé.

Zoomons sur quelque chose de l'exemple de code précédent et voyons où nous pouvons utiliser correctement la délégation..

"rubis

classe Operation < ActiveRecord::Base belongs_to :spectre_member

délégué: nom,: numéro, à:: spectre_member, préfixe: true

def spectre_member_name

# spectre_member.name # end

def spectre_member_number

# spectre_member.number # end

fin

@ operation.spectre_member_name @ operation.spectre_member_number

classe SpectreAgent < ActiveRecord::Base belongs_to :spectre_member

delegate: number, to:: spectre_member, préfixe: true

def superior_in_charge met “Mon patron est le numéro # spectre_member_number” fin

fin

"

Comme vous pouvez le constater, nous pourrions simplifier un peu les choses en utilisant la délégation de méthodes. Nous nous sommes débarrassés de Opération # spectre_member_name et Opération # spectre_member_number complètement et SpectreAgent n'a pas besoin d'appeler nombre sur spectre_member plus-nombre est déléguée directement à sa classe «d'origine» SpectreMember.

Au cas où cela serait un peu déroutant au début, comment cela fonctionne-t-il exactement? Vous dites au délégué lequel : nom_méthode il devrait déléguer à: lequel :nom du cours (plusieurs noms de méthodes sont bien aussi). le préfixe: true la partie est optionnelle.

Dans notre cas, il préfixait le nom de la classe destinataire de la classe destinataire avant le nom de la méthode et nous permettait d'appeler operation.spectre_member_name au lieu de la potentiellement ambiguë operation.name-si nous n'avions pas utilisé l'option de préfixe. Cela fonctionne vraiment bien avec appartient à et en a un les associations.

Sur le a beaucoup côté de choses, cependant, la musique s'arrête et vous aurez des ennuis. Ces associations vous fournissent un proxy de collection qui vous enverra NameErrors ou NoMethodErrors lorsque vous déléguerez des méthodes à ces "collections"..

Spaghetti SQL

Pour terminer ce chapitre sur les modèles AntiPatterns dans Rails, j'aimerais passer un peu de temps sur ce qu'il faut éviter lorsque SQL est impliqué. Les associations Active Record offrent des options qui facilitent considérablement votre vie lorsque vous savez à quoi vous tenir éloigné. Les méthodes de recherche constituent un sujet à part entière - et nous ne les aborderons pas en profondeur - mais je voulais mentionner quelques techniques courantes qui vous aideront même lorsque vous en écrivez de très simples..

Les choses qui devraient nous préoccuper font écho à la plupart de ce que nous avons appris jusqu'à présent. Nous souhaitons disposer de méthodes simples et raisonnablement désignées, révélatrices d'intention, permettant de rechercher des éléments dans nos modèles. Plongeons dans le code.

"rubis

classe Operation < ActiveRecord::Base

has_many: agents

fin

agent de classe < ActiveRecord::Base

appartient_à: opération

fin

classe OperationsController < ApplicationController

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

"

Semble inoffensif, non? Nous recherchons simplement un groupe d’agents possédant le permis de tuer pour notre page ops. Pensez encore. Pourquoi le OperationsController creuser dans les entrailles de Agent? Aussi, est-ce vraiment le mieux que nous puissions faire pour encapsuler un chercheur sur Agent?

Si vous pensez que vous pourriez ajouter une méthode de classe comme Agent.find_licence_to_kill_agents qui encapsule la logique de recherche, vous faites certainement un pas dans la bonne direction - pas assez, cependant.

"rubis

agent de classe < ActiveRecord::Base

appartient_à: opération

def self.find_licence_to_kill_agents (operation) où (operation_id: operation.id, licence_to_kill: true) end…

fin

classe OperationsController < ApplicationController

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

"

Nous devons être un peu plus engagés que cela. Tout d'abord, cela n'utilise pas les associations à notre avantage et l'encapsulation est également sous-optimale. Des associations comme a beaucoup venir avec l'avantage que nous pouvons ajouter sur le tableau de proxy que nous sommes retournés. Nous aurions pu le faire à la place:

"rubis

classe Operation < ActiveRecord::Base

has_many: agents

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

fin

classe OperationsController < ApplicationController

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

"

Cela fonctionne, bien sûr, mais n’est qu’un pas de plus dans la bonne direction. Oui, le contrôleur est un peu meilleur et nous utilisons bien les associations de modèles, mais vous devriez toujours vous demander pourquoi. Opération est concerné par la mise en œuvre de la recherche d'un certain type de Agent. Cette responsabilité revient à la Agent se modèle.

Les portées nommées sont très utiles avec cela. Les étendues définissent des méthodes chainable-very important-class pour vos modèles et vous permettent ainsi de spécifier des requêtes utiles que vous pouvez utiliser comme appels de méthode supplémentaires par-dessus vos associations de modèles. Les deux approches suivantes pour le cadrage Agent sont indifférents.

"rubis

agent de classe < ActiveRecord::Base belongs_to :operation

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

agent de classe < ActiveRecord::Base belongs_to :operation

def self.licenced_to_kill où (licence_to_kill: true) se termine

classe OperationsController < ApplicationController

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

"

C'est beaucoup mieux. Si la syntaxe des portées est nouvelle pour vous, il ne s'agit que de (stabby) lambdas - il n'est pas extrêmement important de les examiner tout de suite, au fait - et elles constituent le moyen approprié d'appeler des portées, car Rails 4. Agent est maintenant en charge de la gestion de ses propres paramètres de recherche, et les associations peuvent simplement choisir ce dont elles ont besoin pour trouver.

Cette approche vous permet de réaliser des requêtes en tant qu'appels SQL uniques. Personnellement, j'aime utiliser portée pour sa clarté. Les scopes sont également très pratiques pour chaîner des méthodes de recherche bien nommées, ce qui augmente les possibilités de réutilisation du code et du code DRY-ing. Disons que nous avons quelque chose d'un peu plus impliqué:

"rubis

agent de classe < ActiveRecord::Base belongs_to :operation

portée: licenced_to_kill, -> où (licence_to_kill: true) portée: womanizer, -> où (womanizer: true) portée: bond, -> où (nom: 'James Bond') portée: joueur, - > where (gambler: true) end

"

Nous pouvons maintenant utiliser toutes ces étendues pour personnaliser des requêtes plus complexes.

"rubis

classe 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

… fin

"

Bien sûr, cela fonctionne, mais je voudrais vous suggérer d'aller plus loin.

"rubis

agent de classe < ActiveRecord::Base belongs_to :operation

portée: licenced_to_kill, -> où (licence_to_kill: true) portée: womanizer, -> où (womanizer: true) portée: bond, -> où (nom: 'James Bond') portée: joueur, - > où (joueur: vrai)

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

fin

classe 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 #ou @bond = @ operation.agents.bond end

fin

"

Comme vous pouvez le constater, cette approche nous permet de tirer parti des avantages d’une encapsulation correcte, des associations de modèles, de la réutilisation de code et de la dénomination expressive de méthodes, le tout tout en effectuant des requêtes SQL uniques. Pas plus de code spaghetti, génial!

Si vous craignez de ne pas enfreindre la loi de Demeter thingie, vous serez ravi d'apprendre que, puisque nous n'ajoutons pas de points en analysant le modèle associé mais en les enchaî