Votre site Web évolue et vous grandissez rapidement. Ruby / Rails est votre meilleure option de programmation. Votre équipe est plus grande et vous avez déjà quitté le concept 'gros modèles, pilotes minces' ( modèles gras, contrôleurs maigres ) comme style de conception pour vos applications Rails. Cependant, vous ne voulez toujours pas arrêter d'utiliser Rails.
Il n'y a pas de problème. Aujourd'hui, nous allons discuter de la manière d'utiliser les meilleures pratiques de POO pour rendre votre code plus propre, plus isolé et séparé.
Commençons par regarder comment vous devez décider si votre application est un bon candidat pour la refactorisation.
Voici une liste de métriques et de questions que je me pose habituellement pour déterminer si mes codes doivent être refactorisés ou non.
Essayez d'utiliser quelque chose comme ceci pour savoir combien de lignes de code source Ruby vous avez:
find app -iname '*.rb' -type f -exec cat {} ;| wc -l
Cette commande recherchera tous les fichiers avec une extension .rb (fichiers ruby) dans le dossier / app, puis imprimera le nombre de lignes. Veuillez noter que ce nombre n'est qu'une estimation, car les lignes de commentaires seront incluses dans ce total.
Une autre option plus précise et informative consiste à utiliser la tâche râteau stats
Rails, qui expose un résumé rapide des lignes de code, du nombre de classes, du nombre de méthodes, du ratio méthodes / classes et du ratio de lignes de code par méthode:
*bundle exec rake stats* +----------------------+-------+-----+-------+---------+-----+-------+ | Nombre | Líneas | LOC | Clase | Método | M/C | LOC/M | +----------------------+-------+-----+-------+---------+-----+-------+ | Controladores | 195 | 153 | 6 | 18 | 3 | 6 | | Helpers | 14 | 13 | 0 | 2 | 0 | 4 | | Modelos | 120 | 84 | 5 | 12 | 2 | 5 | | Mailers | 0 | 0 | 0 | 0 | 0 | 0 | | Javascripts | 45 | 12 | 0 | 3 | 0 | 2 | | Bibliotecas | 0 | 0 | 0 | 0 | 0 | 0 | | Controlador specs | 106 | 75 | 0 | 0 | 0 | 0 | | Helper specs | 15 | 4 | 0 | 0 | 0 | 0 | | Modelo specs | 238 | 182 | 0 | 0 | 0 | 0 | | Petición specs | 699 | 489 | 0 | 14 | 0 | 32 | | Routing specs | 35 | 26 | 0 | 0 | 0 | 0 | | Vista specs | 5 | 4 | 0 | 0 | 0 | 0 | +----------------------+-------+-----+-------+---------+-----+-------+ | Total | 1472 |1042 | 11 | 49 | 4 | 19 | +----------------------+-------+-----+-------+---------+-----+-------+ Código LOC: 262 Prueba LOC: 780 Ratio Código a Prueba: 1:3.0
Commençons par un exemple réel.
Supposons que nous voulions écrire une application qui suit la météo pour les gens qui font du jogging; Sur la page principale, l'utilisateur peut voir les heures entrées.
Chacune des entrées d'heure a la date, la distance, la durée et des informations d'état supplémentaires pertinentes (par exemple, la météo, le type de terrain, etc.), et une vitesse moyenne qui peut être calculée si nécessaire.
Nous avons besoin d'un rapport qui montre la vitesse moyenne et la distance par semaine. Si la vitesse moyenne à l'entrée est supérieure à la vitesse moyenne au total, nous en informerons l'utilisateur par SMS (pour cet exemple, nous utiliserons Nexmo API RESTful pour envoyer le SMS).
La page principale vous permettra de sélectionner la distance, la date et l'heure auxquelles vous faites du jogging, afin de créer une entrée similaire à celle-ci:
Nous avons également une page estadísticas
, qui est essentiellement un rapport hebdomadaire qui comprend la vitesse moyenne et la distance parcourue par semaine.
La structure de répertoires de aplicación
ressemble à ceci:
⇒ tree . ├── assets │ └── ... ├── controllers │ ├── application_controller.rb │ ├── entries_controller.rb │ └── statistics_controller.rb ├── helpers │ ├── application_helper.rb │ ├── entries_helper.rb │ └── statistics_helper.rb ├── mailers ├── models │ ├── entry.rb │ └── user.rb └── views ├── devise │ └── ... ├── entries │ ├── _entry.html.erb │ ├── _form.html.erb │ └── index.html.erb ├── layouts │ └── application.html.erb └── statistics └── index.html.erb
Je ne parlerai pas du modèle Usuario
car il n'y a rien d'extraordinaire, puisque nous l'utilisons avec Devise pour implémenter l'authentification.
Concernant le modèle Entrada
, il contient la logique métier de notre application.
Chaque Entrada
appartient à un Usuario
.
Nous validons la présence des attributs suivants pour chaque entrée distancia
, períodode_tiempo
, fecha_hora
et estatus
.
Chaque fois que nous créons une entrée, nous comparons la vitesse moyenne de l'utilisateur avec la moyenne de tous les utilisateurs du système, et notifions l'utilisateur par SMS, en utilisant Nexmo (Nous ne discuterons pas de la façon dont la bibliothèque Nexmo est utilisée, même si je voulais démontrer un cas où nous utilisons une bibliothèque externe).
Notez que le Entrada
le modèle contient plus que la seule logique métier. Il gère également certaines validations et appels.
Le entries_controller.rb
a les actions CRUEL main (bien que sans mise à jour). EntriesController#index
récupère les entrées de l'utilisateur actuel et trie les enregistrements par date de création, tandis que EntriesController#create
créer une nouvelle entrée. Il n'est pas nécessaire de discuter de l'évidence ou des responsabilités de EntriesController#destroy
:
Alors que statistics_controller.rb
est responsable du calcul du rapport hebdomadaire, StatisticsController#index
récupère les entrées de l'utilisateur connecté et les regroupe par semaine, en utilisant le #group_by
qui est dans la classe Énumérable dans Rails. Essayez ensuite de décorer les résultats en utilisant des méthodes privées.
Nous ne discutons pas beaucoup des vues ici car le code source est explicite.
Ci-dessous se trouve la vue pour lister les entrées de l'utilisateur connecté (index.html.erb
). C'est le modèle qui sera utilisé pour afficher les résultats de l'action d'index (méthode) dans le gestionnaire d'entrée:
Notez que nous utilisons render @entries
Partiels, pour amener le code partagé dans un modèle partiel _entry.html.erb
pour que nous puissions garder notre code SEC et réutilisable:
La même chose s'applique à un _forma
partiel. Au lieu d'utiliser le même code avec des actions (nouvelles et modifiées), nous créons un formulaire partiel réutilisable:
En ce qui concerne la vue de la page du rapport hebdomadaire, statistics/index.html.erb
affiche des statistiques et des rapports sur les activités hebdomadaires de l'utilisateur en regroupant certaines entrées:
Et enfin, le assistant pour les entrées, entries_helper.rb
, comprend deux aides readable_time_period
et readable_speed
ce qui devrait faciliter la lecture des attributs:
Rien de trop compliqué pour l'instant.
La plupart d'entre vous peuvent affirmer que la refactorisation est contraire au principe BAISER et cela rendra le système plus compliqué.
Alors, cette application a-t-elle vraiment besoin d'être refactorisée?
Absolument non , mais nous ne le considérerons qu'à titre d'exemple.
Après tout, si vous regardez la section suivante et les caractéristiques qui indiquent qu'une application doit être refactorisée, il devient évident que l'application dans notre exemple n'est pas un candidat valide pour la refactorisation.
Commençons par expliquer le modèle de structure MVC en Rails.
Cela commence généralement par le moteur de recherche en faisant une requête comme https://www.toptal.com/jogging/show/1
.
Le serveur Web reçoit la demande et utilise rutas
définir quoi controlador
porter.
Les contrôleurs effectuent le travail d'analyse des demandes des utilisateurs, des livraisons de données, des cookies, des sessions, etc., puis demandent modelo
obtenir les données.
Le modelos
Ce sont des classes Ruby qui communiquent avec la base de données, enregistrent et valident les données, exécutent la logique métier et font le gros du travail. Les vues sont ce que l'utilisateur peut voir: HTML, CSS, XML, Javascript, JSON.
Si nous voulons afficher la séquence d'une demande de cycle de vie Rails, cela ressemblerait à ceci:
Ce que je veux réaliser, c'est ajouter plus d'abstraction, en utilisant des PORO et faire du modèle quelque chose de similaire à ce qui suit, pour les actions create/update
:
Et quelque chose de similaire pour les actions list/show
:
En ajoutant des abstractions PORO, nous assurons une séparation complète, entre les responsabilités FAUCILLE , quelque chose que Rails ne maîtrise pas totalement.
Pour réaliser le nouveau design, j'utiliserai les directives ci-dessous, mais gardez à l'esprit que ce ne sont pas des règles que vous devez suivre à la lettre. Considérez-les comme des lignes directrices flexibles qui facilitent la refactorisation.
Les modèles ActiveRecord peuvent contenir des associations et des constantes, mais rien d'autre. Cela signifie qu'il n'y aura pas d'appels (utilisez des objets de service et ajoutez-y les appels) et pas de validations (utilisez Objets de formulaire pour inclure les noms et les validations du modèle).
Conservez les contrôleurs sous forme de couches minces et appelez toujours les objets Service. Certains d'entre vous peuvent se demander pourquoi utiliser des contrôleurs, si nous voulons continuer à appeler des objets de service pour contenir la logique? Eh bien, les contrôleurs sont un bon endroit pour avoir le routage HTTP, analyse des paramètres, authentification, négociation de contenu, appel du service ou de l'éditeur correct, détection des exceptions, mise en forme des réponses et renvoi du statut de code HTTP correct.
Requêtes doit être fait avec des objets requete . Méthodes objet Requete doit retourner un objet, un hacher ou tableau , mais pas une association ActiveRecord.
Évitez d'utiliser Aides , mieux utiliser les décorateurs. Parce que? Une difficulté commune avec aides dans Rails, c'est qu'ils peuvent être transformés en un tas de non- OO , qui partagent un nom d'espace et se chevauchent. Mais c'est bien pire, le fait non que vous ne puissiez utiliser aucun polymorphisme avec le aides Rails - en fournissant différentes implémentations pour différents contextes ou types, et en contournant ou en sous-classifiant les helpers. Je pense que les types de assistant dans Rails, ils doivent généralement être utilisés pour des méthodes utilitaires, pas pour des cas d'utilisation spécifiques; comment mettre en forme les attributs de modèle pour toute logique de présentation. Gardez-les légers et faciles à suivre. Décorateurs / Délégués mieux. ** Pourquoi? Après tout, les soucis semblent faire partie des rails, et ils peuvent sécher ( Sécher ) un code lorsqu'il est partagé entre plusieurs modèles. Cependant, le plus gros problème est que les préoccupations ne rendent pas l'objet modèle plus cohérent. Seul le code est mieux organisé. En d'autres termes, il n'y a pas de véritable changement dans l'API du modèle.
Essayez d'extraire Objets de valeur des modèles pour garder votre code plus propre et regrouper les attributs associés.
Avant de commencer, je veux discuter d'autre chose. Lorsque la refactorisation commence, vous vous demandez généralement: 'Est-ce un bon refactoring?'
Si vous sentez que vous faites plus de séparation ou d'isolement entre les responsabilités (même si cela signifie ajouter plus de code et de nouveaux fichiers), c'est une bonne chose. Après tout, détacher une application est une très bonne pratique et nous permet de réaliser plus facilement un test unitaire approprié.
Je ne vais pas discuter de choses, comme déplacer la logique des contrôleurs vers les modèles, car je suppose que vous faites cela et que vous êtes à l'aise avec les Rails (généralement Skinny Controller et le modèle FAT).
Afin de garder cet article concis, je ne vais pas discuter de la façon de tester, mais cela ne signifie pas que vous ne devriez pas tester.
Au contraire, vous devriez commencez toujours par un test pour s'assurer que tout va bien, avant d'aller de l'avant. C'est quelque chose obligatoire, surtout lors du refactoring.
Nous pouvons ensuite implémenter des modifications et nous assurer que les tests passent par les parties pertinentes du code.
Premièrement, qu'est-ce qu'un objet de valeur?
Martin Fowler Explique:
L'objet de valeur est un petit objet, tel qu'un objet argent ou plage de dates. Leur principale caractéristique est qu'ils suivent la sémantique des valeurs, plutôt que la sémantique référentielle.
Parfois, vous pouvez vous retrouver dans une situation où un concept mérite sa propre abstraction et où son égalité ne repose pas sur des valeurs, mais sur une identité. Des exemples de ceci peuvent être: la date, l'URI et le chemin de Ruby. L'extraction d'un objet précieux (ou d'un modèle de domaine) est d'une grande commodité.
Pourquoi s'embêter?
L'un des grands avantages d'un objet de valeur est qu'il aide à obtenir l'expressivité de votre code. Votre code aura tendance à être plus clair, ou du moins cela peut l'être si vous avez de bonnes pratiques de dénomination. Puisque l'objet de valeur est une abstraction, il conduit à des codes plus clairs et moins d'erreurs.
Un autre avantage est le immutabilité . L'immuabilité des objets est très importante. Lorsque nous stockons certains ensembles de données, qui peuvent être utilisés dans un objet de valeur, je n'aime généralement pas que les données soient manipulées.
Quand est-ce utile?
Il n'y a pas de réponse parfaite à cette question. Faites ce qui est le mieux pour vous et ce qui a le plus de sens dans une situation donnée.
Au-delà de cela, il y a quelques lignes directrices que j'utilise pour m'aider à prendre cette décision.
Si vous pensez qu'un groupe de méthodes est lié, eh bien, les objets de valeur sont plus chers. Cette expressivité signifie qu'un objet de valeur doit représenter un ensemble de données distinctif, qui peut être déduit par votre développeur moyen simplement en regardant le nom de l'objet.
Comment se fait ceci?
Les objets de valeur doivent suivre certaines règles:
Dans notre exemple, je vais créer un objet de valeur EntryStatus
, pour abstraire les attributs Entry#status_weather
et Entry#status_landform
à sa propre classe, qui ressemble à ceci:
Remarque: Il ne s'agit que d'un PORO (Plain Old Ruby Object), il n'hérite pas de ActiveRecord::Base
. Nous avons défini des méthodes de lecture pour nos attributs et nous les attribuons au démarrage. De plus, nous utilisons un mélange comparable pour faire correspondre les objets à l'aide de la méthode ().
Nous pouvons modifier le modèle Entry
pour utiliser l'objet de valeur que nous avons créé:
Nous pouvons également modifier la méthode EntryController#create
pour utiliser le nouvel objet de valeur en conséquence:
Qu'est-ce qu'un objet Service?
Le travail d'un objet Service est de contenir le code pendant un espace particulier de la logique métier. Contrairement au style 'Gros modèle' , où un petit nombre d'objets contient de très nombreuses méthodes, pour toute la logique nécessaire, l'utilisation d'objets de service entraîne la création de nombreuses classes, chacune ayant un objectif unique.
Parce que? Quels sont les bénéfices?
Démonter. Les objets de service vous aident à obtenir une meilleure isolation entre les objets.
Visibilité. Les objets de service (s'ils sont correctement nommés) montrent ce que fait une application. Je peux parcourir le répertoire des services pour voir les fonctionnalités offertes par une application.
SEC et acceptez le changement. Je garde les articles de service aussi simples et petits que possible. Je compose des objets de service avec d'autres objets de service et je les réutilise.
Nettoyez et accélérez votre suite de tests. Les services sont rapides et faciles à tester, car ce sont de petits objets Ruby avec un point d'entrée (la méthode appelée). Les services complexes sont complétés par d'autres services, vous pouvez donc facilement séparer vos tests. En outre, l'utilisation d'objets de service facilite la récupération des objets associés sans charger l'ensemble de l'environnement des rails.
D'un autre côté, rien n'est parfait. Un inconvénient des objets Service est qu'ils peuvent être exagérés pour chaque petite action. Dans ces cas, vous pouvez finir par compliquer et non simplifier votre code.
Quand devez-vous extraire des objets de service?
Il n'y a pas non plus de règle fixe ici.
En règle générale, les objets Service sont les meilleurs pour les systèmes de taille moyenne à grande: ceux avec une quantité décente de logique, au-delà des opérations CRUD standard.
Ainsi, lorsque vous pensez qu'un morceau de code n'appartient pas au répertoire, à l'endroit où vous alliez l'ajouter, c'est une bonne idée de le reconsidérer et de voir s'il vaudrait mieux qu'il aille vers un objet de service.
Voici quelques conseils pour savoir quand utiliser les objets Service:
Comment devez-vous concevoir des objets de service?
La conception de la classe pour un objet de service est relativement simple, car vous n'avez pas besoin gemmes tu ne devrais pas apprendre un DLS nouveau, mais si vous pouvez, plus ou moins, compter sur les compétences en conception de logiciels que vous possédez déjà.
En général, j'utilise les directives et conventions suivantes pour concevoir l'objet de service:
app/services
. Je vous conseille d'utiliser des sous-répertoires, pour des domaines de logique métier forts. Par exemple, le fichier app/services/report/generate_weekly.rb
définira Report::GenerateWeekly
tandis que app/services/report/publish_monthly.rb
définira Report::PublishMonthly
.ApproveTransaction
, SendTestNewsletter
, ImportUsersFromCsv
.Si vous regardez StatisticsController#index
, vous remarquerez un groupe de méthodes (weeks_to_date_from
, weeks_to_date_to
, avg_distance
, etc.) regroupées dans le contrôleur. Ce n'est pas bon. Tenez compte des ramifications, si vous souhaitez générer le rapport hebdomadaire en dehors de statistics_controller
.
Dans notre cas, nous allons créer Report::GenerateWeekly
et extrayez le rapport logique de StatisticsController
:
Donc StatisticsController#index
maintenant ça a l'air plus propre:
En appliquant le modèle d'objet Service, nous regroupons le code autour d'une action complexe et spécifique et favorisons la création de méthodes plus petites et plus claires.
Tâche: envisagez d'utiliser Objet précieux pour le WeeklyReport
au lieu de Struct
.
Qu'est-ce qu'un objet Requete ?
Un objet Requete est un PORE, qui représente une base de données de requêtes. Il peut être réutilisé à différents endroits de l'application, tout en masquant la logique de la requête. Il fournit également une bonne unité isolée pour les tests.
Vous devez extraire les requêtes SQL / NoSQL complexes dans leurs propres classes.
Chaque objet Requete est responsable de renvoyer un ensemble de résultats en fonction des critères / règles métier.
Dans cet exemple, nous n'avons aucune requête ( requete ) complexe, alors utilisez object Requete ce ne serait pas efficace. Cependant, à des fins de démonstration, nous allons extraire la requête dans Report::GenerateWeekly#call
et nous créerons generate_entries_query.rb
:
Et dans Report::GenerateWeekly#call
, remplaçons:
def call @user.entries.group_by(&:week).map do |week, entries| WeeklyReport.new( ... ) end end
avec:
def call weekly_grouped_entries = GroupEntriesQuery.new(@user).call weekly_grouped_entries.map do |week, entries| WeeklyReport.new( ... ) end end
Le motif de l'objet requete (query) permet de garder la logique de votre modèle strictement liée au comportement de classe, tout en gardant vos contrôleurs maigres. Parce qu'ils ne sont rien d'autre que classes de rubis anciennes , les objets requete ils n'ont pas besoin d'hériter de ActiveRecord::Base
et ne devraient être responsables que de l'exécution des requêtes.
Nous allons maintenant extraire la logique de création d'une nouvelle entrée dans un nouvel objet de service. Utilisons la convention et créons CreateEntry
:
Et maintenant notre EntriesController#create
est comme suit:
def create begin CreateEntry.new(current_user, entry_params).call flash[:notice] = 'Entry was successfully created.' rescue Exception => e flash[:error] = e.message end redirect_to root_path end
Maintenant, les choses commencent à devenir plus intéressantes.
N'oubliez pas que dans nos directives, nous avons convenu que nous voulions que les modèles aient des associations et des constantes, mais rien d'autre (pas de validations ni d'appels). Commençons donc par supprimer les légendes et utilisons à la place un objet Shape.
Un objet Shape est un PORO (Plain Old Ruby Object). Prenez les commandes de l'objet contrôleur / service lorsque vous avez besoin de parler à la base de données.
Pourquoi utiliser des objets Shape?
Lorsque vous avez besoin de refactoriser votre application, il est toujours judicieux de garder à l'esprit la principale responsabilité ( FAUCILLE ).
FAUCILLE vous aide à prendre de meilleures décisions en matière de conception, concernant la responsabilité qu'une classe devrait avoir.
Votre modèle de table de base de données (un modèle ActiveRecord dans le contexte de Rails), par exemple, représente un enregistrement de base de données unique dans le code, il n'y a donc aucune raison pour que vous soyez préoccupé par ce que fait votre utilisateur.
C'est là que l'objet Shape entre en jeu.
Un objet Shape est chargé de représenter une forme dans votre application. Ainsi, chaque champ d'entrée peut être traité comme un attribut de la classe. Vous pouvez valider que ces attributs répondent à certaines règles de validation, et vous pouvez transmettre les données 'propres' là où elles devraient aller (par exemple, votre modèle de base de données ou peut-être votre générateur de recherche de requêtes).
Quand devez-vous utiliser un objet Shape?
Cela vous permet de mettre toute votre logique de formulaire (conventions de dénomination, validations et autres) au même endroit.
Comment créer un objet Shape?
ActiveModel::Model
(dans Rails 3, vous devez inclure le nom, la conversion et la validation à la place).Veuillez noter que vous pouvez utiliser le gemme réforme , mais en continuant avec PORO, nous allons créer entry_form.rb
qui ressemble à ceci:
Et nous modifierons CreateEntry
pour commencer à utiliser l'objet Format EntryForm
:
class CreateEntry ...... ...... def call @entry_form = ::EntryForm.new(@params) if @entry_form.valid? .... else .... end end end
Remarque: Certains d'entre vous diront qu'il n'est pas nécessaire d'accéder à l'objet Shape depuis l'objet Service et que nous pouvons appeler l'objet Shape directement depuis le contrôleur, ce qui est un argument valide. Cependant, je préfère avoir un flux clair, c'est pourquoi j'appelle toujours l'objet Form à partir de l'objet Service.
Comme nous l'avons convenu précédemment, nous ne voulons pas que nos modèles contiennent des validations et des appels. Nous avons extrait les validations à l'aide d'objets Shape. Mais nous utilisons toujours des appels (after_create
dans le modèle Entry
compare_speed_and_notify_user
).
Pourquoi voulons-nous supprimer les appels des modèles?
Développeurs de rails ils commencent généralement à remarquer une douleur avec les appels, pendant les tests. Si vous ne testez pas vos modèles ActiveRecord, vous commencerez à remarquer la douleur plus tard, à mesure que votre application se développe et que plus de logique est nécessaire pour appeler ou éviter les appels.
después_*
les appels sont principalement utilisés en relation avec l'enregistrement ou la poursuite de l'objet.
Une fois l'objet enregistré, l'objectif de l'objet (par exemple la responsabilité) a été rempli. Donc, si nous voyons toujours des appels appelés, après que l'objet a été enregistré, ce sont probablement des appels qui cherchent à sortir de la zone de responsabilité de l'objet, et c'est là que nous rencontrons des problèmes.
didacticiel de l'API Web de base asp.net
Dans notre cas, nous envoyons un SMS à l'utilisateur, qui n'est pas lié au domaine d'entrée.
Un moyen simple de résoudre le problème consiste à déplacer l'appel vers l'objet de service associé. Après tout, l'envoi d'un SMS à l'utilisateur correspondant est lié à l'objet de service CreateEntry
et non le modèle Entry en tant que tel.
En faisant cela, nous n'avons plus à arrêter, le compare_speed_and_notify_user
dans nos tests. Nous en avons fait une chose simple, en créant une entrée sans avoir besoin d'envoyer un SMS et nous suivons une bonne conception orientée objet, en veillant à ce que nos classes aient une responsabilité unique ( FAUCILLE ).
Alors maintenant CreateEntry
c'est quelque chose de similaire à ceci:
Bien que nous puissions facilement utiliser la collection Draper de mannequins et décorateurs, je reste avec PORO, pour cet article, comme je l'ai fait jusqu'à présent.
Ce dont j'ai besoin, c'est d'une classe qui appelle des méthodes sur l'objet décoré.
Je peux utiliser method_missing
pour l'implémenter, mais j'utiliserai la bibliothèque Ruby standard, SimpleDelegator
. Le code suivant montre comment utiliser SimpleDelegator
pour implémenter notre décorateur de base:
% app/decorators/base_decorator.rb require 'delegate' class BaseDecorator Pourquoi la méthode _h
?
Cette méthode agit comme un proxy pour le contexte de vue. Par défaut, le contexte de vue est une instance d'une classe de vue, ceci étant ActionView::Base
. Vous pouvez accéder au aides de vues comme suit:
_h.content_tag :div, 'my-div', class: 'my-class'
Pour le rendre plus pratique, nous ajoutons une méthode decorado
a ApplicationHelper
:
module ApplicationHelper # ..... def decorate(object, klass = nil) klass ||= '#{object.class}Decorator'.constantize decorator = klass.new(object, self) yield decorator if block_given? decorator end # ..... end
Maintenant, nous pouvons déplacer le aides EntriesHelper
aux décorateurs:
# app/decorators/entry_decorator.rb class EntryDecorator Et nous pouvons utiliser readable_time_period
et readable_speed
comme suit:
# app/views/entries/_entry.html.erb - +
- +
Structure après refactorisation
Nous nous sommes retrouvés avec plus de fichiers, mais ce n'est pas forcément une mauvaise chose (et rappelez-vous ceci, depuis le début, nous étions conscients que cet exemple était à des fins de démonstration et non était forcément un bon cas d'utilisation pour le refactoring):
app ├── assets │ └── ... ├── controllers │ ├── application_controller.rb │ ├── entries_controller.rb │ └── statistics_controller.rb ├── decorators │ ├── base_decorator.rb │ └── entry_decorator.rb ├── forms │ └── entry_form.rb ├── helpers │ └── application_helper.rb ├── mailers ├── models │ ├── entry.rb │ ├── entry_status.rb │ └── user.rb ├── queries │ └── group_entries_query.rb ├── services │ ├── create_entry.rb │ └── report │ └── generate_weekly.rb └── views ├── devise │ └── .. ├── entries │ ├── _entry.html.erb │ ├── _form.html.erb │ └── index.html.erb ├── layouts │ └── application.html.erb └── statistics └── index.html.erb
conclusion
Bien que nous nous concentrions sur les rails dans cet article de blog, RoR (Ruby on Rails) n'est pas une dépendance sur des objets de service ou d'autres PORO. Vous pouvez utiliser cette approche avec n'importe quel cadre web , application mobile ou console.
Lors de l'utilisation MVC En tant qu'architecture, tout colle ensemble et vous ralentit car la plupart des changements ont un impact sur d'autres parties de l'application. Cela vous oblige également à réfléchir à l'endroit où placer la logique métier - doit-elle aller dans le modèle, le contrôleur ou la vue?
En utilisant un simple PORO, nous avons déplacé la logique métier vers des modèles ou des services, qui n'héritent pas de ActiveRecord
, ce qui est déjà un gain, sans oublier que nous avons un code plus clair, qui prend en charge FAUCILLE et des tests unitaires plus rapides.
Une architecture propre essaie de placer les boîtes d'utilisation au centre / en haut de votre structure afin que vous puissiez facilement voir ce que fait votre application. Elle facilite également l'adoption des changements, car elle est plus modulaire et isolée. J'espère avoir montré comment Objets rubis anciens simples et plus d'abstractions, il sépare les préoccupations, simplifie les tests et aide à produire un code propre et maintenable.
En relation: Quels sont les avantages de Ruby on Rails? Après deux décennies de programmation. Utiliser des rails