web-dev-qa-db-fra.com

OO Conception dans Rails: Où placer des éléments

J'apprécie vraiment Rails (même si je suis généralement sans RESTless), et j'apprécie Ruby étant très OO. Néanmoins, la tendance à faire d'ActiveRecord énorme Les sous-classes et les énormes contrôleurs sont assez naturels (même si vous utilisez un contrôleur par ressource). Si vous deviez créer des mondes objets plus profonds, où mettriez-vous les classes (et les modules, je suppose)? les assistants eux-mêmes?), contrôleurs et modèles.

Lib va ​​bien, et j'ai trouvé quelques solutions pour le recharger dans un environnement de développement , mais j'aimerais savoir s'il existe une meilleure façon de faire cela. Je suis vraiment juste préoccupé par le fait que les classes deviennent trop grandes. Aussi, qu'en est-il des moteurs et comment s'intègrent-ils?

241
Dan Rosenstark

Parce que Rails fournit une structure en termes de MVC, il est naturel de finir par utiliser niquement les conteneurs de modèles, de vues et de contrôleurs fournis. L'idiome typique de Les débutants (et même certains programmeurs intermédiaires) doivent intégrer toute la logique de l'application dans le modèle (classe de base de données), le contrôleur ou la vue.

À un moment donné, quelqu'un fait remarquer le paradigme "modèle lourd, contrôleur maigre", et les développeurs intermédiaires extraient à la hâte le contenu de leurs contrôleurs et le jettent dans le modèle, qui commence à devenir une nouvelle poubelle pour la logique d'application.

Les contrôleurs maigres sont, en fait, une bonne idée, mais le corollaire - tout mettre dans le modèle, n’est pas vraiment le meilleur plan.

Dans Ruby, vous avez plusieurs bonnes options pour rendre les choses plus modulaires. Une solution assez répandue consiste à utiliser simplement des modules (généralement stockés dans lib) contenant des groupes de méthodes, puis à les inclure dans les classes appropriées. Cela est utile dans les cas où vous souhaitez réutiliser des catégories de fonctionnalités dans plusieurs classes, mais dans lequel les fonctionnalités sont toujours liées de manière théorique aux classes.

Souvenez-vous que lorsque vous incluez un module dans une classe, les méthodes deviennent des méthodes d'instance de la classe. Vous obtenez donc toujours une classe contenant un ton méthodes, elles sont simplement organisées en plusieurs fichiers. .

Cette solution peut bien fonctionner dans certains cas. Dans d'autres cas, vous allez vouloir utiliser des classes dans votre code qui sont non modèles, vues ou contrôleurs.

Une bonne façon de penser à cela est le "principe de responsabilité unique", qui dit qu'une classe devrait être responsable d'un seul (ou d'un petit nombre) de choses. Vos modèles sont responsables de la persistance des données de votre application dans la base de données. Vos contrôleurs sont chargés de recevoir une demande et de renvoyer une réponse viable.

Si vous avez des concepts qui ne rentrent pas parfaitement dans ces cases (gestion de la persistance, des requêtes/réponses), vous voudrez probablement réfléchir à la façon dont vous voudriez modéliser l'idée en question. Vous pouvez stocker des classes non-modèles dans app/classes ou ailleurs, et ajouter ce répertoire à votre chemin de chargement en procédant comme suit:

config.load_paths << File.join(Rails.root, "app", "classes")

Si vous utilisez passagers ou JRuby, vous voudrez probablement aussi ajouter votre chemin aux chemins de chargement désireux:

config.eager_load_paths << File.join(Rails.root, "app", "classes")

En fin de compte, une fois que vous arrivez à un point dans Rails où vous vous posez cette question, il est temps de renforcer vos côtelettes Ruby et commencez classes de modélisation qui ne sont pas que les classes MVC que Rails vous donne par défaut.

Mise à jour: Cette réponse s'applique à Rails 2.x et versions supérieures.

378
Yehuda Katz

Update : L'utilisation de Préoccupations a été confirmée comme nouvelle valeur par défaut dans Rails 4 .

Cela dépend vraiment de la nature du module lui-même. Je place généralement les extensions de contrôleur/modèle dans un dossier/concerne dans l'application.

# concerns/authentication.rb
module Authentication
  ...
end    

# controllers/application_controller.rb
class ApplicationController
  include Authentication
end



# concerns/configurable.rb
module Configurable
  ...
end    

class Model 
  include Indexable
end 

# controllers/foo_controller.rb
class FooController < ApplicationController
  include Indexable
end

# controllers/bar_controller.rb
class BarController < ApplicationController
  include Indexable
end

/ lib est mon choix préféré pour les bibliothèques à usage général. J'ai toujours un espace de noms de projet dans lib où je mets toutes les bibliothèques spécifiques à l'application.

/lib/myapp.rb
module MyApp
  VERSION = ...
end

/lib/myapp/CacheKey.rb
/lib/myapp/somecustomlib.rb

Les extensions principales Ruby/Rails ont généralement lieu dans les initialiseurs de configuration, de sorte que les bibliothèques ne sont chargées qu'une seule fois sur Rails boostrap.

/config/initializer/config.rb
/config/initializer/core_ext/string.rb
/config/initializer/core_ext/array.rb

Pour les fragments de code réutilisables, je crée souvent des (micro) plugins afin de pouvoir les réutiliser dans d'autres projets.

Les fichiers auxiliaires contiennent généralement des méthodes auxiliaires et parfois des classes lorsque l'objet est destiné à être utilisé par des assistants (par exemple, les générateurs de formulaire).

Ceci est un aperçu vraiment général. Veuillez fournir plus de détails sur des exemples spécifiques si vous souhaitez obtenir des suggestions plus personnalisées. :)

62
Simone Carletti

... la tendance à créer d'énormes sous-classes ActiveRecord et de grands contrôleurs est tout à fait naturelle ...

"énorme" est un mot inquiétant ... ;-)

Comment vos contrôleurs deviennent-ils énormes? C'est quelque chose que vous devriez regarder: idéalement, les contrôleurs devraient être minces. En choisissant une règle empirique, je suggérerais que si vous avez régulièrement plus de, disons, 5 ou 6 lignes de code par méthode de contrôleur (action), vos contrôleurs sont probablement trop gros. Y a-t-il une duplication qui pourrait passer à une fonction d'assistance ou à un filtre? Existe-t-il une logique métier qui pourrait être intégrée dans les modèles?

Comment vos modèles deviennent-ils énormes? Devriez-vous chercher des moyens de réduire le nombre de responsabilités dans chaque classe? Existe-t-il des comportements courants que vous pouvez extraire dans mixins? Ou des zones de fonctionnalités que vous pouvez déléguer à des classes auxiliaires?

EDIT: J'essaie de développer un peu, en espérant ne pas trop déformer quoi que ce soit ...

Helpers: vivent à app/helpers et sont principalement utilisés pour simplifier les vues. Ils sont spécifiques au contrôleur (également disponibles pour toutes les vues de ce contrôleur) ou généralement disponibles (module ApplicationHelper dans application_helper.rb).

Filtres: Disons que vous avez la même ligne de code dans plusieurs actions (assez souvent, la récupération d'un objet en utilisant params[:id] ou similaire). Cette duplication peut être résumée d’abord en une méthode distincte, puis extraite entièrement des actions en déclarant un filtre dans la définition de classe, tel que before_filter :get_object. Voir la section 6 du ActionController Rails Guide ) Laissez la programmation déclarative devenir votre ami.

Les modèles de refactoring sont un peu plus religieux. Les disciples de Oncle Bob suggéreront, par exemple, de suivre les cinq commandements de SOLIDE . Joel et Jeff peut recommander une approche plus, euh, "pragmatique", bien qu'ils semblaient être une n peu plus réconciliée par la suite. Trouver une ou plusieurs méthodes dans une classe qui fonctionnent sur un sous-ensemble clairement défini de ses attributs est un moyen d'essayer d'identifier les classes susceptibles d'être refactorisées à partir de votre modèle dérivé d'ActiveRecord.

Les modèles Rails ne doivent pas nécessairement être des sous-classes de ActiveRecord :: Base. Autrement dit, un modèle ne doit pas nécessairement être analogue à une table, ni même être lié à quoi que ce soit stocké. Mieux encore, tant que vous nommez votre fichier dans app/models selon les conventions de Rails (appelez #underscore sur le nom de la classe pour trouver ce que Rails cherchera)], Rails le trouvera sans aucun requires étant nécessaire.

10
Mike Woodhouse

Voici un excellent article de blog sur la refactorisation des gros modèles qui semblent découler de la philosophie des "contrôleurs minces":

http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/

Le message de base est "Ne pas extraire les mixins des modèles Fat", utilisez plutôt les classes de service, l'auteur fournit 7 modèles pour le faire.

1
bbozo