Beaucoup de gens apprennent Ruby en commençant par le framework Rails et, malheureusement, c'est la pire façon d'apprendre ce langage. Ne vous méprenez pas : Rails est excellent, il vous aide à créer des applications web rapidement et efficacement sans avoir à entrer dans les détails techniques.
Enchanté de vous rencontrer !
Beaucoup de gens apprennent Ruby en commençant par le framework Rails et, malheureusement, c'est la pire façon d'apprendre ce langage. Ne vous méprenez pas : Rails est excellent, il vous aide à construire des applications web rapidement et efficacement sans avoir à entrer dans de nombreux détails techniques. Il fournit beaucoup de "magie Rails" qui permet de faire fonctionner les choses simplement. Et pour un programmeur débutant, c'est vraiment génial, car le moment le plus agréable du processus est celui où l'on peut dire "c'est vivant !", et où l'on voit que toutes les parties s'emboîtent et que les gens utilisent notre application. Nous aimons être des "créateurs" 🙂 Mais il y a une chose qui distingue les bons programmeurs de la moyenne : les bons programmeurs comprennent comment fonctionnent les outils qu'ils utilisent. Et par " comprendre ses outils ", je ne veux pas dire connaître toutes les méthodes et modules fournis par un framework, mais comprendre comment il fonctionne, comprendre comment la " magie Rails " opère. Ce n'est qu'alors que vous pourrez vous sentir à l'aise avec l'utilisation d'objets et la programmation avec Rails. La base de la programmation orientée objet, et l'arme secrète qui rend les applications Rails compliquées plus faciles, est le PORO, déjà mentionné dans le titre, c'est-à-dire Plain Old Ruby Object.
Qu'est-ce qui se cache vraiment sous ce nom ? Quelle est cette grande arme secrète ? C'est une simple classe Ruby qui n'hérite de rien. Oui, juste cela, et tant d'autres choses.
classe AwesomePoro
fin
Comment puis-je vous aider ?
Vous développez continuellement votre application et ajoutez de nouvelles fonctionnalités à mesure que le nombre d'utilisateurs et leurs attentes augmentent. Vous en arrivez au point où vous rencontrez de plus en plus d'endroits sombres d'une logique extrêmement tordue, des endroits que même les développeurs les plus courageux évitent comme la peste. Plus ces endroits sont nombreux, plus il est difficile de gérer et de développer l'application. Un exemple classique est l'action d'enregistrer un nouvel utilisateur, qui déclenche tout un groupe d'autres actions associées à cet événement :
- vérifier l'adresse IP dans une base de données de spam,
- l'envoi d'un courrier électronique au nouvel utilisateur,
- l'ajout d'un bonus sur le compte d'un utilisateur recommandant,
- la création de comptes dans des services connexes,
- et bien d'autres encore...
Un échantillon code responsable de l'enregistrement des utilisateurs pourrait ressembler à ceci :
classe RegistrationController < ApplicationController
def create
user = User.new(registration_params)
if user.valid ? && ip_valid ?(registration_ip)
user.save !
user.add_bonuses
user.synchronize_related_accounts
user.send_email
fin
fin
fin
D'accord, vous avez codé, tout fonctionne, mais... tout ce code est-il vraiment bon ? Peut-être pourrions-nous l'écrire mieux ? Tout d'abord, il enfreint le principe de base de la programmation, à savoir la responsabilité unique. Mais comment ? C'est ici que PORO, déjà mentionné, vient vous aider. Il suffit de séparer une classe RegistrationService, qui ne sera responsable que d'une seule chose : notifier tous les services concernés. Par services, nous entendons les actions individuelles que nous avons déjà identifiées plus haut. Dans le même contrôleur, il suffit de créer un objet RegistrationService et d'appeler la méthode "fire !". Le code est devenu beaucoup plus clair, notre contrôleur prend moins de place et chacune des classes nouvellement créées n'est plus responsable que d'une seule action, de sorte que nous pouvons facilement les remplacer si le besoin s'en fait sentir.
classe RegistrationService
def fire !(params)
user = User.new(params)
if user.valid ? && ip_validator.valid ?(registration_ip)
user.save !
after_registered_events(user)
end
utilisateur
fin
private
def after_registered_events(user)
BonusesCreator.new.fire !(user)
AccountsSynchronizator.fire !(utilisateur)
EmailSender.fire !(utilisateur)
end
def ip_validator
@ip_validator ||= IpValidator.new
end
fin
classe RegistrationController < ApplicationController
def create
user = RegistrationService.new.fire !(registration_params)
fin
fin
Cependant, Plain Old Ruby Object peut s'avérer utile non seulement pour les contrôleurs. Imaginez que l'application que vous créez utilise un système de facturation mensuelle. Le jour exact de la création d'une telle facture n'est pas important pour nous, nous avons seulement besoin de savoir qu'il s'agit d'un mois et d'une année spécifiques. Bien sûr, vous pouvez fixer le jour au premier jour de chaque mois et stocker cette information dans l'objet de la classe "Date", mais il ne s'agit pas d'une véritable information et vous n'en avez pas besoin dans votre application. En utilisant PORO, vous pouvez créer une classe "MonthOfYear", dont les objets stockeront l'information exacte dont vous avez besoin. De plus, en y appliquant le module "Comparable", il sera possible d'itérer et de comparer ses objets, tout comme lorsque vous utilisez la classe Date.
classe Mois de l'année
include Comparable
attr_reader :year, :month
def initialize(mois, année)
raise ArgumentError unless month.between ?(1, 12)
@year, @month = année, mois
end
def (other)
[année, mois] [autre.année, autre.mois]
end
fin
Présentez-moi Rails.
Dans le monde Rails, nous sommes habitués à ce que chaque classe soit un modèle, une vue ou un contrôleur. Elles ont également leur emplacement précis dans la structure des répertoires, alors où pouvez-vous mettre notre petite armée PORO ? Envisagez quelques options. La première idée qui vient à l'esprit est la suivante : si les classes créées ne sont ni des modèles, ni des vues, ni des contrôleurs, nous devrions toutes les placer dans le répertoire "/lib". Théoriquement, c'est une bonne idée, mais si tous vos fichiers PORO atterrissent dans un seul répertoire, et si l'application est volumineuse, ce répertoire deviendra rapidement un endroit sombre que vous craindrez d'ouvrir. Donc, sans aucun doute, ce n'est pas une bonne idée.
AwesomeProject
├──app
│ ├─contrôleurs
│ ├─models
│ └─views
│
└─lib
└─services
#all poro here
Vous pouvez également nommer certaines de vos classes "non-ActiveRecord Models" et les placer dans le répertoire "app/models", et nommer celles qui sont responsables de la gestion d'autres classes "services" et les placer dans le répertoire "app/services". C'est une assez bonne solution, mais elle présente un inconvénient : lorsque vous créez un nouveau PORO, vous devez à chaque fois décider s'il s'agit plutôt d'un modèle ou d'un service. De cette manière, vous risquez de vous retrouver avec deux zones d'ombre dans votre application, mais seulement des zones plus petites. Il existe encore une troisième approche, à savoir l'utilisation de classes et de modules à espace de noms. Il suffit de créer un répertoire portant le même nom qu'un contrôleur ou un modèle, et d'y placer tous les fichiers PORO utilisés par le contrôleur ou le modèle en question.
AwesomeProject
├──app
│ ├─contrôleurs
│ │ ├─registration_controller
│ │ └─registration_service.rb
│ │ └─registration_controller.rb
│ ├─models
│ │ ├─settlement
│ │ └─month_of_year.rb
│ │ └─settlement.rb
│ └─views
│
└─lib
Grâce à cette disposition, il n'est pas nécessaire de faire précéder le nom d'une classe d'un espace de noms. Vous avez ainsi obtenu un code plus court et une structure de répertoire plus logique.
Jetez un coup d'œil !
Il est agréablement surprenant de constater qu'en utilisant PORO, les tests unitaires de votre application sont plus rapides et plus faciles à écrire et, par la suite, plus susceptibles d'être compris par d'autres. Comme chaque classe n'est plus responsable que d'une seule chose, vous pouvez reconnaître plus rapidement les conditions limites et leur ajouter facilement des scénarios de test appropriés.
describe MonthOfYear do
sujet { MonthOfYear.new(11, 2015) }
it { should be_kind_of Comparable }
description "création d'une nouvelle instance" do
it "initialise avec l'année et le mois corrects" do
expect { described_class.new(10, 2015) }.to_not raise_error
end
it "lève une erreur lorsque le mois donné est incorrect" do
expect { described_class.new(0, 2015) }.to raise_error(ArgumentError)
expect { described_class.new(13, 2015) }.to raise_error(ArgumentError)
end
fin
end
J'espère que nous nous reverrons !
Les exemples que nous avons présentés montrent clairement que l'utilisation de PORO améliore la lisibilité des applications et les rend plus modulaires et, par conséquent, plus faciles à gérer et à développer. L'adoption du principe de la responsabilité unique facilite l'échange de classes particulières si nécessaire, et ce sans interférer avec d'autres éléments. Cela rend également la procédure de test plus simple et plus rapide. De plus, il est ainsi plus facile de réduire la taille des modèles et des contrôleurs Rails, qui, nous le savons tous, ont tendance à devenir inutilement volumineux au cours du processus de développement.