edge
Plus sur rubyonrails.org: Plus de Ruby on Rails

Création et personnalisation des générateurs et des modèles Rails

Les générateurs Rails sont un outil essentiel pour améliorer votre flux de travail. Avec ce guide, vous apprendrez comment créer des générateurs et personnaliser ceux existants.

Après avoir lu ce guide, vous saurez :

1 Premier contact

Lorsque vous créez une application en utilisant la commande rails, vous utilisez en réalité un générateur Rails. Ensuite, vous pouvez obtenir une liste de tous les générateurs disponibles en invoquant bin/rails generate :

$ rails new myapp
$ cd myapp
$ bin/rails generate

NOTE : Pour créer une application Rails, nous utilisons la commande globale rails qui utilise la version de Rails installée via gem install rails. Lorsque vous êtes dans le répertoire de votre application, nous utilisons la commande bin/rails qui utilise la version de Rails fournie avec l'application.

Vous obtiendrez une liste de tous les générateurs fournis avec Rails. Pour voir une description détaillée d'un générateur particulier, invoquez le générateur avec l'option --help. Par exemple :

$ bin/rails generate scaffold --help

2 Création de votre premier générateur

Les générateurs sont construits sur Thor, qui offre de puissantes options pour l'analyse et une excellente API pour la manipulation des fichiers.

Créons un générateur qui crée un fichier d'initialisation nommé initializer.rb dans config/initializers. La première étape consiste à créer un fichier à lib/generators/initializer_generator.rb avec le contenu suivant :

class InitializerGenerator < Rails::Generators::Base
  def create_initializer_file
    create_file "config/initializers/initializer.rb", <<~RUBY
      # Ajoutez ici le contenu de l'initialisation
    RUBY
  end
end

Notre nouveau générateur est assez simple : il hérite de Rails::Generators::Base et a une définition de méthode. Lorsqu'un générateur est invoqué, chaque méthode publique du générateur est exécutée séquentiellement dans l'ordre où elle est définie. Notre méthode invoque create_file, qui créera un fichier à la destination donnée avec le contenu donné.

Pour invoquer notre nouveau générateur, nous exécutons :

$ bin/rails generate initializer

Avant de continuer, voyons la description de notre nouveau générateur :

$ bin/rails generate initializer --help

Rails est généralement capable de dériver une bonne description si un générateur est regroupé, comme ActiveRecord::Generators::ModelGenerator, mais pas dans ce cas. Nous pouvons résoudre ce problème de deux manières. La première façon d'ajouter une description est d'appeler desc à l'intérieur de notre générateur :

class InitializerGenerator < Rails::Generators::Base
  desc "Ce générateur crée un fichier d'initialisation dans config/initializers"
  def create_initializer_file
    create_file "config/initializers/initializer.rb", <<~RUBY
      # Ajoutez ici le contenu de l'initialisation
    RUBY
  end
end

Maintenant, nous pouvons voir la nouvelle description en invoquant --help sur le nouveau générateur.

La deuxième façon d'ajouter une description est de créer un fichier nommé USAGE dans le même répertoire que notre générateur. Nous allons le faire à l'étape suivante.

3 Création de générateurs avec des générateurs

Les générateurs eux-mêmes ont un générateur. Supprimons notre InitializerGenerator et utilisons bin/rails generate generator pour en générer un nouveau :

$ rm lib/generators/initializer_generator.rb

$ bin/rails generate generator initializer
      create  lib/generators/initializer
      create  lib/generators/initializer/initializer_generator.rb
      create  lib/generators/initializer/USAGE
      create  lib/generators/initializer/templates
      invoke  test_unit
      create    test/lib/generators/initializer_generator_test.rb

Voici le générateur qui vient d'être créé :

class InitializerGenerator < Rails::Generators::NamedBase
  source_root File.expand_path("templates", __dir__)
end

Tout d'abord, remarquez que le générateur hérite de Rails::Generators::NamedBase au lieu de Rails::Generators::Base. Cela signifie que notre générateur attend au moins un argument, qui sera le nom de l'initialiseur et sera disponible dans notre code via name.

Nous pouvons le voir en vérifiant la description du nouveau générateur :

$ bin/rails generate initializer --help
Usage:
  bin/rails generate initializer NAME [options]

Remarquez également que le générateur a une méthode de classe appelée source_root. Cette méthode indique l'emplacement de nos modèles, le cas échéant. Par défaut, elle pointe vers le répertoire lib/generators/initializer/templates qui vient d'être créé.

Pour comprendre comment fonctionnent les modèles de générateur, créons le fichier lib/generators/initializer/templates/initializer.rb avec le contenu suivant :

# Ajoutez ici le contenu de l'initialisation

Et modifions le générateur pour copier ce modèle lorsqu'il est invoqué : ```ruby class InitializerGenerator < Rails::Generators::NamedBase source_root File.expand_path("templates", dir)

def copy_initializer_file copy_file "initializer.rb", "config/initializers/#{file_name}.rb" end end ```

Maintenant, exécutons notre générateur :

$ bin/rails generate initializer core_extensions
      create  config/initializers/core_extensions.rb

$ cat config/initializers/core_extensions.rb
# Ajoutez ici le contenu d'initialisation

Nous voyons que copy_file a créé config/initializers/core_extensions.rb avec le contenu de notre modèle. (La méthode file_name utilisée dans le chemin de destination est héritée de Rails::Generators::NamedBase.)

4 Options de ligne de commande du générateur

Les générateurs peuvent prendre en charge des options de ligne de commande en utilisant class_option. Par exemple :

class InitializerGenerator < Rails::Generators::NamedBase
  class_option :scope, type: :string, default: "app"
end

Maintenant, notre générateur peut être invoqué avec l'option --scope :

$ bin/rails generate initializer theme --scope dashboard

Les valeurs des options sont accessibles dans les méthodes du générateur via options :

def copy_initializer_file
  @scope = options["scope"]
end

5 Résolution du générateur

Lors de la résolution du nom d'un générateur, Rails recherche le générateur en utilisant plusieurs noms de fichiers. Par exemple, lorsque vous exécutez bin/rails generate initializer core_extensions, Rails essaie de charger chacun des fichiers suivants, dans l'ordre, jusqu'à ce qu'un fichier soit trouvé :

  • rails/generators/initializer/initializer_generator.rb
  • generators/initializer/initializer_generator.rb
  • rails/generators/initializer_generator.rb
  • generators/initializer_generator.rb

Si aucun de ces fichiers n'est trouvé, une erreur est levée.

Nous avons placé notre générateur dans le répertoire lib/ de l'application car ce répertoire est dans $LOAD_PATH, ce qui permet à Rails de trouver et de charger le fichier.

6 Remplacement des modèles de générateur de Rails

Rails recherche également dans plusieurs endroits lors de la résolution des fichiers de modèle de générateur. L'un de ces endroits est le répertoire lib/templates/ de l'application. Ce comportement nous permet de remplacer les modèles utilisés par les générateurs intégrés de Rails. Par exemple, nous pourrions remplacer le modèle de contrôleur de scaffold ou les modèles de vue de scaffold.

Pour voir cela en action, créons un fichier lib/templates/erb/scaffold/index.html.erb.tt avec le contenu suivant :

<%% @<%= plural_table_name %>.count %> <%= human_name.pluralize %>

Notez que le modèle est un modèle ERB qui rend un autre modèle ERB. Ainsi, tout <% qui doit apparaître dans le modèle résultant doit être échappé en tant que <%% dans le modèle générateur.

Maintenant, exécutons le générateur de scaffold intégré de Rails :

$ bin/rails generate scaffold Post title:string
      ...
      create      app/views/posts/index.html.erb
      ...

Le contenu de app/views/posts/index.html.erb est :

<% @posts.count %> Posts

7 Remplacement des générateurs de Rails

Les générateurs intégrés de Rails peuvent être configurés via config.generators, y compris le remplacement de certains générateurs entièrement.

Tout d'abord, examinons de plus près le fonctionnement du générateur de scaffold.

$ bin/rails generate scaffold User name:string
      invoke  active_record
      create    db/migrate/20230518000000_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
      invoke  resource_route
       route    resources :users
      invoke  scaffold_controller
      create    app/controllers/users_controller.rb
      invoke    erb
      create      app/views/users
      create      app/views/users/index.html.erb
      create      app/views/users/edit.html.erb
      create      app/views/users/show.html.erb
      create      app/views/users/new.html.erb
      create      app/views/users/_form.html.erb
      create      app/views/users/_user.html.erb
      invoke    resource_route
      invoke    test_unit
      create      test/controllers/users_controller_test.rb
      create      test/system/users_test.rb
      invoke    helper
      create      app/helpers/users_helper.rb
      invoke      test_unit
      invoke    jbuilder
      create      app/views/users/index.json.jbuilder
      create      app/views/users/show.json.jbuilder

À partir de la sortie, nous pouvons voir que le générateur de scaffold invoque d'autres générateurs, tels que le générateur scaffold_controller. Et certains de ces générateurs invoquent également d'autres générateurs. En particulier, le générateur scaffold_controller invoque plusieurs autres générateurs, y compris le générateur helper.

Remplaçons le générateur intégré helper par un nouveau générateur. Nous nommerons le générateur my_helper :

$ bin/rails generate generator rails/my_helper
      create  lib/generators/rails/my_helper
      create  lib/generators/rails/my_helper/my_helper_generator.rb
      create  lib/generators/rails/my_helper/USAGE
      create  lib/generators/rails/my_helper/templates
      invoke  test_unit
      create    test/lib/generators/rails/my_helper_generator_test.rb

Et dans lib/generators/rails/my_helper/my_helper_generator.rb, nous définirons le générateur comme suit :

class Rails::MyHelperGenerator < Rails::Generators::NamedBase
  def create_helper_file
    create_file "app/helpers/#{file_name}_helper.rb", <<~RUBY
      module #{class_name}Helper
        # Je suis utile !
      end
    RUBY
  end
end

Enfin, nous devons indiquer à Rails d'utiliser le générateur my_helper à la place du générateur intégré helper. Pour cela, nous utilisons config.generators. Dans config/application.rb, ajoutons :

config.generators do |g|
  g.helper :my_helper
end

Maintenant, si nous exécutons à nouveau le générateur de scaffold, nous voyons le générateur my_helper en action :

$ bin/rails generate scaffold Article body:text
      ...
      invoke  scaffold_controller
      ...
      invoke    my_helper
      create      app/helpers/articles_helper.rb
      ...

REMARQUE : Vous remarquerez peut-être que la sortie pour le générateur intégré helper inclut "invoke test_unit", tandis que la sortie pour my_helper ne le fait pas. Bien que le générateur helper ne génère pas de tests par défaut, il fournit un crochet pour le faire en utilisant hook_for. Nous pouvons faire de même en incluant hook_for :test_framework, as: :helper dans la classe MyHelperGenerator. Consultez la documentation de hook_for pour plus d'informations.

7.1 Résolutions de générateurs

Une autre façon de remplacer des générateurs spécifiques est d'utiliser des fallbacks. Un fallback permet à un espace de noms de générateur de déléguer à un autre espace de noms de générateur. Par exemple, supposons que nous voulons remplacer le générateur test_unit:model par notre propre générateur my_test_unit:model, mais nous ne voulons pas remplacer tous les autres générateurs test_unit:* tels que test_unit:controller.

Tout d'abord, nous créons le générateur my_test_unit:model dans lib/generators/my_test_unit/model/model_generator.rb :

module MyTestUnit
  class ModelGenerator < Rails::Generators::NamedBase
    source_root File.expand_path("templates", __dir__)

    def do_different_stuff
      say "Doing different stuff..."
    end
  end
end

Ensuite, nous utilisons config.generators pour configurer le générateur test_framework en tant que my_test_unit, mais nous configurons également une solution de repli de sorte que tous les générateurs my_test_unit:* manquants se résolvent en test_unit:* :

config.generators do |g|
  g.test_framework :my_test_unit, fixture: false
  g.fallbacks[:my_test_unit] = :test_unit
end

Maintenant, lorsque nous exécutons le générateur de scaffold, nous constatons que my_test_unit a remplacé test_unit, mais seuls les tests de modèle ont été affectés :

$ bin/rails generate scaffold Comment body:text
      invoke  active_record
      create    db/migrate/20230518000000_create_comments.rb
      create    app/models/comment.rb
      invoke    my_test_unit
    Doing different stuff...
      invoke  resource_route
       route    resources :comments
      invoke  scaffold_controller
      create    app/controllers/comments_controller.rb
      invoke    erb
      create      app/views/comments
      create      app/views/comments/index.html.erb
      create      app/views/comments/edit.html.erb
      create      app/views/comments/show.html.erb
      create      app/views/comments/new.html.erb
      create      app/views/comments/_form.html.erb
      create      app/views/comments/_comment.html.erb
      invoke    resource_route
      invoke    my_test_unit
      create      test/controllers/comments_controller_test.rb
      create      test/system/comments_test.rb
      invoke    helper
      create      app/helpers/comments_helper.rb
      invoke      my_test_unit
      invoke    jbuilder
      create      app/views/comments/index.json.jbuilder
      create      app/views/comments/show.json.jbuilder

8 Modèles d'application

Les modèles d'application sont un type spécial de générateur. Ils peuvent utiliser toutes les méthodes d'aide au générateur, mais sont écrits sous forme de script Ruby plutôt que sous forme de classe Ruby. Voici un exemple :

# template.rb

if yes?("Voulez-vous installer Devise ?")
  gem "devise"
  devise_model = ask("Comment souhaitez-vous appeler le modèle utilisateur ?", default: "User")
end

after_bundle do
  if devise_model
    generate "devise:install"
    generate "devise", devise_model
    rails_command "db:migrate"
  end

  git add: ".", commit: %(-m 'Commit initial')
end

Tout d'abord, le modèle demande à l'utilisateur s'il souhaite installer Devise. Si l'utilisateur répond "oui" (ou "o"), le modèle ajoute Devise au Gemfile et demande à l'utilisateur le nom du modèle utilisateur Devise (par défaut User). Plus tard, après l'exécution de bundle install, le modèle exécutera les générateurs Devise et rails db:migrate si un modèle Devise a été spécifié. Enfin, le modèle effectuera un git add et un git commit de l'ensemble du répertoire de l'application.

Nous pouvons exécuter notre modèle lors de la génération d'une nouvelle application Rails en passant l'option -m à la commande rails new :

$ rails new my_cool_app -m path/to/template.rb

Alternativement, nous pouvons exécuter notre modèle dans une application existante avec bin/rails app:template :

$ bin/rails app:template LOCATION=path/to/template.rb

Les modèles n'ont pas besoin d'être stockés localement - vous pouvez spécifier une URL à la place d'un chemin :

$ rails new my_cool_app -m http://example.com/template.rb
$ bin/rails app:template LOCATION=http://example.com/template.rb

9 Méthodes d'aide au générateur

Thor fournit de nombreuses méthodes d'aide au générateur via Thor::Actions, telles que :

En plus de celles-ci, Rails fournit également de nombreuses méthodes d'aide via Rails::Generators::Actions, telles que :

Retour d'information

Vous êtes encouragé à contribuer à l'amélioration de la qualité de ce guide.

Veuillez contribuer si vous trouvez des fautes de frappe ou des erreurs factuelles. Pour commencer, vous pouvez lire notre contribution à la documentation section.

Vous pouvez également trouver du contenu incomplet ou des informations qui ne sont pas à jour. Veuillez ajouter toute documentation manquante pour la version principale. Assurez-vous de vérifier Edge Guides d'abord pour vérifier si les problèmes ont déjà été résolus ou non sur la branche principale. Consultez les Directives des guides Ruby on Rails pour le style et les conventions.

Si pour une raison quelconque vous repérez quelque chose à corriger mais ne pouvez pas le faire vous-même, veuillez ouvrir un problème.

Et enfin, toute discussion concernant la documentation de Ruby on Rails est la bienvenue sur le Forum officiel de Ruby on Rails.