1 Ayudantes de Vista para Depuración
Una tarea común es inspeccionar el contenido de una variable. Rails proporciona tres formas diferentes de hacer esto:
debug
to_yaml
inspect
1.1 debug
El ayudante debug
devolverá una etiqueta <pre> que renderiza el objeto usando el formato YAML. Esto generará datos legibles por humanos de cualquier objeto. Por ejemplo, si tienes este código en una vista:
<%= debug @article %>
<p>
<b>Título:</b>
<%= @article.title %>
</p>
Verás algo como esto:
--- !ruby/object Article
attributes:
updated_at: 2008-09-05 22:55:47
body: Es una guía muy útil para depurar tu aplicación Rails.
title: Guía de depuración de Rails
published: t
id: "1"
created_at: 2008-09-05 22:55:47
attributes_cache: {}
Título: Guía de depuración de Rails
1.2 to_yaml
Alternativamente, llamar a to_yaml
en cualquier objeto lo convierte a YAML. Puedes pasar este objeto convertido al método ayudante simple_format
para formatear la salida. Así es como debug
hace su magia.
<%= simple_format @article.to_yaml %>
<p>
<b>Título:</b>
<%= @article.title %>
</p>
El código anterior renderizará algo como esto:
--- !ruby/object Article
attributes:
updated_at: 2008-09-05 22:55:47
body: Es una guía muy útil para depurar tu aplicación Rails.
title: Guía de depuración de Rails
published: t
id: "1"
created_at: 2008-09-05 22:55:47
attributes_cache: {}
Título: Guía de depuración de Rails
1.3 inspect
Otro método útil para mostrar valores de objetos es inspect
, especialmente cuando se trabaja con arrays o hashes. Esto imprimirá el valor del objeto como una cadena. Por ejemplo:
<%= [1, 2, 3, 4, 5].inspect %>
<p>
<b>Título:</b>
<%= @article.title %>
</p>
Renderizará:
[1, 2, 3, 4, 5]
Título: Guía de depuración de Rails
2 El Registrador
También puede ser útil guardar información en archivos de registro en tiempo de ejecución. Rails mantiene un archivo de registro separado para cada entorno de ejecución.
2.1 ¿Qué es el Registrador?
Rails utiliza la clase ActiveSupport::Logger
para escribir información de registro. Otros registradores, como Log4r
, también pueden ser sustituidos.
Puedes especificar un registrador alternativo en config/application.rb
o cualquier otro archivo de entorno, por ejemplo:
config.logger = Logger.new(STDOUT)
config.logger = Log4r::Logger.new("Application Log")
O en la sección Initializer
, agrega cualquiera de los siguientes
Rails.logger = Logger.new(STDOUT)
Rails.logger = Log4r::Logger.new("Application Log")
CONSEJO: Por defecto, cada registro se crea bajo Rails.root/log/
y el archivo de registro se nombra según el entorno en el que se está ejecutando la aplicación.
2.2 Niveles de Registro
Cuando algo se registra, se imprime en el registro correspondiente si el nivel de registro del mensaje es igual o superior al nivel de registro configurado. Si quieres saber el nivel de registro actual, puedes llamar al método Rails.logger.level
.
Los niveles de registro disponibles son: :debug
, :info
, :warn
, :error
, :fatal
, y :unknown
, correspondientes a los números de nivel de registro de 0 a 5, respectivamente. Para cambiar el nivel de registro predeterminado, usa
config.log_level = :warn # En cualquier inicializador de entorno, o
Rails.logger.level = 0 # en cualquier momento
Esto es útil cuando deseas registrar bajo desarrollo o pruebas sin inundar tu registro de producción con información innecesaria.
CONSEJO: El nivel de registro predeterminado de Rails es :debug
. Sin embargo, se establece en :info
para el entorno production
en el archivo config/environments/production.rb
generado por defecto.
2.3 Envío de Mensajes
Para escribir en el registro actual utiliza el método logger.(debug|info|warn|error|fatal|unknown)
desde un controlador, modelo o mailer:
logger.debug "Hash de atributos de la persona: #{@person.attributes.inspect}"
logger.info "Procesando la solicitud..."
logger.fatal "Terminando la aplicación, ¡se ha generado un error irrecuperable!"
Aquí hay un ejemplo de un método instrumentado con registro adicional:
class ArticlesController < ApplicationController
# ...
def create
@article = Article.new(article_params)
logger.debug "Nuevo artículo: #{@article.attributes.inspect}"
logger.debug "El artículo debería ser válido: #{@article.valid?}"
if @article.save
logger.debug "El artículo se guardó y ahora el usuario será redirigido..."
redirect_to @article, notice: 'El artículo fue creado exitosamente.'
else
render :new, status: :unprocessable_entity
end
end
# ...
private
def article_params
params.require(:article).permit(:title, :body, :published)
end
end
Aquí hay un ejemplo del registro generado cuando se ejecuta esta acción del controlador:
Iniciado POST "/articles" para 127.0.0.1 a las 2018-10-18 20:09:23 -0400
Procesando por ArticlesController#create como HTML
Parámetros: {"utf8"=>"✓", "authenticity_token"=>"XLveDrKzF1SwaiNRPTaMtkrsTzedtebPPkmxEFIU0ordLjICSnXsSNfrdMa4ccyBjuGwnnEiQhEoMN6H1Gtz3A==", "article"=>{"title"=>"Depuración de Rails", "body"=>"Estoy aprendiendo cómo imprimir en registros.", "published"=>"0"}, "commit"=>"Crear Artículo"}
Nuevo artículo: {"id"=>nil, "title"=>"Depuración de Rails", "body"=>"Estoy aprendiendo cómo imprimir en registros.", "published"=>false, "created_at"=>nil, "updated_at"=>nil}
El artículo debería ser válido: true
(0.0ms) iniciar transacción
↳ app/controllers/articles_controller.rb:31
Crear Artículo (0.5ms) INSERTAR EN "articles" ("title", "body", "published", "created_at", "updated_at") VALORES (?, ?, ?, ?, ?) [["title", "Depuración de Rails"], ["body", "Estoy aprendiendo cómo imprimir en registros."], ["published", 0], ["created_at", "2018-10-19 00:09:23.216549"], ["updated_at", "2018-10-19 00:09:23.216549"]]
↳ app/controllers/articles_controller.rb:31
(2.3ms) confirmar transacción
↳ app/controllers/articles_controller.rb:31
El artículo se guardó y ahora el usuario será redirigido...
Redirigido a http://localhost:3000/articles/1
Completado 302 Encontrado en 4ms (ActiveRecord: 0.8ms)
Agregar registros adicionales como este facilita la búsqueda de comportamientos inesperados o inusuales en tus registros. Si agregas registros adicionales, asegúrate de hacer un uso sensato de los niveles de registro para evitar llenar tus registros de producción con información trivial.
2.4 Registros Verbosos de Consultas
Al observar la salida de consultas de base de datos en los registros, puede que no sea inmediatamente claro por qué se activan múltiples consultas de base de datos cuando se llama a un solo método:
irb(main):001:0> Article.pamplemousse
Carga de Artículo (0.4ms) SELECT "articles".* FROM "articles"
Carga de Comentario (0.2ms) SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ? [["article_id", 1]]
Carga de Comentario (0.1ms) SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ? [["article_id", 2]]
Carga de Comentario (0.1ms) SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ? [["article_id", 3]]
=> #<Comment id: 2, author: "1", body: "Bueno, en realidad...", article_id: 1, created_at: "2018-10-19 00:56:10", updated_at: "2018-10-19 00:56:10">
Después de ejecutar ActiveRecord.verbose_query_logs = true
en la sesión de bin/rails console
para habilitar registros verbosos de consultas y ejecutar el método nuevamente, se vuelve obvio qué línea de código está generando todas estas llamadas discretas a la base de datos:
irb(main):003:0> Article.pamplemousse
Carga de Artículo (0.2ms) SELECT "articles".* FROM "articles"
↳ app/models/article.rb:5
Carga de Comentario (0.1ms) SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ? [["article_id", 1]]
↳ app/models/article.rb:6
Carga de Comentario (0.1ms) SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ? [["article_id", 2]]
↳ app/models/article.rb:6
Carga de Comentario (0.1ms) SELECT "comments".* FROM "comments" WHERE "comments"."article_id" = ? [["article_id", 3]]
↳ app/models/article.rb:6
=> #<Comment id: 2, author: "1", body: "Bueno, en realidad...", article_id: 1, created_at: "2018-10-19 00:56:10", updated_at: "2018-10-19 00:56:10">
Debajo de cada declaración de base de datos puedes ver flechas que apuntan al archivo fuente específico (y número de línea) del método que resultó en una llamada a la base de datos. Esto puede ayudarte a identificar y abordar problemas de rendimiento causados por consultas N+1: consultas de base de datos únicas que generan múltiples consultas adicionales.
Los registros verbosos de consultas están habilitados por defecto en los registros del entorno de desarrollo después de Rails 5.2.
ADVERTENCIA: Recomendamos no usar esta configuración en entornos de producción. Depende del método Kernel#caller
de Ruby, que tiende a asignar mucha memoria para generar rastros de pila de llamadas a métodos. Usa etiquetas de registro de consultas (ver abajo) en su lugar.
2.5 Registros Verbosos de Encolado
Similar a los "Registros Verbosos de Consultas" anteriores, permite imprimir ubicaciones de origen de métodos que encolan trabajos en segundo plano.
Está habilitado por defecto en desarrollo. Para habilitar en otros entornos, agrega en application.rb
o cualquier inicializador de entorno:
config.active_job.verbose_enqueue_logs = true
Al igual que los registros verbosos de consultas, no se recomienda para uso en entornos de producción.
3 Comentarios en Consultas SQL
Las declaraciones SQL pueden comentarse con etiquetas que contienen información de tiempo de ejecución, como el nombre del controlador o del trabajo, para rastrear consultas problemáticas hasta el área de la aplicación que generó estas declaraciones. Esto es útil cuando estás registrando consultas lentas (por ejemplo, MySQL, PostgreSQL), viendo consultas que se están ejecutando actualmente, o para herramientas de rastreo de extremo a extremo.
Para habilitar, agrega en application.rb
o cualquier inicializador de entorno:
config.active_record.query_log_tags_enabled = true
Por defecto, se registran el nombre de la aplicación, el nombre y la acción del controlador, o el nombre del trabajo. El formato predeterminado es SQLCommenter. Por ejemplo:
Carga de Artículo (0.2ms) SELECT "articles".* FROM "articles" /*application='Blog',controller='articles',action='index'*/
Actualización de Artículo (0.3ms) UPDATE "articles" SET "title" = ?, "updated_at" = ? WHERE "posts"."id" = ? /*application='Blog',job='ImproveTitleJob'*/ [["title", "Guía de depuración de Rails mejorada"], ["updated_at", "2022-10-16 20:25:40.091371"], ["id", 1]]
El comportamiento de ActiveRecord::QueryLogs
puede modificarse para incluir cualquier cosa que ayude a conectar los puntos desde la consulta SQL, como identificadores de solicitud y trabajo para registros de la aplicación, identificadores de cuenta e inquilino, etc.
3.1 Registro con Etiquetas
Al ejecutar aplicaciones multiusuario y multi-cuenta, a menudo es útil poder filtrar los registros usando algunas reglas personalizadas. TaggedLogging
en Active Support te ayuda a hacer exactamente eso al estampar líneas de registro con subdominios, identificadores de solicitud, y cualquier otra cosa para ayudar a depurar tales aplicaciones.
logger = ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
logger.tagged("BCX") { logger.info "Cosas" } # Registra "[BCX] Cosas"
logger.tagged("BCX", "Jason") { logger.info "Cosas" } # Registra "[BCX] [Jason] Cosas"
logger.tagged("BCX") { logger.tagged("Jason") { logger.info "Cosas" } } # Registra "[BCX] [Jason] Cosas"
3.2 Impacto de los Registros en el Rendimiento
El registro siempre tendrá un pequeño impacto en el rendimiento de tu aplicación Rails, particularmente cuando se registra en disco. Además, hay algunas sutilezas:
Usar el nivel :debug
tendrá una mayor penalización de rendimiento que :fatal
, ya que se evalúa y escribe una cantidad mucho mayor de cadenas en la salida de registro (por ejemplo, disco).
Otra posible trampa es demasiadas llamadas a Logger
en tu código:
logger.debug "Hash de atributos de la persona: #{@person.attributes.inspect}"
En el ejemplo anterior, habrá un impacto en el rendimiento incluso si el nivel de salida permitido no incluye depuración. La razón es que Ruby tiene que evaluar estas cadenas, lo que incluye instanciar el objeto String
algo pesado y hacer la interpolación de variables.
Por lo tanto, se recomienda pasar bloques a los métodos del registrador, ya que estos solo se evalúan si el nivel de salida es el mismo que — o está incluido en — el nivel permitido (es decir, carga perezosa). El mismo código reescrito sería:
logger.debug { "Hash de atributos de la persona: #{@person.attributes.inspect}" }
El contenido del bloque, y por lo tanto la interpolación de cadenas, solo se evalúa si la depuración está habilitada. Este ahorro de rendimiento solo es realmente notable con grandes cantidades de registro, pero es una buena práctica emplearla.
Esta sección fue escrita por Jon Cairns en una respuesta de Stack Overflow y está licenciada bajo cc by-sa 4.0.
4 Depuración con la Gem debug
Cuando tu código se comporta de manera inesperada, puedes intentar imprimir en los registros o en la consola para diagnosticar el problema. Desafortunadamente, hay momentos en que este tipo de seguimiento de errores no es efectivo para encontrar la causa raíz de un problema. Cuando realmente necesitas adentrarte en tu código fuente en ejecución, el depurador es tu mejor compañero.
El depurador también puede ayudarte si quieres aprender sobre el código fuente de Rails pero no sabes por dónde empezar. Simplemente depura cualquier solicitud a tu aplicación y usa esta guía para aprender cómo moverte desde el código que has escrito hacia el código subyacente de Rails.
Rails 7 incluye la gem debug
en el Gemfile
de nuevas aplicaciones generadas por CRuby. Por defecto, está lista en los entornos development
y test
. Por favor, consulta su documentación para su uso.
4.1 Iniciando una Sesión de Depuración
Por defecto, una sesión de depuración comenzará después de que la biblioteca debug
sea requerida, lo cual ocurre cuando tu aplicación se inicia. Pero no te preocupes, la sesión no interferirá con tu aplicación.
Para entrar en la sesión de depuración, puedes usar binding.break
y sus alias: binding.b
y debugger
. Los siguientes ejemplos usarán debugger
:
class PostsController < ApplicationController
before_action :set_post, only: %i[ show edit update destroy ]
# GET /posts or /posts.json
def index
@posts = Post.all
debugger
end
# ...
end
Una vez que tu aplicación evalúe la declaración de depuración, entrará en la sesión de depuración:
Processing by PostsController#index as HTML
[2, 11] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
2| before_action :set_post, only: %i[ show edit update destroy ]
3|
4| # GET /posts or /posts.json
5| def index
6| @posts = Post.all
=> 7| debugger
8| end
9|
10| # GET /posts/1 or /posts/1.json
11| def show
=>#0 PostsController#index at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:7
#1 ActionController::BasicImplicitRender#send_action(method="index", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.2.0.alpha/lib/action_controller/metal/basic_implicit_render.rb:6
# and 72 frames (use `bt' command for all frames)
(rdbg)
Puedes salir de la sesión de depuración en cualquier momento y continuar con la ejecución de tu aplicación con el comando continue
(o c
). O, para salir tanto de la sesión de depuración como de tu aplicación, usa el comando quit
(o q
).
4.2 El Contexto
Después de entrar en la sesión de depuración, puedes escribir código Ruby como si estuvieras en una consola de Rails o IRB.
(rdbg) @posts # ruby
[]
(rdbg) self
#<PostsController:0x0000000000aeb0>
(rdbg)
También puedes usar el comando p
o pp
para evaluar expresiones Ruby, lo cual es útil cuando un nombre de variable entra en conflicto con un comando del depurador.
(rdbg) p headers # command
=> {"X-Frame-Options"=>"SAMEORIGIN", "X-XSS-Protection"=>"1; mode=block", "X-Content-Type-Options"=>"nosniff", "X-Download-Options"=>"noopen", "X-Permitted-Cross-Domain-Policies"=>"none", "Referrer-Policy"=>"strict-origin-when-cross-origin"}
(rdbg) pp headers # command
{"X-Frame-Options"=>"SAMEORIGIN",
"X-XSS-Protection"=>"1; mode=block",
"X-Content-Type-Options"=>"nosniff",
"X-Download-Options"=>"noopen",
"X-Permitted-Cross-Domain-Policies"=>"none",
"Referrer-Policy"=>"strict-origin-when-cross-origin"}
(rdbg)
Además de la evaluación directa, el depurador también te ayuda a recopilar una gran cantidad de información a través de diferentes comandos, como:
info
(oi
) - Información sobre el marco actual.backtrace
(obt
) - Rastreo de pila (con información adicional).outline
(oo
,ls
) - Métodos, constantes, variables locales y variables de instancia disponibles en el alcance actual.
4.2.1 El Comando info
info
proporciona una visión general de los valores de las variables locales y de instancia que son visibles desde el marco actual.
(rdbg) info # command
%self = #<PostsController:0x0000000000af78>
@_action_has_layout = true
@_action_name = "index"
@_config = {}
@_lookup_context = #<ActionView::LookupContext:0x00007fd91a037e38 @details_key=nil, @digest_cache=...
@_request = #<ActionDispatch::Request GET "http://localhost:3000/posts" for 127.0.0.1>
@_response = #<ActionDispatch::Response:0x00007fd91a03ea08 @mon_data=#<Monitor:0x00007fd91a03e8c8>...
@_response_body = nil
@_routes = nil
@marked_for_same_origin_verification = true
@posts = []
@rendered_format = nil
4.2.2 El Comando backtrace
Cuando se usa sin ninguna opción, backtrace
lista todos los marcos en la pila:
=>#0 PostsController#index at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:7
#1 ActionController::BasicImplicitRender#send_action(method="index", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-2.0.alpha/lib/action_controller/metal/basic_implicit_render.rb:6
#2 AbstractController::Base#process_action(method_name="index", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.2.0.alpha/lib/abstract_controller/base.rb:214
#3 ActionController::Rendering#process_action(#arg_rest=nil) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.2.0.alpha/lib/action_controller/metal/rendering.rb:53
#4 bloque en process_action at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.2.0.alpha/lib/abstract_controller/callbacks.rb:221
#5 bloque en run_callbacks at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activesupport-7.2.0.alpha/lib/active_support/callbacks.rb:118
#6 ActionText::Rendering::ClassMethods#with_renderer(renderer=#<PostsController:0x0000000000af78>) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actiontext-7.2.0.alpha/lib/action_text/rendering.rb:20
#7 bloque {|controller=#<PostsController:0x0000000000af78>, action=#<Proc:0x00007fd91985f1c0 /Users/st0012/...|} en <class:Engine> (4 niveles) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actiontext-7.2.0.alpha/lib/action_text/engine.rb:69
#8 [C] BasicObject#instance_exec at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activesupport-7.2.0.alpha/lib/active_support/callbacks.rb:127
..... y más
Cada marco viene con:
- Identificador de marco
- Ubicación de la llamada
- Información adicional (por ejemplo, argumentos de bloque o método)
Esto te dará una gran idea de lo que está sucediendo en tu aplicación. Sin embargo, probablemente notarás que:
- Hay demasiados marcos (generalmente más de 50 en una aplicación Rails).
- La mayoría de los marcos son de Rails u otras bibliotecas que usas.
El comando backtrace
proporciona 2 opciones para ayudarte a filtrar marcos:
backtrace [num]
- solo muestranum
números de marcos, por ejemplo,backtrace 10
.backtrace /pattern/
- solo muestra marcos con identificador o ubicación que coinciden con el patrón, por ejemplo,backtrace /MyModel/
.
También es posible usar estas opciones juntas: backtrace [num] /pattern/
.
4.2.3 El Comando outline
outline
es similar al comando ls
de pry
e irb
. Te mostrará lo que es accesible desde el alcance actual, incluyendo:
- Variables locales
- Variables de instancia
- Variables de clase
- Métodos y sus fuentes
ActiveSupport::Configurable#methods: config
AbstractController::Base#methods:
action_methods action_name action_name= available_action? controller_path inspect
response_body
ActionController::Metal#methods:
content_type content_type= controller_name dispatch headers
location location= media_type middleware_stack middleware_stack=
middleware_stack? performed? request request= reset_session
response response= response_body= response_code session
set_request! set_response! status status= to_a
ActionView::ViewPaths#methods:
_prefixes any_templates? append_view_path details_for_lookup formats formats= locale
locale= lookup_context prepend_view_path template_exists? view_paths
AbstractController::Rendering#methods: view_assigns
# .....
PostsController#methods: create destroy edit index new show update
instance variables:
@_action_has_layout @_action_name @_config @_lookup_context @_request
@_response @_response_body @_routes @marked_for_same_origin_verification @posts
@rendered_format
class variables: @@raise_on_open_redirects
4.3 Puntos de Interrupción
Hay muchas formas de insertar y activar un punto de interrupción en el depurador. Además de agregar declaraciones de depuración (por ejemplo, debugger
) directamente en tu código, también puedes insertar puntos de interrupción con comandos:
break
(ob
)break
- lista todos los puntos de interrupciónbreak <num>
- establece un punto de interrupción en la líneanum
del archivo actualbreak <file:num>
- establece un punto de interrupción en la líneanum
delfile
break <Class#method>
obreak <Class.method>
- establece un punto de interrupción enClass#method
oClass.method
break <expr>.<method>
- establece un punto de interrupción en el método<method>
del resultado de<expr>
.
catch <Exception>
- establece un punto de interrupción que se detendrá cuando se lanceException
watch <@ivar>
- establece un punto de interrupción que se detendrá cuando cambie el resultado del@ivar
del objeto actual (esto es lento)
Y para eliminarlos, puedes usar:
delete
(odel
)delete
- elimina todos los puntos de interrupcióndelete <num>
- elimina el punto de interrupción con idnum
4.3.1 El Comando break
Establece un punto de interrupción en un número de línea especificado - por ejemplo, b 28
[20, 29] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
20| end
21|
22| # POST /posts or /posts.json
23| def create
24| @post = Post.new(post_params)
=> 25| debugger
26|
27| respond_to do |format|
28| if @post.save
29| format.html { redirect_to @post, notice: "El post fue creado exitosamente." }
=>#0 PostsController#create at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:25
#1 ActionController::BasicImplicitRender#send_action(method="create", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/basic_implicit_render.rb:6
# y 72 marcos (usa el comando `bt' para todos los marcos)
(rdbg) b 28 # comando break
#0 BP - Línea /Users/st0012/projects/rails-guide-example/app/controllers/posts_controller.rb:28 (línea)
(rdbg) c # comando continue
[23, 32] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
23| def create
24| @post = Post.new(post_params)
25| debugger
26|
27| respond_to do |format|
=> 28| if @post.save
29| format.html { redirect_to @post, notice: "El post fue creado exitosamente." }
30| format.json { render :show, status: :created, location: @post }
31| else
32| format.html { render :new, status: :unprocessable_entity }
=>#0 bloque {|format=#<ActionController::MimeResponds::Collec...|} en create at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:28
#1 ActionController::MimeResponds#respond_to(mimes=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/mime_responds.rb:205
# y 74 marcos (usa el comando `bt' para todos los marcos)
Detenido por #0 BP - Línea /Users/st0012/projects/rails-guide-example/app/controllers/posts_controller.rb:28 (línea)
Establece un punto de interrupción en una llamada a método dada - por ejemplo, b @post.save
.
[20, 29] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
20| end
21|
22| # POST /posts or /posts.json
23| def create
24| @post = Post.new(post_params)
=> 25| debugger
26|
27| respond_to do |format|
28| if @post.save
29| format.html { redirect_to @post, notice: "El post fue creado exitosamente." }
=>#0 PostsController#create at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:25
#1 ActionController::BasicImplicitRender#send_action(method="create", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/basic_implicit_render.rb:6
# y 72 marcos (usa el comando `bt' para todos los marcos)
(rdbg) b @post.save # comando break
#0 BP - Método @post.save en /Users/st0012/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/suppressor.rb:43
(rdbg) c # comando continue
[39, 48] in ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/suppressor.rb
39| SuppressorRegistry.suppressed[name] = previous_state
40| end
41| end
42|
43| def save(**) # :nodoc:
=> 44| SuppressorRegistry.suppressed[self.class.name] ? true : super
45| end
46|
47| def save!(**) # :nodoc:
48| SuppressorRegistry.suppressed[self.class.name] ? true : super
=>#0 ActiveRecord::Suppressor#save(#arg_rest=nil) en ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/suppressor.rb:44
#1 bloque {|format=#<ActionController::MimeResponds::Collec...|} en create en ~/projects/rails-guide-example/app/controllers/posts_controller.rb:28
# y 75 marcos (usa el comando `bt' para todos los marcos)
Detenido por #0 BP - Método @post.save en /Users/st0012/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/suppressor.rb:43
4.3.2 El Comando catch
Detenerse cuando se lanza una excepción - por ejemplo, catch ActiveRecord::RecordInvalid
.
[20, 29] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
20| end
21|
22| # POST /posts or /posts.json
23| def create
24| @post = Post.new(post_params)
=> 25| debugger
26|
27| respond_to do |format|
28| if @post.save!
29| format.html { redirect_to @post, notice: "El post fue creado exitosamente." }
=>#0 PostsController#create at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:25
#1 ActionController::BasicImplicitRender#send_action(method="create", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/basic_implicit_render.rb:6
# y 72 marcos (usa el comando `bt' para todos los marcos)
(rdbg) catch ActiveRecord::RecordInvalid # comando
#1 BP - Catch "ActiveRecord::RecordInvalid"
(rdbg) c # comando continue
[75, 84] in ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb
75| def default_validation_context
76| new_record? ? :create : :update
77| end
78|
79| def raise_validation_error
=> 80| raise(RecordInvalid.new(self))
81| end
82|
83| def perform_validations(options = {})
84| options[:validate] == false || valid?(options[:context])
=>#0 ActiveRecord::Validations#raise_validation_error en ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb:80
#1 ActiveRecord::Validations#save!(options={}) en ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb:53
# y 88 marcos (usa el comando `bt' para todos los marcos)
Detenido por #1 BP - Catch "ActiveRecord::RecordInvalid"
4.3.3 El Comando watch
Detenerse cuando la variable de instancia cambia - por ejemplo, watch @_response_body
.
[20, 29] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
20| end
21|
22| # POST /posts or /posts.json
23| def create
24| @post = Post.new(post_params)
=> 25| debugger
26|
27| respond_to do |format|
28| if @post.save!
29| format.html { redirect_to @post, notice: "El post fue creado exitosamente." }
=>#0 PostsController#create at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:25
#1 ActionController::BasicImplicitRender#send_action(method="create", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/basic_implicit_render.rb:6
# y 72 marcos (usa el comando `bt' para todos los marcos)
(rdbg) watch @_response_body # comando
#0 BP - Watch #<PostsController:0x00007fce69ca5320> @_response_body =
(rdbg) c # comando continue
[173, 182] in ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal.rb
173| body = [body] unless body.nil? || body.respond_to?(:each)
174| response.reset_body!
175| return unless body
176| response.body = body
177| super
=> 178| end
179|
180| # Prueba si renderizar o redirigir ya ha ocurrido.
181| def performed?
182| response_body || response.committed?
=>#0 ActionController::Metal#response_body=(body=["<html><body>Estás siendo <a href=\"ht...) en ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal.rb:178 #=> ["<html><body>Estás siendo <a href=\"http://localhost:3000/posts/13\">redirigido</a>.</body></html>"]
#1 ActionController::Redirecting#redirect_to(options=#<Post id: 13, title: "qweqwe", content:..., response_options={:allow_other_host=>false}) en ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/redirecting.rb:74
# y 82 marcos (usa el comando `bt' para todos los marcos)
Detenido por #0 BP - Watch #<PostsController:0x00007fce69ca5320> @_response_body = -> ["<html><body>Estás siendo <a href=\"http://localhost:3000/posts/13\">redirigido</a>.</body></html>"]
(rdbg)
4.3.4 Opciones de Puntos de Interrupción
Además de diferentes tipos de puntos de interrupción, también puedes especificar opciones para lograr flujos de trabajo de depuración más avanzados. Actualmente, el depurador admite 4 opciones:
do: <cmd o expr>
- cuando se activa el punto de interrupción, ejecuta el comando/expresión dado y continúa el programa:break Foo#bar do: bt
- cuando se llama aFoo#bar
, imprime los marcos de pila.
pre: <cmd o expr>
- cuando se activa el punto de interrupción, ejecuta el comando/expresión dado antes de detenerse:break Foo#bar pre: info
- cuando se llama aFoo#bar
, imprime sus variables circundantes antes de detenerse.
if: <expr>
- el punto de interrupción solo se detiene si el resultado de<expr>
es verdadero:break Post#save if: params[:debug]
- se detiene enPost#save
siparams[:debug]
también es verdadero.
path: <path_regexp>
- el punto de interrupción solo se detiene si el evento que lo activa (por ejemplo, una llamada a método) ocurre desde la ruta dada:break Post#save path: app/services/a_service
- se detiene enPost#save
si la llamada al método ocurre en una ruta que incluyeapp/services/a_service
.
También ten en cuenta que las primeras 3 opciones: do:
, pre:
e if:
también están disponibles para las declaraciones de depuración que mencionamos anteriormente. Por ejemplo:
[2, 11] in ~/projects/rails-guide-example/app/controllers/posts_controller.rb
2| before_action :set_post, only: %i[ show edit update destroy ]
3|
4| # GET /posts or /posts.json
5| def index
6| @posts = Post.all
=> 7| debugger(do: "info")
8| end
9|
10| # GET /posts/1 or /posts/1.json
11| def show
=>#0 PostsController#index at ~/projects/rails-guide-example/app/controllers/posts_controller.rb:7
#1 ActionController::BasicImplicitRender#send_action(method="index", args=[]) at ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/basic_implicit_render.rb:6
# y 72 marcos (usa el comando `bt' para todos los marcos)
(rdbg:binding.break) info
%self = #<PostsController:0x00000000017480>
@_action_has_layout = true
@_action_name = "index"
@_config = {}
@_lookup_context = #<ActionView::LookupContext:0x00007fce3ad336b8 @details_key=nil, @digest_cache=...
@_request = #<ActionDispatch::Request GET "http://localhost:3000/posts" for 127.0.0.1>
@_response = #<ActionDispatch::Response:0x00007fce3ad397e8 @mon_data=#<Monitor:0x00007fce3ad396a8>...
@_response_body = nil
@_routes = nil
@marked_for_same_origin_verification = true
@posts = #<ActiveRecord::Relation [#<Post id: 2, title: "qweqwe", content: "qweqwe", created_at: "...
@rendered_format = nil
4.3.5 Programa tu Flujo de Trabajo de Depuración
Con esas opciones, puedes programar tu flujo de trabajo de depuración en una línea como:
def create
debugger(do: "catch ActiveRecord::RecordInvalid do: bt 10")
# ...
end
Y luego el depurador ejecutará el comando programado e insertará el punto de interrupción catch
(rdbg:binding.break) catch ActiveRecord::RecordInvalid do: bt 10
#0 BP - Catch "ActiveRecord::RecordInvalid"
[75, 84] in ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb
75| def default_validation_context
76| new_record? ? :create : :update
77| end
78|
79| def raise_validation_error
=> 80| raise(RecordInvalid.new(self))
81| end
82|
83| def perform_validations(options = {})
84| options[:validate] == false || valid?(options[:context])
=>#0 ActiveRecord::Validations#raise_validation_error en ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb:80
#1 ActiveRecord::Validations#save!(options={}) en ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb:53
# y 88 marcos (usa el comando `bt' para todos los marcos)
Una vez que se activa el punto de interrupción catch, imprimirá los marcos de pila
Detenido por #0 BP - Catch "ActiveRecord::RecordInvalid"
(rdbg:catch) bt 10
=>#0 ActiveRecord::Validations#raise_validation_error en ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb:80
#1 ActiveRecord::Validations#save!(options={}) en ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/validations.rb:53
#2 bloque en save! en ~/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/activerecord-7.0.0.alpha2/lib/active_record/transactions.rb:302
Esta técnica puede ahorrarte de la entrada manual repetida y hacer que la experiencia de depuración sea más fluida.
Puedes encontrar más comandos y opciones de configuración en su documentación.
5 Depuración con la Gem web-console
Web Console es un poco como debug
, pero se ejecuta en el navegador. Puedes solicitar una consola en el contexto de una vista o un controlador en cualquier página. La consola se renderizará junto a tu contenido HTML.
5.1 Consola
Dentro de cualquier acción de controlador o vista, puedes invocar la consola llamando al método console
.
Por ejemplo, en un controlador:
class PostsController < ApplicationController
def new
console
@post = Post.new
end
end
O en una vista:
<% console %>
<h2>Nuevo Post</h2>
Esto renderizará una consola dentro de tu vista. No necesitas preocuparte por la ubicación de la llamada console
; no se renderizará en el lugar de su invocación, sino junto a tu contenido HTML.
La consola ejecuta código Ruby puro: Puedes definir e instanciar clases personalizadas, crear nuevos modelos e inspeccionar variables.
NOTA: Solo se puede renderizar una consola por solicitud. De lo contrario, web-console
levantará un error en la segunda invocación de console
.
5.2 Inspección de Variables
Puedes invocar instance_variables
para listar todas las variables de instancia disponibles en tu contexto. Si quieres listar todas las variables locales, puedes hacerlo con local_variables
.
5.3 Configuraciones
config.web_console.allowed_ips
: Lista autorizada de direcciones y redes IPv4 o IPv6 (por defecto:127.0.0.1/8, ::1
).config.web_console.whiny_requests
: Registra un mensaje cuando se previene la renderización de una consola (por defecto:true
).
Dado que web-console
evalúa código Ruby plano de forma remota en el servidor, no intentes usarlo en producción.
6 Depuración de Fugas de Memoria
Una aplicación Ruby (en Rails o no), puede tener fugas de memoria — ya sea en el código Ruby o a nivel de código C.
En esta sección, aprenderás cómo encontrar y corregir tales fugas utilizando herramientas como Valgrind.
6.1 Valgrind
Valgrind es una aplicación para detectar fugas de memoria basadas en C y condiciones de carrera.
Hay herramientas de Valgrind que pueden detectar automáticamente muchos errores de gestión de memoria e hilos, y perfilar tus programas en detalle. Por ejemplo, si una extensión C en el intérprete llama a malloc()
pero no llama correctamente a free()
, esta memoria no estará disponible hasta que la aplicación termine.
Para obtener más información sobre cómo instalar Valgrind y usarlo con Ruby, consulta Valgrind y Ruby por Evan Weaver.
6.2 Encontrar una Fuga de Memoria
Hay un excelente artículo sobre la detección y corrección de fugas de memoria en Derailed, que puedes leer aquí.
7 Plugins para Depuración
Hay algunos plugins de Rails para ayudarte a encontrar errores y depurar tu aplicación. Aquí hay una lista de plugins útiles para depuración:
- Query Trace Agrega trazado de origen de consultas a tus registros.
- Exception Notifier Proporciona un objeto de correo y un conjunto predeterminado de plantillas para enviar notificaciones por correo electrónico cuando ocurren errores en una aplicación Rails.
- Better Errors Reemplaza la página de error estándar de Rails con una nueva que contiene más información contextual, como código fuente e inspección de variables.
- RailsPanel Extensión de Chrome para el desarrollo de Rails que terminará con tu seguimiento del development.log. Ten toda la información sobre las solicitudes de tu aplicación Rails en el navegador — en el panel de Herramientas para Desarrolladores. Proporciona información sobre tiempos de db/renderizado/total, lista de parámetros, vistas renderizadas y más.
- Pry Una alternativa a IRB y consola de desarrollo en tiempo de ejecución.
8 Referencias
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.