1 Primer Contacto
Cuando creas una aplicación usando el comando rails
, de hecho estás usando un generador de Rails. Después de eso, puedes obtener una lista de todos los generadores disponibles invocando bin/rails generate
:
$ rails new myapp
$ cd myapp
$ bin/rails generate
NOTA: Para crear una aplicación de Rails usamos el comando global rails
que utiliza la versión de Rails instalada a través de gem install rails
. Cuando estamos dentro del directorio de tu aplicación, usamos el comando bin/rails
que utiliza la versión de Rails incluida con la aplicación.
Obtendrás una lista de todos los generadores que vienen con Rails. Para ver una descripción detallada de un generador en particular, invoca el generador con la opción --help
. Por ejemplo:
$ bin/rails generate scaffold --help
2 Creando Tu Primer Generador
Los generadores están construidos sobre Thor, que proporciona opciones poderosas para el análisis y una gran API para manipular archivos.
Vamos a construir un generador que crea un archivo inicializador llamado initializer.rb
dentro de config/initializers
. El primer paso es crear un archivo en lib/generators/initializer_generator.rb
con el siguiente contenido:
class InitializerGenerator < Rails::Generators::Base
def create_initializer_file
create_file "config/initializers/initializer.rb", <<~RUBY
# Añade contenido de inicialización aquí
RUBY
end
end
Nuestro nuevo generador es bastante simple: hereda de Rails::Generators::Base
y tiene una definición de método. Cuando se invoca un generador, cada método público en el generador se ejecuta secuencialmente en el orden en que está definido. Nuestro método invoca create_file
, que creará un archivo en el destino dado con el contenido dado.
Para invocar nuestro nuevo generador, ejecutamos:
$ bin/rails generate initializer
Antes de continuar, veamos la descripción de nuestro nuevo generador:
$ bin/rails generate initializer --help
Rails generalmente puede derivar una buena descripción si un generador está en un espacio de nombres, como ActiveRecord::Generators::ModelGenerator
, pero no en este caso. Podemos resolver este problema de dos maneras. La primera forma de agregar una descripción es llamando a desc
dentro de nuestro generador:
class InitializerGenerator < Rails::Generators::Base
desc "Este generador crea un archivo inicializador en config/initializers"
def create_initializer_file
create_file "config/initializers/initializer.rb", <<~RUBY
# Añade contenido de inicialización aquí
RUBY
end
end
Ahora podemos ver la nueva descripción invocando --help
en el nuevo generador.
La segunda forma de agregar una descripción es creando un archivo llamado USAGE
en el mismo directorio que nuestro generador. Vamos a hacer eso en el siguiente paso.
3 Creando Generadores con Generadores
Los generadores en sí mismos tienen un generador. Vamos a eliminar nuestro InitializerGenerator
y usar bin/rails generate generator
para generar uno nuevo:
$ 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
Este es el generador recién creado:
class InitializerGenerator < Rails::Generators::NamedBase
source_root File.expand_path("templates", __dir__)
end
Primero, nota que el generador hereda de Rails::Generators::NamedBase
en lugar de Rails::Generators::Base
. Esto significa que nuestro generador espera al menos un argumento, que será el nombre del inicializador y estará disponible para nuestro código a través de name
.
Podemos ver eso verificando la descripción del nuevo generador:
$ bin/rails generate initializer --help
Usage:
bin/rails generate initializer NAME [options]
Además, nota que el generador tiene un método de clase llamado source_root
. Este método apunta a la ubicación de nuestras plantillas, si las hay. Por defecto, apunta al directorio lib/generators/initializer/templates
que acaba de ser creado.
Para entender cómo funcionan las plantillas de generador, vamos a crear el archivo lib/generators/initializer/templates/initializer.rb
con el siguiente contenido:
# Añade contenido de inicialización aquí
Y vamos a cambiar el generador para copiar esta plantilla cuando se invoque:
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
Ahora ejecutemos nuestro generador:
$ bin/rails generate initializer core_extensions
create config/initializers/core_extensions.rb
$ cat config/initializers/core_extensions.rb
# Añade contenido de inicialización aquí
Vemos que copy_file
creó config/initializers/core_extensions.rb
con el contenido de nuestra plantilla. (El método file_name
usado en la ruta de destino se hereda de Rails::Generators::NamedBase
).
4 Opciones de Línea de Comando del Generador
Los generadores pueden soportar opciones de línea de comando usando class_option
. Por ejemplo:
class InitializerGenerator < Rails::Generators::NamedBase
class_option :scope, type: :string, default: "app"
end
Ahora nuestro generador puede ser invocado con una opción --scope
:
$ bin/rails generate initializer theme --scope dashboard
Los valores de opción son accesibles en los métodos del generador a través de options
:
def copy_initializer_file
@scope = options["scope"]
end
5 Resolución del Generador
Al resolver el nombre de un generador, Rails busca el generador usando múltiples nombres de archivo. Por ejemplo, cuando ejecutas bin/rails generate initializer core_extensions
, Rails intenta cargar cada uno de los siguientes archivos, en orden, hasta que se encuentra uno:
rails/generators/initializer/initializer_generator.rb
generators/initializer/initializer_generator.rb
rails/generators/initializer_generator.rb
generators/initializer_generator.rb
Si ninguno de estos se encuentra, se generará un error.
Colocamos nuestro generador en el directorio lib/
de la aplicación porque ese directorio está en $LOAD_PATH
, permitiendo así que Rails encuentre y cargue el archivo.
6 Sobrescribiendo Plantillas de Generadores de Rails
Rails también buscará en múltiples lugares al resolver archivos de plantilla de generador. Uno de esos lugares es el directorio lib/templates/
de la aplicación. Este comportamiento nos permite sobrescribir las plantillas utilizadas por los generadores integrados de Rails. Por ejemplo, podríamos sobrescribir la plantilla del controlador scaffold o las plantillas de vistas scaffold.
Para ver esto en acción, vamos a crear un archivo lib/templates/erb/scaffold/index.html.erb.tt
con el siguiente contenido:
<%% @<%= plural_table_name %>.count %> <%= human_name.pluralize %>
Nota que la plantilla es una plantilla ERB que renderiza otra plantilla ERB. Así que cualquier <%
que deba aparecer en la plantilla resultante debe escaparse como <%%
en la plantilla del generador.
Ahora ejecutemos el generador scaffold integrado de Rails:
$ bin/rails generate scaffold Post title:string
...
create app/views/posts/index.html.erb
...
El contenido de app/views/posts/index.html.erb
es:
<% @posts.count %> Posts
7 Sobrescribiendo Generadores de Rails
Los generadores integrados de Rails se pueden configurar a través de config.generators
, incluyendo sobrescribir algunos generadores por completo.
Primero, echemos un vistazo más de cerca a cómo funciona el generador 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
Del resultado, podemos ver que el generador scaffold invoca otros generadores, como el generador scaffold_controller
. Y algunos de esos generadores invocan otros generadores también. En particular, el generador scaffold_controller
invoca varios otros generadores, incluyendo el generador helper
.
Vamos a sobrescribir el generador helper
integrado con un nuevo generador. Nombraremos el generador 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
Y en lib/generators/rails/my_helper/my_helper_generator.rb
definiremos el generador como:
class Rails::MyHelperGenerator < Rails::Generators::NamedBase
def create_helper_file
create_file "app/helpers/#{file_name}_helper.rb", <<~RUBY
module #{class_name}Helper
# ¡Estoy ayudando!
end
RUBY
end
end
Finalmente, necesitamos decirle a Rails que use el generador my_helper
en lugar del generador helper
integrado. Para eso usamos config.generators
. En config/application.rb
, agreguemos:
config.generators do |g|
g.helper :my_helper
end
Ahora, si ejecutamos el generador scaffold nuevamente, vemos el generador my_helper
en acción:
$ bin/rails generate scaffold Article body:text
...
invoke scaffold_controller
...
invoke my_helper
create app/helpers/articles_helper.rb
...
NOTA: Puedes notar que la salida para el generador helper
integrado incluye "invoke test_unit", mientras que la salida para my_helper
no. Aunque el generador helper
no genera pruebas por defecto, sí proporciona un gancho para hacerlo usando hook_for
. Podemos hacer lo mismo incluyendo hook_for :test_framework, as: :helper
en la clase MyHelperGenerator
. Consulta la documentación de hook_for
para más información.
7.1 Alternativas de Generadores
Otra forma de sobrescribir generadores específicos es usando alternativas. Una alternativa permite que un espacio de nombres de generador delegue a otro espacio de nombres de generador.
Por ejemplo, digamos que queremos sobrescribir el generador test_unit:model
con nuestro propio generador my_test_unit:model
, pero no queremos reemplazar todos los demás generadores test_unit:*
como test_unit:controller
.
Primero, creamos el generador my_test_unit:model
en 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 "Haciendo cosas diferentes..."
end
end
end
A continuación, usamos config.generators
para configurar el generador test_framework
como my_test_unit
, pero también configuramos una alternativa para que cualquier generador my_test_unit:*
faltante se resuelva como test_unit:*
:
config.generators do |g|
g.test_framework :my_test_unit, fixture: false
g.fallbacks[:my_test_unit] = :test_unit
end
Ahora, cuando ejecutamos el generador scaffold, vemos que my_test_unit
ha reemplazado a test_unit
, pero solo se han afectado las pruebas del modelo:
$ 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
Haciendo cosas diferentes...
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 Plantillas de Aplicación
Las plantillas de aplicación son un tipo especial de generador. Pueden usar todos los métodos auxiliares del generador, pero están escritas como un script Ruby en lugar de una clase Ruby. Aquí hay un ejemplo:
# template.rb
if yes?("¿Te gustaría instalar Devise?")
gem "devise"
devise_model = ask("¿Cómo te gustaría que se llamara el modelo de usuario?", 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 inicial')
end
Primero, la plantilla pregunta al usuario si le gustaría instalar Devise. Si el usuario responde "sí" (o "y"), la plantilla añade Devise al Gemfile
, y pregunta al usuario por el nombre del modelo de usuario de Devise (por defecto User
). Más tarde, después de que se haya ejecutado bundle install
, la plantilla ejecutará los generadores de Devise y rails db:migrate
si se especificó un modelo de Devise. Finalmente, la plantilla hará git add
y git commit
de todo el directorio de la aplicación.
Podemos ejecutar nuestra plantilla al generar una nueva aplicación de Rails pasando la opción -m
al comando rails new
:
$ rails new my_cool_app -m path/to/template.rb
Alternativamente, podemos ejecutar nuestra plantilla dentro de una aplicación existente con bin/rails app:template
:
$ bin/rails app:template LOCATION=path/to/template.rb
Las plantillas tampoco necesitan almacenarse localmente; puedes especificar una URL en lugar de una ruta:
$ rails new my_cool_app -m http://example.com/template.rb
$ bin/rails app:template LOCATION=http://example.com/template.rb
9 Métodos Auxiliares del Generador
Thor proporciona muchos métodos auxiliares para generadores a través de Thor::Actions
, tales como:
Además de esos, Rails también proporciona muchos métodos auxiliares a través de Rails::Generators::Actions
, tales como:
10 Pruebas de Generadores
Rails proporciona métodos auxiliares para pruebas a través de Rails::Generators::Testing::Behaviour
, tales como:
Si ejecutas pruebas contra generadores, necesitarás establecer RAILS_LOG_TO_STDOUT=true
para que las herramientas de depuración funcionen.
RAILS_LOG_TO_STDOUT=true ./bin/test test/generators/actions_test.rb
Además de esos, Rails también proporciona afirmaciones adicionales a través de Rails::Generators::Testing::Assertions
.
Comentarios
Se te anima a ayudar a mejorar la calidad de esta guía.
Por favor contribuye si ves algún error tipográfico o errores fácticos. Para comenzar, puedes leer nuestra sección de contribuciones a la documentación.
También puedes encontrar contenido incompleto o cosas que no están actualizadas. Por favor agrega cualquier documentación faltante para main. Asegúrate de revisar Guías Edge primero para verificar si los problemas ya están resueltos o no en la rama principal. Revisa las Guías de Ruby on Rails para estilo y convenciones.
Si por alguna razón detectas algo que corregir pero no puedes hacerlo tú mismo, por favor abre un issue.
Y por último, pero no menos importante, cualquier tipo de discusión sobre la documentación de Ruby on Rails es muy bienvenida en el Foro oficial de Ruby on Rails.