1 Primer contacto
Cuando creas una aplicación utilizando el comando rails
, en realidad estás utilizando 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 utilizamos el comando global rails
, que utiliza la versión de Rails instalada a través de gem install rails
. Cuando estás dentro del directorio de tu aplicación, utilizamos el comando bin/rails
, que utiliza la versión de Rails incluida en 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 se construyen 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 de inicialización 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
# Agrega aquí el contenido de inicialización
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 se define. Nuestro método invoca create_file
, que creará un archivo en la ubicación especificada 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 suele ser capaz de derivar una buena descripción si un generador tiene un espacio de nombres, como ActiveRecord::Generators::ModelGenerator
, pero no en este caso. Podemos resolver este problema de dos formas. 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 de inicialización en config/initializers"
def create_initializer_file
create_file "config/initializers/initializer.rb", <<~RUBY
# Agrega aquí el contenido de inicialización
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 que acaba de ser creado:
class InitializerGenerator < Rails::Generators::NamedBase
source_root File.expand_path("templates", __dir__)
end
Primero, observa 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 en nuestro código a través de name
.
Podemos ver eso al verificar la descripción del nuevo generador:
$ bin/rails generate initializer --help
Uso:
bin/rails generate initializer NAME [opciones]
Además, observa 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, creemos el archivo lib/generators/initializer/templates/initializer.rb
con el siguiente contenido:
# Agrega aquí el contenido de inicialización
Y cambiemos el generador para copiar esta plantilla cuando se invoque: ```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 ```
Ahora vamos a ejecutar nuestro generador:
$ bin/rails generate initializer core_extensions
create config/initializers/core_extensions.rb
$ cat config/initializers/core_extensions.rb
# Agrega aquí el contenido de inicialización
Vemos que copy_file
creó config/initializers/core_extensions.rb
con el contenido de nuestra plantilla. (El método file_name
utilizado en la
ruta de destino se hereda de Rails::Generators::NamedBase
.)
4 Opciones de línea de comandos del generador
Los generadores pueden admitir opciones de línea de comandos utilizando class_option
. Por ejemplo:
class InitializerGenerator < Rails::Generators::NamedBase
class_option :scope, type: :string, default: "app"
end
Ahora nuestro generador puede ser invocado con la opción --scope
:
$ bin/rails generate initializer theme --scope dashboard
Los valores de las opciones son accesibles en los métodos del generador a través de options
:
def copy_initializer_file
@scope = options["scope"]
end
5 Resolución de generadores
Al resolver el nombre de un generador, Rails busca el generador utilizando 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 encuentre 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
, lo que permite que Rails encuentre y cargue el archivo.
6 Anulación de las plantillas de generador de Rails
Rails también buscará en varios lugares al resolver los archivos de plantilla del generador.
Uno de esos lugares es el directorio lib/templates/
de la aplicación. Este
comportamiento nos permite anular las plantillas utilizadas por los generadores incorporados de Rails.
Por ejemplo, podríamos anular la plantilla del controlador de scaffold o las
plantillas de vista de scaffold.
Para ver esto en acción, creemos un archivo lib/templates/erb/scaffold/index.html.erb.tt
con el siguiente contenido:
<%% @<%= plural_table_name %>.count %> <%= human_name.pluralize %>
Ten en cuenta que la plantilla es una plantilla ERB que renderiza otra plantilla ERB.
Por lo tanto, cualquier <%
que deba aparecer en la plantilla resultante debe escaparse como
<%%
en la plantilla del generador.
Ahora ejecutemos el generador de scaffold incorporado 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 Anulación de los generadores de Rails
Los generadores incorporados de Rails se pueden configurar a través de config.generators
,
incluyendo la anulación de algunos generadores por completo.
Primero, echemos un vistazo más de cerca a cómo funciona el generador 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
A partir de la salida, podemos ver que el generador de scaffold invoca otros
generadores, como el generador scaffold_controller
. Y algunos de esos
generadores también invocan a otros generadores. En particular, el generador scaffold_controller
invoca varios otros generadores, incluido el generador helper
.
Anulemos el generador incorporado helper
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 incorporado helper
. Para eso, usamos config.generators
. En
config/application.rb
, agreguemos:
config.generators do |g|
g.helper :my_helper
end
Ahora, si ejecutamos el generador de scaffold nuevamente, veremos 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: Puede que notes que la salida para el generador incorporado helper
incluye "invoke test_unit", mientras que la salida para my_helper
no lo hace.
Aunque el generador helper
no genera pruebas de forma predeterminada, 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 obtener más información.
7.1 Fallbacks de generadores
Otra forma de anular generadores específicos es mediante el uso de fallbacks. Un fallback
permite que un espacio de nombres de generador delegue en otro espacio de nombres de generador.
Por ejemplo, supongamos que queremos anular 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, utilizamos config.generators
para configurar el generador test_framework
como my_test_unit
, pero también configuramos un fallback para que cualquier generador my_test_unit:*
que falte 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 de scaffold, vemos que my_test_unit
ha reemplazado a test_unit
, pero solo se han visto afectadas las pruebas de 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 utilizar todos los métodos auxiliares del generador, pero se escriben como un script de Ruby en lugar de una clase de Ruby. Aquí tienes un ejemplo:
# template.rb
if yes?("¿Deseas instalar Devise?")
gem "devise"
devise_model = ask("¿Cómo te gustaría llamar al 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 le pregunta al usuario si desea instalar Devise. Si el usuario responde "sí" (o "s"), la plantilla agrega Devise al Gemfile
y le pregunta al usuario 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 del generador a través de Thor::Actions
, como:
Además de eso, Rails también proporciona muchos métodos auxiliares a través de Rails::Generators::Actions
, como:
Comentarios
Se te anima a ayudar a mejorar la calidad de esta guía.
Por favor, contribuye si encuentras algún error tipográfico o factual. Para empezar, puedes leer nuestra contribución a la documentación sección.
También puedes encontrar contenido incompleto o desactualizado. Por favor, añade cualquier documentación faltante para main. Asegúrate de revisar Edge Guides primero para verificar si los problemas ya están resueltos o no en la rama principal. Consulta las Directrices de las Guías de Ruby on Rails para el estilo y las convenciones.
Si por alguna razón encuentras algo que corregir pero no puedes solucionarlo tú mismo, por favor abre un problema.
Y por último, cualquier tipo de discusión sobre la documentación de Ruby on Rails es muy bienvenida en el Foro oficial de Ruby on Rails.