Parfois, les clients nous font des demandes de fonctionnalités que nous n'aimons vraiment pas. Ce n’est pas que nous n’aimons pas nos clients, nous aimons nos clients. Ce n'est pas que nous n'aimions pas cette fonctionnalité: la plupart des fonctionnalités demandées par les clients sont parfaitement alignées sur leurs objectifs commerciaux et leurs revenus.
Parfois, nous n'aimons pas une demande de fonctionnalité, car le moyen le plus simple de la résoudre est d'écrire du mauvais code, et nous n'avons pas de solution élégante en tête. Cela enverra beaucoup d'entre nous Développeurs Rails sur des recherches infructueuses à travers RubyToolbox , GitHub , blogs de développeurs et StackOverflow à la recherche d'un bijou, d'un plugin ou d'un exemple de code qui nous fera nous sentir mieux dans notre peau.
Eh bien, je suis ici pour vous dire: il est normal d’écrire du mauvais code. Parfois, un mauvais code Rails est plus facile à refactoriser en beau code qu'une solution mal pensée implémentée dans un temps limité.
C'est le processus de refactoring de Rails que j'aime suivre pour éliminer les problèmes de mes horribles solutions de pansement:
Pour une autre perspective, voici le Journal de validation Git pour une fonctionnalité qui a été refactorisée étape par étape:
Et voici un autre article intéressant sur refactoring à grande échelle d'un collègue du réseau ApeeScape.
Voyons comment cela se fait.
Étape 1. Commencez dans les vues
Disons que nous commençons un ticket pour une nouvelle fonctionnalité. Le client nous dit: 'Les visiteurs doivent pouvoir afficher une liste des projets actifs sur la page d'accueil.'
Ce ticket nécessite un changement visible, donc un endroit raisonnable pour commencer le travail serait dans les vues. Le problème est simple et nous avons tous été formés pour le résoudre plusieurs fois. Je vais le résoudre La mauvaise direction et montrer comment refactoriser ma solution dans ses domaines appropriés. Résoudre un problème La mauvaise direction peut nous aider à surmonter le problème de ne pas connaître la bonne solution.
Pour commencer, supposons que nous ayons un modèle appelé Project
avec un attribut booléen appelé active
. Nous voulons obtenir une liste de tous Projects
où active
est égal à true
, donc nous pouvons utiliser Project.where(active: true)
, et boucler dessus avec un each
bloquer.
app/views/pages/welcome.haml: %ul.projects - Project.where(active: true).each do |project| %li.project= link_to project_path(project), project.name
Je sais ce que vous dites: 'Cela ne passerait jamais une révision de code' ou 'Mon client me licencierait sûrement pour cela.' Oui, cette solution rompt la séparation des problèmes Modèle-Vue-Contrôleur, cela peut entraîner des appels de base de données parasites difficiles à tracer et il peut devenir difficile à maintenir à l'avenir. Mais considérez la valeur de le faire La mauvaise direction :
Étape 2. Partiels
Après l'avoir fait La mauvaise direction , Je me sens mal dans ma peau et je veux isoler mon mauvais code. Si ce changement n'était clairement qu'une préoccupation du calque Vue, je pourrais refactoriser ma honte en un partiel.
app/views/pages/welcome.haml: = render :partial => 'shared/projects_list' app/views/shared/projects_list.haml: %ul.projects - Project.where(active: true).each do |project| %li.project= link_to project_path(project), project.name
C’est un peu mieux. De toute évidence, nous faisons toujours l'erreur d'une requête de modèle dans une vue, mais au moins lorsqu'un responsable arrive plus tard et voit mon horrible partiel, il aura un moyen simple de s'attaquer à ce problème de code Rails en particulier. Corriger quelque chose de évidemment stupide est toujours plus facile que de corriger une abstraction boguée mal implémentée.
Corriger quelque chose de évidemment stupide est toujours plus facile que de corriger une abstraction boguée mal implémentée. TweetÉtape 3. Aides
Les Helpers in Rails sont un moyen de créer un DSL (langage spécifique au domaine) pour une section de vos vues. Vous devez réécrire votre code en utilisant content_tag au lieu de hameau ou HTML, mais vous bénéficiez de la possibilité de manipuler les structures de données sans que les autres développeurs vous regardent pendant 15 lignes de code View non imprimable.
Si je devais utiliser des helpers ici, je refactoriserais probablement le li
marque.
app/views/shared/projects_list.haml: %ul.projects - Project.where(active: true).each do |project| = project_list_item(project) app/helpers/projects_helper.rb: def project_list_item(project) content_tag(:li, :class => 'project') do link_to project_path(project), project.name end end
Étape 4. Déplacez-le vers les contrôleurs
Peut-être que votre code horrible n'est pas seulement un problème de vue. Si votre code sent toujours, recherchez les requêtes que vous pouvez passer des vues aux contrôleurs.
app/views/shared/projects_list.haml: %ul.projects - @projects_list.each do |project| = project_list_item(project) app/controllers/pages_controller.rb: def welcome @projects = Project.where(active: true) end
Étape 5. Filtres du contrôleur
La raison la plus évidente de déplacer du code dans un contrôleur before_filter
ou after_filter
est pour le code que vous avez dupliqué dans plusieurs actions du contrôleur. Vous pouvez également déplacer le code dans un Filtre contrôleur si vous souhaitez séparer le but de l'action du contrôleur des exigences de vos vues.
app/controllers/pages_controller.rb: before_filter :projects_list def welcome end def projects_list @projects = Project.where(active:true) end
Étape 6. Contrôleur d'application
Supposons que vous ayez besoin que votre code apparaisse sur chaque page, ou que vous souhaitiez rendre les fonctions d'assistance du contrôleur disponibles pour tous les contrôleurs, vous pouvez déplacer votre fonction dans ApplicationController. Si les modifications sont globales, vous pouvez également modifier la disposition de votre application.
app/controllers/pages_controller.rb: def welcome end app/views/layouts/application.haml: %ul.projects - projects_list.each do |project| = project_list_item(project) app/controllers/application_controller.rb: before_filter :projects_list def projects_list @projects = Project.where(active: true) end
Étape 7. Refactoring au modèle
Comme le dit la devise MVC: Modèle gras, contrôleurs skinny et vues stupides . Nous sommes censés refactoriser tout ce que nous pouvons dans le modèle, et il est vrai que les fonctionnalités les plus complexes deviendront éventuellement des associations de modèles et des méthodes de modèle. Nous devons toujours éviter de mettre en forme / visualiser des choses dans le modèle, mais la transformation de données en d'autres types de données est autorisée. Dans ce cas, la meilleure chose à refactoriser dans le modèle serait le where(active: true)
clause, que nous pouvons transformer en champ d'application. Utiliser une portée est utile non seulement parce que cela rend l'appel plus joli, mais si nous décidons d'ajouter un attribut comme delete
ou outdated
, nous pouvons modifier cette portée au lieu de traquer tous nos where
clauses.
app/controllers/application_controller.rb: before_filter :projects_list def projects_list @projects = Project.active end app/models/project.rb: scope :active, where(active: true)
Étape 8. Filtres de modèle
Nous n’avons pas d’utilisation particulière pour un modèle before_save
ou after_save filters
dans ce cas, mais l'étape suivante que je prends habituellement est de déplacer les fonctionnalités des contrôleurs et des méthodes de modèle vers les filtres de modèle.
Supposons que nous ayons un autre attribut, num_views
. Si num_views > 50
, le projet devient inactif. Nous pourrions résoudre ce problème dans la vue, mais apporter des modifications à la base de données dans une vue est inapproprié. Nous pourrions le résoudre dans le contrôleur, mais nos contrôleurs doivent être aussi minces que possible! Nous pouvons le résoudre facilement dans le modèle.
s corp contre c corp contre llc
app/models/project.rb: before_save :deactivate_if_over_num_views def deactivate_if_over_num_views if num_views > 50 self.active = false fi end
Remarque: évitez d'appeler self.save
dans un filtre de modèle, car cela provoque des événements de sauvegarde récursifs, et la couche de manipulation de base de données de votre application doit de toute façon être le contrôleur.
Étape 9. Bibliothèques
Parfois, votre fonctionnalité est suffisamment volumineuse pour justifier sa propre bibliothèque. Vous souhaiterez peut-être le déplacer dans un fichier de bibliothèque, car il est réutilisé dans de nombreux endroits, ou il est suffisamment volumineux pour que vous souhaitiez en développer séparément.
Vous pouvez stocker des fichiers de bibliothèque dans le lib / répertoire, mais au fur et à mesure qu'ils grandissent, vous pouvez les transférer dans un véritable RubyGem ! Un avantage majeur du déplacement de votre code dans une bibliothèque est que vous pouvez tester la bibliothèque séparément de votre modèle.
Quoi qu'il en soit, dans le cas d'une liste de projets, nous pourrions justifier le déplacement du scope :active
appel depuis le Project
model dans un fichier de bibliothèque et le ramener dans Ruby:
app/models/project.rb: class Project 50 self.active = false end end end
Remarque: le self.included
est appelée lorsqu'une classe de modèle Rails est chargée et transmet la portée de la classe en tant que variable k
.
Dans ce didacticiel de refactorisation Ruby on Rails, nous avons pris moins de 15 minutes et mis en œuvre une solution et l'avons mise en préparation pour les tests utilisateur, prête à être acceptée dans l'ensemble de fonctionnalités ou supprimée. À la fin du processus de refactorisation, nous avons un morceau de code qui définit un cadre pour la mise en œuvre d'éléments pouvant être listés et activés sur plusieurs modèles qui passeraient même le processus d'examen le plus strict.
Dans votre propre processus de refactoring de Rails, n'hésitez pas à sauter quelques étapes dans le pipeline si vous êtes sûr de le faire (par exemple, passer de la vue au contrôleur ou du contrôleur au modèle). Gardez simplement à l'esprit que le flux de code va de la vue au modèle.
N'ayez pas peur de paraître stupide. Ce qui distingue les langages modernes des anciennes applications de rendu de modèles CGI, ce n’est pas que nous faisons tout Le droit chemin à chaque fois - c'est que nous prenons le temps de refactoriser, réutiliser et partager nos efforts.
En relation: Timestamp Truncation: A Ruby on Rails ActiveRecord Tale