-
Notifications
You must be signed in to change notification settings - Fork 0
/
active_job_basics.html
1031 lines (958 loc) · 71 KB
/
active_job_basics.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!doctype html>
<html dir="ltr" lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Conceptos Básicos de Active Job — Ruby on Rails Guides</title>
<link rel="stylesheet" type="text/css" href="stylesheets/style-v2.css" data-turbo-track="reload">
<link rel="stylesheet" type="text/css" href="stylesheets/print-v2.css" media="print">
<link rel="stylesheet" type="text/css" href="stylesheets/highlight-v2.css" data-turbo-track="reload">
<link rel="icon" href="images/favicon.ico" sizes="any">
<link rel="apple-touch-icon" href="images/icon.png">
<script src="javascripts/@hotwired--turbo.js" data-turbo-track="reload"></script>
<script src="javascripts/clipboard.js" data-turbo-track="reload"></script>
<script src="javascripts/guides.js" data-turbo-track="reload"></script>
<meta property="og:title" content="Conceptos Básicos de Active Job — Ruby on Rails Guides" />
<meta name="description" content="NO LEAS ESTE ARCHIVO EN GITHUB, LAS GUÍAS ESTÁN PUBLICADAS EN https://guides.rubyonrails.org.Conceptos Básicos de Active JobEsta guía te proporciona todo lo que necesitas para comenzar a crear, encolar y ejecutar trabajos en segundo plano.Después de leer esta guía, sabrás: Cómo crear trabajos. Cómo encolar trabajos. Cómo ejecutar trabajos en segundo plano. Cómo enviar correos electrónicos desde tu aplicación de manera asincrónica." />
<meta property="og:description" content="NO LEAS ESTE ARCHIVO EN GITHUB, LAS GUÍAS ESTÁN PUBLICADAS EN https://guides.rubyonrails.org.Conceptos Básicos de Active JobEsta guía te proporciona todo lo que necesitas para comenzar a crear, encolar y ejecutar trabajos en segundo plano.Después de leer esta guía, sabrás: Cómo crear trabajos. Cómo encolar trabajos. Cómo ejecutar trabajos en segundo plano. Cómo enviar correos electrónicos desde tu aplicación de manera asincrónica." />
<meta property="og:locale" content="en_US" />
<meta property="og:site_name" content="Ruby on Rails Guides" />
<meta property="og:image" content="https://avatars.githubusercontent.com/u/4223" />
<meta property="og:type" content="website" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+Arabic:[email protected]&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Heebo:[email protected]&family=Noto+Sans+Arabic:[email protected]&display=swap" rel="stylesheet">
<meta name="theme-color" content="#C81418">
</head>
<body class="guide">
<nav id="topNav" aria-label="Secondary">
<div class="wrapper">
<strong class="more-info-label">Más en <a href="https://rubyonrails.org/">rubyonrails.org:</a> </strong>
<span class="red-button more-info-button">
Más Ruby on Rails
</span>
<ul class="more-info-links s-hidden">
<li class="more-info"><a href="https://rubyonrails.org/blog">Blog</a></li>
<li class="more-info"><a href="https://guides.rubyonrails.org/">Guías</a></li>
<li class="more-info"><a href="https://api.rubyonrails.org/">API</a></li>
<li class="more-info"><a href="https://discuss.rubyonrails.org/">Foro</a></li>
<li class="more-info"><a href="https://github.com/rails/rails">Contribuir en GitHub</a></li>
</ul>
</div>
</nav>
<header id="page_header">
<div class="wrapper clearfix">
<nav id="feature_nav">
<div class="header-logo">
<a href="index.html" title="Regresar a la página principal de Guías para Edge">Guías</a>
<span id="version_switcher">
Versión:
<select class="guides-version">
<option value="https://edgeguides.rubyonrails.org/" selected>Edge</option>
<option value="https://guides.rubyonrails.org/v7.2/">7.2</option>
<option value="https://guides.rubyonrails.org/v7.1/">7.1</option>
<option value="https://guides.rubyonrails.org/v7.0/">7.0</option>
<option value="https://guides.rubyonrails.org/v6.1/">6.1</option>
<option value="https://guides.rubyonrails.org/v6.0/">6.0</option>
<option value="https://guides.rubyonrails.org/v5.2/">5.2</option>
<option value="https://guides.rubyonrails.org/v5.1/">5.1</option>
<option value="https://guides.rubyonrails.org/v5.0/">5.0</option>
<option value="https://guides.rubyonrails.org/v4.2/">4.2</option>
<option value="https://guides.rubyonrails.org/v4.1/">4.1</option>
<option value="https://guides.rubyonrails.org/v4.0/">4.0</option>
<option value="https://guides.rubyonrails.org/v3.2/">3.2</option>
<option value="https://guides.rubyonrails.org/v3.1/">3.1</option>
<option value="https://guides.rubyonrails.org/v3.0/">3.0</option>
<option value="https://guides.rubyonrails.org/v2.3/">2.3</option>
</select>
</span>
</div>
<ul class="nav">
<li><a class="nav-item" id="home_nav" href="https://rubyonrails.org/">Inicio</a></li>
<li class="guides-index guides-index-large">
<a href="index.html" id="guidesMenu" class="guides-index-item nav-item">Índice de Guías</a>
<div id="guides" class="clearfix" style="display: none;">
<hr />
<dl class="guides-section-container">
<div class="guides-section">
<dt>Comienza Aquí</dt>
<dd><a href="getting_started.html">Primeros Pasos con Rails</a></dd>
</div>
<div class="guides-section">
<dt>Modelos</dt>
<dd><a href="active_record_basics.html">Conceptos Básicos de Active Record</a></dd>
<dd><a href="active_record_migrations.html">Migraciones de Active Record</a></dd>
<dd><a href="active_record_validations.html">Validaciones de Active Record</a></dd>
</div>
<div class="guides-section">
<dt>Vistas</dt>
<dd><a href="action_view_overview.html">Resumen de Action View</a></dd>
<dd><a href="layouts_and_rendering.html">Diseños y Renderizado en Rails</a></dd>
</div>
<div class="guides-section">
<dt>Controladores</dt>
<dd><a href="action_controller_overview.html">Resumen de Action Controller</a></dd>
<dd><a href="routing.html">Enrutamiento en Rails desde el Exterior</a></dd>
</div>
<div class="guides-section">
<dt>Otros Componentes</dt>
<dd><a href="active_support_core_extensions.html">Extensiones Básicas de Active Support</a></dd>
<dd><a href="action_mailer_basics.html">Conceptos Básicos de Action Mailer</a></dd>
<dd><a href="action_mailbox_basics.html">Conceptos Básicos de Action Mailbox</a></dd>
<dd><a href="action_text_overview.html">Resumen de Action Text</a></dd>
<dd><a href="active_job_basics.html">Conceptos Básicos de Active Job</a></dd>
</div>
<div class="guides-section">
<dt>Políticas</dt>
<dd><a href="maintenance_policy.html">Política de Mantenimiento</a></dd>
</div>
<div class="guides-section">
<dt>Notas de Lanzamiento</dt>
<dd><a href="upgrading_ruby_on_rails.html">Actualizando Ruby on Rails</a></dd>
<dd><a href="7_2_release_notes.html">Versión 7.2 - ?</a></dd>
<dd><a href="7_1_release_notes.html">Versión 7.1 - Octubre 2023</a></dd>
<dd><a href="7_0_release_notes.html">Versión 7.0 - Diciembre 2021</a></dd>
<dd><a href="6_1_release_notes.html">Versión 6.1 - Diciembre 2020</a></dd>
</div>
</dl>
</div>
</li>
<li><a class="nav-item" href="contributing_to_ruby_on_rails.html">Contribuir</a></li>
<li class="guides-index guides-index-small">
<select class="guides-index-item nav-item">
<option value="index.html">Índice de Guías</option>
<optgroup label="Comienza Aquí">
<option value="getting_started.html">Primeros Pasos con Rails</option>
</optgroup>
<optgroup label="Modelos">
<option value="active_record_basics.html">Conceptos Básicos de Active Record</option>
<option value="active_record_migrations.html">Migraciones de Active Record</option>
<option value="active_record_validations.html">Validaciones de Active Record</option>
</optgroup>
<optgroup label="Vistas">
<option value="action_view_overview.html">Resumen de Action View</option>
<option value="layouts_and_rendering.html">Diseños y Renderizado en Rails</option>
</optgroup>
<optgroup label="Controladores">
<option value="action_controller_overview.html">Resumen de Action Controller</option>
<option value="routing.html">Enrutamiento en Rails desde el Exterior</option>
</optgroup>
<optgroup label="Otros Componentes">
<option value="active_support_core_extensions.html">Extensiones Básicas de Active Support</option>
<option value="action_mailer_basics.html">Conceptos Básicos de Action Mailer</option>
<option value="action_mailbox_basics.html">Conceptos Básicos de Action Mailbox</option>
<option value="action_text_overview.html">Resumen de Action Text</option>
<option value="active_job_basics.html">Conceptos Básicos de Active Job</option>
</optgroup>
<optgroup label="Políticas">
<option value="maintenance_policy.html">Política de Mantenimiento</option>
</optgroup>
<optgroup label="Notas de Lanzamiento">
<option value="upgrading_ruby_on_rails.html">Actualizando Ruby on Rails</option>
<option value="7_2_release_notes.html">Versión 7.2 - ?</option>
<option value="7_1_release_notes.html">Versión 7.1 - Octubre 2023</option>
<option value="7_0_release_notes.html">Versión 7.0 - Diciembre 2021</option>
<option value="6_1_release_notes.html">Versión 6.1 - Diciembre 2020</option>
</optgroup>
</select>
</li>
</ul>
</nav>
</div>
</header>
<hr class="hide" />
<section id="feature">
<div class="wrapper">
<p><strong>NO LEAS ESTE ARCHIVO EN GITHUB, LAS GUÍAS ESTÁN PUBLICADAS EN <a href="https://guides.rubyonrails.org">https://guides.rubyonrails.org</a>.</strong></p><h1>Conceptos Básicos de Active Job</h1><p>Esta guía te proporciona todo lo que necesitas para comenzar a crear,
encolar y ejecutar trabajos en segundo plano.</p><p>Después de leer esta guía, sabrás:</p>
<ul>
<li>Cómo crear trabajos.</li>
<li>Cómo encolar trabajos.</li>
<li>Cómo ejecutar trabajos en segundo plano.</li>
<li>Cómo enviar correos electrónicos desde tu aplicación de manera asincrónica.</li>
</ul>
<nav id="subCol">
<h3 class="chapter">
<picture>
<!-- Using the `source` HTML tag to set the dark theme image -->
<source
srcset="images/icon_book-close-bookmark-1-wht.svg"
media="(prefers-color-scheme: dark)"
/>
<img src="images/icon_book-close-bookmark-1.svg" alt="Chapter Icon" />
</picture>
Chapters
</h3>
<ol class="chapters">
<li><a href="#¿qué-es-active-job-questionmark">¿Qué es Active Job?</a></li>
<li><a href="#el-propósito-de-active-job">El Propósito de Active Job</a></li>
<li><a href="#crear-y-encolar-trabajos">Crear y Encolar Trabajos</a>
<ul>
<li><a href="#crear-el-trabajo">Crear el Trabajo</a></li>
<li><a href="#encolar-el-trabajo">Encolar el Trabajo</a></li>
<li><a href="#encolar-trabajos-en-masa">Encolar Trabajos en Masa</a></li>
</ul></li>
<li><a href="#ejecución-de-trabajos">Ejecución de Trabajos</a>
<ul>
<li><a href="#backends">Backends</a></li>
<li><a href="#configurando-el-backend">Configurando el Backend</a></li>
<li><a href="#iniciando-el-backend">Iniciando el Backend</a></li>
</ul></li>
<li><a href="#colas">Colas</a></li>
<li><a href="#prioridad">Prioridad</a></li>
<li><a href="#callbacks">Callbacks</a>
<ul>
<li><a href="#callbacks-disponibles">Callbacks Disponibles</a></li>
</ul></li>
<li><a href="#encolado-en-masa">Encolado en Masa</a>
<ul>
<li><a href="#encolar-múltiples-clases-de-active-job">Encolar Múltiples Clases de Active Job</a></li>
<li><a href="#callbacks-de-encolado-en-masa">Callbacks de Encolado en Masa</a></li>
<li><a href="#soporte-de-backend-de-cola">Soporte de Backend de Cola</a></li>
</ul></li>
<li><a href="#action-mailer">Action Mailer</a></li>
<li><a href="#internacionalización">Internacionalización</a></li>
<li><a href="#tipos-soportados-para-argumentos">Tipos Soportados para Argumentos</a>
<ul>
<li><a href="#globalid">GlobalID</a></li>
<li><a href="#serializadores">Serializadores</a></li>
</ul></li>
<li><a href="#excepciones">Excepciones</a>
<ul>
<li><a href="#reintentando-o-descartando-trabajos-fallidos">Reintentando o Descartando Trabajos Fallidos</a></li>
<li><a href="#deserialización">Deserialización</a></li>
</ul></li>
<li><a href="#pruebas-de-trabajos">Pruebas de Trabajos</a></li>
<li><a href="#depuración">Depuración</a></li>
</ol>
</nav>
<hr>
</div>
</section>
<main id="container">
<div class="wrapper">
<div id="mainCol">
<h2 id="¿qué-es-active-job-questionmark"><a class="anchorlink" href="#¿qué-es-active-job-questionmark"><span>1</span> ¿Qué es Active Job?</a></h2><p>Active Job es un marco para declarar trabajos y hacerlos funcionar en una variedad
de sistemas de cola. Estos trabajos pueden ser desde limpiezas programadas regularmente,
hasta cargos de facturación, o envíos de correos. Cualquier cosa que pueda dividirse
en pequeñas unidades de trabajo y ejecutarse en paralelo.</p><h2 id="el-propósito-de-active-job"><a class="anchorlink" href="#el-propósito-de-active-job"><span>2</span> El Propósito de Active Job</a></h2><p>El objetivo principal es asegurar que todas las aplicaciones Rails tengan una infraestructura
de trabajos establecida. De esta manera, podemos tener características del marco y otras gemas
construidas sobre eso, sin tener que preocuparnos por las diferencias de API entre varios
ejecutores de trabajos como Delayed Job y Resque. Elegir tu sistema de cola se convierte
más en una preocupación operativa. Y podrás cambiar entre ellos sin tener que reescribir
tus trabajos.</p><p>NOTA: Rails por defecto viene con una implementación de cola asincrónica que
ejecuta trabajos con un grupo de hilos en el mismo proceso. Los trabajos se ejecutarán
asincrónicamente, pero cualquier trabajo en la cola se perderá al reiniciar.</p><h2 id="crear-y-encolar-trabajos"><a class="anchorlink" href="#crear-y-encolar-trabajos"><span>3</span> Crear y Encolar Trabajos</a></h2><p>Esta sección proporcionará una guía paso a paso para crear un trabajo y encolarlo.</p><h3 id="crear-el-trabajo"><a class="anchorlink" href="#crear-el-trabajo"><span>3.1</span> Crear el Trabajo</a></h3><p>Active Job proporciona un generador de Rails para crear trabajos. Lo siguiente creará un
trabajo en <code>app/jobs</code> (con un caso de prueba adjunto en <code>test/jobs</code>):</p><div class="interstitial code">
<pre><code class="highlight console"><span class="gp">$</span><span class="w"> </span><span class="nb">bin/rails </span>generate job guests_cleanup
<span class="go">invoke test_unit
create test/jobs/guests_cleanup_job_test.rb
create app/jobs/guests_cleanup_job.rb
</span></code></pre>
<button class="clipboard-button" data-clipboard-text="bin/rails generate job guests_cleanup
">Copy</button>
</div>
<p>También puedes crear un trabajo que se ejecute en una cola específica:</p><div class="interstitial code">
<pre><code class="highlight console"><span class="gp">$</span><span class="w"> </span><span class="nb">bin/rails </span>generate job guests_cleanup <span class="nt">--queue</span> urgent
</code></pre>
<button class="clipboard-button" data-clipboard-text="bin/rails generate job guests_cleanup --queue urgent
">Copy</button>
</div>
<p>Si no quieres usar un generador, puedes crear tu propio archivo dentro de
<code>app/jobs</code>, solo asegúrate de que herede de <code>ApplicationJob</code>.</p><p>Así es como se ve un trabajo:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">GuestsCleanupJob</span> <span class="o"><</span> <span class="no">ApplicationJob</span>
<span class="n">queue_as</span> <span class="ss">:default</span>
<span class="k">def</span> <span class="nf">perform</span><span class="p">(</span><span class="o">*</span><span class="n">guests</span><span class="p">)</span>
<span class="c1"># Hacer algo después</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class GuestsCleanupJob < ApplicationJob
queue_as :default
def perform(*guests)
# Hacer algo después
end
end
">Copy</button>
</div>
<p>Ten en cuenta que puedes definir <code>perform</code> con tantos argumentos como desees.</p><p>Si ya tienes una clase abstracta y su nombre difiere de <code>ApplicationJob</code>, puedes pasar
la opción <code>--parent</code> para indicar que deseas una clase abstracta diferente:</p><div class="interstitial code">
<pre><code class="highlight console"><span class="gp">$</span><span class="w"> </span><span class="nb">bin/rails </span>generate job process_payment <span class="nt">--parent</span><span class="o">=</span>payment_job
</code></pre>
<button class="clipboard-button" data-clipboard-text="bin/rails generate job process_payment --parent=payment_job
">Copy</button>
</div>
<div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">ProcessPaymentJob</span> <span class="o"><</span> <span class="no">PaymentJob</span>
<span class="n">queue_as</span> <span class="ss">:default</span>
<span class="k">def</span> <span class="nf">perform</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span>
<span class="c1"># Hacer algo después</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class ProcessPaymentJob < PaymentJob
queue_as :default
def perform(*args)
# Hacer algo después
end
end
">Copy</button>
</div>
<h3 id="encolar-el-trabajo"><a class="anchorlink" href="#encolar-el-trabajo"><span>3.2</span> Encolar el Trabajo</a></h3><p>Encola un trabajo usando <a href="https://edgeapi.rubyonrails.org/classes/ActiveJob/Enqueuing/ClassMethods.html#method-i-perform_later"><code>perform_later</code></a> y, opcionalmente, <a href="https://edgeapi.rubyonrails.org/classes/ActiveJob/Core/ClassMethods.html#method-i-set"><code>set</code></a>. De esta manera:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># Encola un trabajo para que se realice tan pronto como el sistema de colas esté</span>
<span class="c1"># libre.</span>
<span class="no">GuestsCleanupJob</span><span class="p">.</span><span class="nf">perform_later</span> <span class="n">guest</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="# Encola un trabajo para que se realice tan pronto como el sistema de colas esté
# libre.
GuestsCleanupJob.perform_later guest
">Copy</button>
</div>
<div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># Encola un trabajo para que se realice mañana al mediodía.</span>
<span class="no">GuestsCleanupJob</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="ss">wait_until: </span><span class="no">Date</span><span class="p">.</span><span class="nf">tomorrow</span><span class="p">.</span><span class="nf">noon</span><span class="p">).</span><span class="nf">perform_later</span><span class="p">(</span><span class="n">guest</span><span class="p">)</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="# Encola un trabajo para que se realice mañana al mediodía.
GuestsCleanupJob.set(wait_until: Date.tomorrow.noon).perform_later(guest)
">Copy</button>
</div>
<div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># Encola un trabajo para que se realice dentro de 1 semana.</span>
<span class="no">GuestsCleanupJob</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="ss">wait: </span><span class="mi">1</span><span class="p">.</span><span class="nf">week</span><span class="p">).</span><span class="nf">perform_later</span><span class="p">(</span><span class="n">guest</span><span class="p">)</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="# Encola un trabajo para que se realice dentro de 1 semana.
GuestsCleanupJob.set(wait: 1.week).perform_later(guest)
">Copy</button>
</div>
<div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># `perform_now` y `perform_later` llamarán a `perform` internamente, por lo que</span>
<span class="c1"># puedes pasar tantos argumentos como se definan en este último.</span>
<span class="no">GuestsCleanupJob</span><span class="p">.</span><span class="nf">perform_later</span><span class="p">(</span><span class="n">guest1</span><span class="p">,</span> <span class="n">guest2</span><span class="p">,</span> <span class="ss">filter: </span><span class="s1">'some_filter'</span><span class="p">)</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="# `perform_now` y `perform_later` llamarán a `perform` internamente, por lo que
# puedes pasar tantos argumentos como se definan en este último.
GuestsCleanupJob.perform_later(guest1, guest2, filter: 'some_filter')
">Copy</button>
</div>
<p>¡Eso es todo!</p><h3 id="encolar-trabajos-en-masa"><a class="anchorlink" href="#encolar-trabajos-en-masa"><span>3.3</span> Encolar Trabajos en Masa</a></h3><p>Puedes encolar múltiples trabajos a la vez usando <a href="https://edgeapi.rubyonrails.org/classes/ActiveJob.html#method-c-perform_all_later"><code>perform_all_later</code></a>. Para más detalles, consulta <a href="#encolado-en-masa">Encolado en Masa</a>.</p><h2 id="ejecución-de-trabajos"><a class="anchorlink" href="#ejecución-de-trabajos"><span>4</span> Ejecución de Trabajos</a></h2><p>Para encolar y ejecutar trabajos en producción necesitas configurar un sistema de cola,
es decir, debes decidir sobre una librería de cola de terceros que Rails debería usar.
Rails en sí solo proporciona un sistema de colas en el mismo proceso, que solo mantiene
los trabajos en RAM. Si el proceso falla o la máquina se reinicia, entonces todos los
trabajos pendientes se pierden con el backend asincrónico por defecto. Esto puede ser
suficiente para aplicaciones más pequeñas o trabajos no críticos, pero la mayoría de las
aplicaciones en producción necesitarán elegir un backend persistente.</p><h3 id="backends"><a class="anchorlink" href="#backends"><span>4.1</span> Backends</a></h3><p>Active Job tiene adaptadores integrados para múltiples backends de cola (Sidekiq,
Resque, Delayed Job, y otros). Para obtener una lista actualizada de los adaptadores,
consulta la Documentación de la API para <a href="https://edgeapi.rubyonrails.org/classes/ActiveJob/QueueAdapters.html"><code>ActiveJob::QueueAdapters</code></a>.</p><h3 id="configurando-el-backend"><a class="anchorlink" href="#configurando-el-backend"><span>4.2</span> Configurando el Backend</a></h3><p>Puedes configurar fácilmente tu backend de cola con <a href="configuring.html#config-active-job-queue-adapter"><code>config.active_job.queue_adapter</code></a>:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># config/application.rb</span>
<span class="k">module</span> <span class="nn">YourApp</span>
<span class="k">class</span> <span class="nc">Application</span> <span class="o"><</span> <span class="no">Rails</span><span class="o">::</span><span class="no">Application</span>
<span class="c1"># Asegúrate de tener la gema del adaptador en tu Gemfile</span>
<span class="c1"># y sigue las instrucciones específicas de instalación</span>
<span class="c1"># y despliegue del adaptador.</span>
<span class="n">config</span><span class="p">.</span><span class="nf">active_job</span><span class="p">.</span><span class="nf">queue_adapter</span> <span class="o">=</span> <span class="ss">:sidekiq</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="module YourApp
class Application < Rails::Application
# Asegúrate de tener la gema del adaptador en tu Gemfile
# y sigue las instrucciones específicas de instalación
# y despliegue del adaptador.
config.active_job.queue_adapter = :sidekiq
end
end
">Copy</button>
</div>
<p>También puedes configurar tu backend por trabajo:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">GuestsCleanupJob</span> <span class="o"><</span> <span class="no">ApplicationJob</span>
<span class="nb">self</span><span class="p">.</span><span class="nf">queue_adapter</span> <span class="o">=</span> <span class="ss">:resque</span>
<span class="c1"># ...</span>
<span class="k">end</span>
<span class="c1"># Ahora tu trabajo usará `resque` como su adaptador de cola backend, sobrescribiendo lo que</span>
<span class="c1"># fue configurado en `config.active_job.queue_adapter`.</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class GuestsCleanupJob < ApplicationJob
self.queue_adapter = :resque
# ...
end
# Ahora tu trabajo usará `resque` como su adaptador de cola backend, sobrescribiendo lo que
# fue configurado en `config.active_job.queue_adapter`.
">Copy</button>
</div>
<h3 id="iniciando-el-backend"><a class="anchorlink" href="#iniciando-el-backend"><span>4.3</span> Iniciando el Backend</a></h3><p>Dado que los trabajos se ejecutan en paralelo a tu aplicación Rails, la mayoría de las librerías
de colas requieren que inicies un servicio de cola específico de la librería (además de
iniciar tu aplicación Rails) para que el procesamiento de trabajos funcione. Consulta la
documentación de la librería para instrucciones sobre cómo iniciar tu backend de cola.</p><p>Aquí hay una lista no exhaustiva de documentación:</p>
<ul>
<li><a href="https://github.com/mperham/sidekiq/wiki/Active-Job">Sidekiq</a></li>
<li><a href="https://github.com/resque/resque/wiki/ActiveJob">Resque</a></li>
<li><a href="https://github.com/jondot/sneakers/wiki/How-To:-Rails-Background-Jobs-with-ActiveJob">Sneakers</a></li>
<li><a href="https://github.com/brandonhilkert/sucker_punch#active-job">Sucker Punch</a></li>
<li><a href="https://github.com/QueueClassic/queue_classic#active-job">Queue Classic</a></li>
<li><a href="https://github.com/collectiveidea/delayed_job#active-job">Delayed Job</a></li>
<li><a href="https://github.com/que-rb/que#additional-rails-specific-setup">Que</a></li>
<li><a href="https://github.com/bensheldon/good_job#readme">Good Job</a></li>
<li><a href="https://github.com/rails/solid_queue?tab=readme-ov-file#solid-queue">Solid Queue</a></li>
</ul>
<h2 id="colas"><a class="anchorlink" href="#colas"><span>5</span> Colas</a></h2><p>La mayoría de los adaptadores soportan múltiples colas. Con Active Job puedes programar
el trabajo para que se ejecute en una cola específica usando <a href="https://edgeapi.rubyonrails.org/classes/ActiveJob/QueueName/ClassMethods.html#method-i-queue_as"><code>queue_as</code></a>:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">GuestsCleanupJob</span> <span class="o"><</span> <span class="no">ApplicationJob</span>
<span class="n">queue_as</span> <span class="ss">:low_priority</span>
<span class="c1"># ...</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class GuestsCleanupJob < ApplicationJob
queue_as :low_priority
# ...
end
">Copy</button>
</div>
<p>Puedes prefijar el nombre de la cola para todos tus trabajos usando
<a href="configuring.html#config-active-job-queue-name-prefix"><code>config.active_job.queue_name_prefix</code></a> en <code>application.rb</code>:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># config/application.rb</span>
<span class="k">module</span> <span class="nn">YourApp</span>
<span class="k">class</span> <span class="nc">Application</span> <span class="o"><</span> <span class="no">Rails</span><span class="o">::</span><span class="no">Application</span>
<span class="n">config</span><span class="p">.</span><span class="nf">active_job</span><span class="p">.</span><span class="nf">queue_name_prefix</span> <span class="o">=</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">env</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="module YourApp
class Application < Rails::Application
config.active_job.queue_name_prefix = Rails.env
end
end
">Copy</button>
</div>
<div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># app/jobs/guests_cleanup_job.rb</span>
<span class="k">class</span> <span class="nc">GuestsCleanupJob</span> <span class="o"><</span> <span class="no">ApplicationJob</span>
<span class="n">queue_as</span> <span class="ss">:low_priority</span>
<span class="c1"># ...</span>
<span class="k">end</span>
<span class="c1"># Ahora tu trabajo se ejecutará en la cola production_low_priority en tu</span>
<span class="c1"># entorno de producción y en staging_low_priority</span>
<span class="c1"># en tu entorno de pruebas</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class GuestsCleanupJob < ApplicationJob
queue_as :low_priority
# ...
end
# Ahora tu trabajo se ejecutará en la cola production_low_priority en tu
# entorno de producción y en staging_low_priority
# en tu entorno de pruebas
">Copy</button>
</div>
<p>También puedes configurar el prefijo por trabajo.</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">GuestsCleanupJob</span> <span class="o"><</span> <span class="no">ApplicationJob</span>
<span class="n">queue_as</span> <span class="ss">:low_priority</span>
<span class="nb">self</span><span class="p">.</span><span class="nf">queue_name_prefix</span> <span class="o">=</span> <span class="kp">nil</span>
<span class="c1"># ...</span>
<span class="k">end</span>
<span class="c1"># Ahora la cola de tu trabajo no tendrá prefijo, sobrescribiendo lo que</span>
<span class="c1"># fue configurado en `config.active_job.queue_name_prefix`.</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class GuestsCleanupJob < ApplicationJob
queue_as :low_priority
self.queue_name_prefix = nil
# ...
end
# Ahora la cola de tu trabajo no tendrá prefijo, sobrescribiendo lo que
# fue configurado en `config.active_job.queue_name_prefix`.
">Copy</button>
</div>
<p>El delimitador por defecto del prefijo del nombre de la cola es '_'. Esto se puede cambiar configurando
<a href="configuring.html#config-active-job-queue-name-delimiter"><code>config.active_job.queue_name_delimiter</code></a> en <code>application.rb</code>:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># config/application.rb</span>
<span class="k">module</span> <span class="nn">YourApp</span>
<span class="k">class</span> <span class="nc">Application</span> <span class="o"><</span> <span class="no">Rails</span><span class="o">::</span><span class="no">Application</span>
<span class="n">config</span><span class="p">.</span><span class="nf">active_job</span><span class="p">.</span><span class="nf">queue_name_prefix</span> <span class="o">=</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">env</span>
<span class="n">config</span><span class="p">.</span><span class="nf">active_job</span><span class="p">.</span><span class="nf">queue_name_delimiter</span> <span class="o">=</span> <span class="s1">'.'</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="module YourApp
class Application < Rails::Application
config.active_job.queue_name_prefix = Rails.env
config.active_job.queue_name_delimiter = '.'
end
end
">Copy</button>
</div>
<div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># app/jobs/guests_cleanup_job.rb</span>
<span class="k">class</span> <span class="nc">GuestsCleanupJob</span> <span class="o"><</span> <span class="no">ApplicationJob</span>
<span class="n">queue_as</span> <span class="ss">:low_priority</span>
<span class="c1"># ...</span>
<span class="k">end</span>
<span class="c1"># Ahora tu trabajo se ejecutará en la cola production.low_priority en tu</span>
<span class="c1"># entorno de producción y en staging.low_priority</span>
<span class="c1"># en tu entorno de pruebas</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class GuestsCleanupJob < ApplicationJob
queue_as :low_priority
# ...
end
# Ahora tu trabajo se ejecutará en la cola production.low_priority en tu
# entorno de producción y en staging.low_priority
# en tu entorno de pruebas
">Copy</button>
</div>
<p>Para controlar la cola desde el nivel del trabajo, puedes pasar un bloque a <code>queue_as</code>. El
bloque se ejecutará en el contexto del trabajo (por lo que puede acceder a <code>self.arguments</code>),
y debe devolver el nombre de la cola:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">ProcessVideoJob</span> <span class="o"><</span> <span class="no">ApplicationJob</span>
<span class="n">queue_as</span> <span class="k">do</span>
<span class="n">video</span> <span class="o">=</span> <span class="nb">self</span><span class="p">.</span><span class="nf">arguments</span><span class="p">.</span><span class="nf">first</span>
<span class="k">if</span> <span class="n">video</span><span class="p">.</span><span class="nf">owner</span><span class="p">.</span><span class="nf">premium?</span>
<span class="ss">:premium_videojobs</span>
<span class="k">else</span>
<span class="ss">:videojobs</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">perform</span><span class="p">(</span><span class="n">video</span><span class="p">)</span>
<span class="c1"># Procesar video</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class ProcessVideoJob < ApplicationJob
queue_as do
video = self.arguments.first
if video.owner.premium?
:premium_videojobs
else
:videojobs
end
end
def perform(video)
# Procesar video
end
end
">Copy</button>
</div>
<div class="interstitial code">
<pre><code class="highlight ruby"><span class="no">ProcessVideoJob</span><span class="p">.</span><span class="nf">perform_later</span><span class="p">(</span><span class="no">Video</span><span class="p">.</span><span class="nf">last</span><span class="p">)</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="ProcessVideoJob.perform_later(Video.last)
">Copy</button>
</div>
<p>Si deseas tener más control sobre en qué cola se ejecutará un trabajo, puedes pasar una opción <code>:queue</code>
a <code>set</code>:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="no">MyJob</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="ss">queue: :another_queue</span><span class="p">).</span><span class="nf">perform_later</span><span class="p">(</span><span class="n">record</span><span class="p">)</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="MyJob.set(queue: :another_queue).perform_later(record)
">Copy</button>
</div>
<p>NOTA: Asegúrate de que tu backend de cola "escuche" en el nombre de tu cola. Para algunos
backends necesitas especificar las colas a las que escuchar.</p><h2 id="prioridad"><a class="anchorlink" href="#prioridad"><span>6</span> Prioridad</a></h2><p>Algunos adaptadores soportan prioridades a nivel de trabajo, donde los trabajos pueden ser priorizados en relación con otros en la cola o a través de todas las colas.</p><p>Puedes programar un trabajo para que se ejecute con una prioridad específica usando <a href="https://edgeapi.rubyonrails.org/classes/ActiveJob/QueuePriority/ClassMethods.html#method-i-queue_with_priority"><code>queue_with_priority</code></a>:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">GuestsCleanupJob</span> <span class="o"><</span> <span class="no">ApplicationJob</span>
<span class="n">queue_with_priority</span> <span class="mi">10</span>
<span class="c1"># ...</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class GuestsCleanupJob < ApplicationJob
queue_with_priority 10
# ...
end
">Copy</button>
</div>
<p>Ten en cuenta que esto no tendrá ningún efecto con adaptadores que no soporten prioridades.</p><p>Similar a <code>queue_as</code>, también puedes pasar un bloque a <code>queue_with_priority</code> para que sea evaluado en el contexto del trabajo:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">ProcessVideoJob</span> <span class="o"><</span> <span class="no">ApplicationJob</span>
<span class="n">queue_with_priority</span> <span class="k">do</span>
<span class="n">video</span> <span class="o">=</span> <span class="nb">self</span><span class="p">.</span><span class="nf">arguments</span><span class="p">.</span><span class="nf">first</span>
<span class="k">if</span> <span class="n">video</span><span class="p">.</span><span class="nf">owner</span><span class="p">.</span><span class="nf">premium?</span>
<span class="mi">0</span>
<span class="k">else</span>
<span class="mi">10</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">perform</span><span class="p">(</span><span class="n">video</span><span class="p">)</span>
<span class="c1"># Procesar video</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class ProcessVideoJob < ApplicationJob
queue_with_priority do
video = self.arguments.first
if video.owner.premium?
0
else
10
end
end
def perform(video)
# Procesar video
end
end
">Copy</button>
</div>
<div class="interstitial code">
<pre><code class="highlight ruby"><span class="no">ProcessVideoJob</span><span class="p">.</span><span class="nf">perform_later</span><span class="p">(</span><span class="no">Video</span><span class="p">.</span><span class="nf">last</span><span class="p">)</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="ProcessVideoJob.perform_later(Video.last)
">Copy</button>
</div>
<p>También puedes pasar una opción <code>:priority</code> a <code>set</code>:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="no">MyJob</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="ss">priority: </span><span class="mi">50</span><span class="p">).</span><span class="nf">perform_later</span><span class="p">(</span><span class="n">record</span><span class="p">)</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="MyJob.set(priority: 50).perform_later(record)
">Copy</button>
</div>
<p>NOTA: Si un número de prioridad más bajo se ejecuta antes o después de un número de prioridad más alto depende de la
implementación del adaptador. Consulta la documentación de tu backend para obtener más información.
Se anima a los autores de adaptadores a tratar un número más bajo como más importante.</p><h2 id="callbacks"><a class="anchorlink" href="#callbacks"><span>7</span> Callbacks</a></h2><p>Active Job proporciona ganchos para activar lógica durante el ciclo de vida de un trabajo. Al igual
que otros callbacks en Rails, puedes implementar los callbacks como métodos ordinarios
y usar un método de clase estilo macro para registrarlos como callbacks:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">GuestsCleanupJob</span> <span class="o"><</span> <span class="no">ApplicationJob</span>
<span class="n">queue_as</span> <span class="ss">:default</span>
<span class="n">around_perform</span> <span class="ss">:around_cleanup</span>
<span class="k">def</span> <span class="nf">perform</span>
<span class="c1"># Hacer algo después</span>
<span class="k">end</span>
<span class="kp">private</span>
<span class="k">def</span> <span class="nf">around_cleanup</span>
<span class="c1"># Hacer algo antes de perform</span>
<span class="k">yield</span>
<span class="c1"># Hacer algo después de perform</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class GuestsCleanupJob < ApplicationJob
queue_as :default
around_perform :around_cleanup
def perform
# Hacer algo después
end
private
def around_cleanup
# Hacer algo antes de perform
yield
# Hacer algo después de perform
end
end
">Copy</button>
</div>
<p>Los métodos de clase estilo macro también pueden recibir un bloque. Considera usar este
estilo si el código dentro de tu bloque es tan corto que cabe en una sola línea.
Por ejemplo, podrías enviar métricas para cada trabajo encolado:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">ApplicationJob</span> <span class="o"><</span> <span class="no">ActiveJob</span><span class="o">::</span><span class="no">Base</span>
<span class="n">before_enqueue</span> <span class="p">{</span> <span class="o">|</span><span class="n">job</span><span class="o">|</span> <span class="vg">$statsd</span><span class="p">.</span><span class="nf">increment</span> <span class="s2">"</span><span class="si">#{</span><span class="n">job</span><span class="p">.</span><span class="nf">class</span><span class="p">.</span><span class="nf">name</span><span class="p">.</span><span class="nf">underscore</span><span class="si">}</span><span class="s2">.enqueue"</span> <span class="p">}</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class ApplicationJob < ActiveJob::Base
before_enqueue { |job| $statsd.increment "#{job.class.name.underscore}.enqueue" }
end
">Copy</button>
</div>
<h3 id="callbacks-disponibles"><a class="anchorlink" href="#callbacks-disponibles"><span>7.1</span> Callbacks Disponibles</a></h3>
<ul>
<li><a href="https://edgeapi.rubyonrails.org/classes/ActiveJob/Callbacks/ClassMethods.html#method-i-before_enqueue"><code>before_enqueue</code></a></li>
<li><a href="https://edgeapi.rubyonrails.org/classes/ActiveJob/Callbacks/ClassMethods.html#method-i-around_enqueue"><code>around_enqueue</code></a></li>
<li><a href="https://edgeapi.rubyonrails.org/classes/ActiveJob/Callbacks/ClassMethods.html#method-i-after_enqueue"><code>after_enqueue</code></a></li>
<li><a href="https://edgeapi.rubyonrails.org/classes/ActiveJob/Callbacks/ClassMethods.html#method-i-before_perform"><code>before_perform</code></a></li>
<li><a href="https://edgeapi.rubyonrails.org/classes/ActiveJob/Callbacks/ClassMethods.html#method-i-around_perform"><code>around_perform</code></a></li>
<li><a href="https://edgeapi.rubyonrails.org/classes/ActiveJob/Callbacks/ClassMethods.html#method-i-after_perform"><code>after_perform</code></a></li>
</ul>
<p>Ten en cuenta que cuando encolas trabajos en masa usando <code>perform_all_later</code>,
callbacks como <code>around_enqueue</code> no se activarán en los trabajos individuales.
Consulta <a href="#callbacks-de-encolado-en-masa">Callbacks de Encolado en Masa</a>.</p><h2 id="encolado-en-masa"><a class="anchorlink" href="#encolado-en-masa"><span>8</span> Encolado en Masa</a></h2><p>Puedes encolar múltiples trabajos a la vez usando
<a href="https://edgeapi.rubyonrails.org/classes/ActiveJob.html#method-c-perform_all_later"><code>perform_all_later</code></a>.
El encolado en masa reduce el número de viajes de ida y vuelta al almacén de datos de la cola (como
Redis o una base de datos), haciéndolo una operación más eficiente que encolar los
mismos trabajos individualmente.</p><p><code>perform_all_later</code> es una API de nivel superior en Active Job. Acepta instancias
de trabajos como argumentos (ten en cuenta que esto es diferente de <code>perform_later</code>).
<code>perform_all_later</code> llama a <code>perform</code> internamente. Los argumentos pasados a
<code>new</code> se pasarán a <code>perform</code> cuando finalmente se llame.</p><p>Aquí tienes un ejemplo llamando a <code>perform_all_later</code> con instancias de <code>GuestCleanupJob</code>:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># Crear trabajos para pasar a `perform_all_later`.</span>
<span class="c1"># Los argumentos para `new` se pasan a `perform`</span>
<span class="n">guest_cleanup_jobs</span> <span class="o">=</span> <span class="no">Guest</span><span class="p">.</span><span class="nf">all</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">guest</span><span class="o">|</span> <span class="no">GuestsCleanupJob</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">guest</span><span class="p">)</span> <span class="p">}</span>
<span class="c1"># Encolará un trabajo separado para cada instancia de `GuestCleanupJob`</span>
<span class="no">ActiveJob</span><span class="p">.</span><span class="nf">perform_all_later</span><span class="p">(</span><span class="n">guest_cleanup_jobs</span><span class="p">)</span>
<span class="c1"># También puedes usar el método `set` para configurar opciones antes de encolar trabajos en masa.</span>
<span class="n">guest_cleanup_jobs</span> <span class="o">=</span> <span class="no">Guest</span><span class="p">.</span><span class="nf">all</span><span class="p">.</span><span class="nf">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">guest</span><span class="o">|</span> <span class="no">GuestsCleanupJob</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">guest</span><span class="p">).</span><span class="nf">set</span><span class="p">(</span><span class="ss">wait: </span><span class="mi">1</span><span class="p">.</span><span class="nf">day</span><span class="p">)</span> <span class="p">}</span>
<span class="no">ActiveJob</span><span class="p">.</span><span class="nf">perform_all_later</span><span class="p">(</span><span class="n">guest_cleanup_jobs</span><span class="p">)</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="# Crear trabajos para pasar a `perform_all_later`.
# Los argumentos para `new` se pasan a `perform`
guest_cleanup_jobs = Guest.all.map { |guest| GuestsCleanupJob.new(guest) }
# Encolará un trabajo separado para cada instancia de `GuestCleanupJob`
ActiveJob.perform_all_later(guest_cleanup_jobs)
# También puedes usar el método `set` para configurar opciones antes de encolar trabajos en masa.
guest_cleanup_jobs = Guest.all.map { |guest| GuestsCleanupJob.new(guest).set(wait: 1.day) }
ActiveJob.perform_all_later(guest_cleanup_jobs)
">Copy</button>
</div>
<p><code>perform_all_later</code> registra el número de trabajos encolados con éxito, por ejemplo,
si <code>Guest.all.map</code> arriba resultó en 3 <code>guest_cleanup_jobs</code>, registraría
<code>Enqueued 3 jobs to Async (3 GuestsCleanupJob)</code> (asumiendo que todos fueron encolados).</p><p>El valor de retorno de <code>perform_all_later</code> es <code>nil</code>. Ten en cuenta que esto es diferente
de <code>perform_later</code>, que devuelve la instancia de la clase de trabajo encolada.</p><h3 id="encolar-múltiples-clases-de-active-job"><a class="anchorlink" href="#encolar-múltiples-clases-de-active-job"><span>8.1</span> Encolar Múltiples Clases de Active Job</a></h3><p>Con <code>perform_all_later</code>, también es posible encolar diferentes instancias de clases de Active Job
en la misma llamada. Por ejemplo:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">ExportDataJob</span> <span class="o"><</span> <span class="no">ApplicationJob</span>
<span class="k">def</span> <span class="nf">perform</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span>
<span class="c1"># Exportar datos</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">NotifyGuestsJob</span> <span class="o"><</span> <span class="no">ApplicationJob</span>
<span class="k">def</span> <span class="nf">perform</span><span class="p">(</span><span class="o">*</span><span class="n">guests</span><span class="p">)</span>
<span class="c1"># Enviar correos a los invitados</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># Instanciar instancias de trabajos</span>
<span class="n">cleanup_job</span> <span class="o">=</span> <span class="no">GuestsCleanupJob</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">guest</span><span class="p">)</span>
<span class="n">export_job</span> <span class="o">=</span> <span class="no">ExportDataJob</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="n">notify_job</span> <span class="o">=</span> <span class="no">NotifyGuestsJob</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">guest</span><span class="p">)</span>
<span class="c1"># Encola instancias de trabajos de múltiples clases a la vez</span>
<span class="no">ActiveJob</span><span class="p">.</span><span class="nf">perform_all_later</span><span class="p">(</span><span class="n">cleanup_job</span><span class="p">,</span> <span class="n">export_job</span><span class="p">,</span> <span class="n">notify_job</span><span class="p">)</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class ExportDataJob < ApplicationJob
def perform(*args)
# Exportar datos
end
end
class NotifyGuestsJob < ApplicationJob
def perform(*guests)
# Enviar correos a los invitados
end
end
# Instanciar instancias de trabajos
cleanup_job = GuestsCleanupJob.new(guest)
export_job = ExportDataJob.new(data)
notify_job = NotifyGuestsJob.new(guest)
# Encola instancias de trabajos de múltiples clases a la vez
ActiveJob.perform_all_later(cleanup_job, export_job, notify_job)
">Copy</button>
</div>
<h3 id="callbacks-de-encolado-en-masa"><a class="anchorlink" href="#callbacks-de-encolado-en-masa"><span>8.2</span> Callbacks de Encolado en Masa</a></h3><p>Cuando encolas trabajos en masa usando <code>perform_all_later</code>, callbacks como
<code>around_enqueue</code> no se activarán en los trabajos individuales. Este comportamiento está
en línea con otros métodos en masa de Active Record. Dado que los callbacks se ejecutan en trabajos individuales,
no pueden aprovechar la naturaleza en masa de este método.</p><p>Sin embargo, el método
<a href="https://edgeapi.rubyonrails.org/classes/ActiveJob/Core.html#method-i-successfully_enqueued-3F"><code>successfully_enqueued?</code></a>
puede usarse para averiguar si un trabajo dado fue encolado con éxito.</p><h3 id="soporte-de-backend-de-cola"><a class="anchorlink" href="#soporte-de-backend-de-cola"><span>8.3</span> Soporte de Backend de Cola</a></h3><p>Para <code>perform_all_later</code>, el encolado en masa necesita ser respaldado por el <a href="#backends">backend de cola</a>.</p><p>Por ejemplo, Sidekiq tiene un método <code>push_bulk</code>, que puede enviar una gran cantidad de
trabajos a Redis y prevenir la latencia de red de ida y vuelta. GoodJob también soporta
el encolado en masa con el método <code>GoodJob::Bulk.enqueue</code>. El nuevo backend de cola
<a href="https://github.com/rails/solid_queue/pull/93"><code>Solid Queue</code></a> ha añadido
soporte para el encolado en masa también.</p><p>Si el backend de cola <em>no</em> soporta el encolado en masa, <code>perform_all_later</code> encolará
trabajos uno por uno.</p><h2 id="action-mailer"><a class="anchorlink" href="#action-mailer"><span>9</span> Action Mailer</a></h2><p>Uno de los trabajos más comunes en una aplicación web moderna es enviar correos electrónicos fuera
del ciclo de solicitud-respuesta, para que el usuario no tenga que esperar. Active Job
está integrado con Action Mailer para que puedas enviar correos electrónicos fácilmente de manera asincrónica:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># Si deseas enviar el correo ahora usa #deliver_now</span>
<span class="no">UserMailer</span><span class="p">.</span><span class="nf">welcome</span><span class="p">(</span><span class="vi">@user</span><span class="p">).</span><span class="nf">deliver_now</span>
<span class="c1"># Si deseas enviar el correo a través de Active Job usa #deliver_later</span>
<span class="no">UserMailer</span><span class="p">.</span><span class="nf">welcome</span><span class="p">(</span><span class="vi">@user</span><span class="p">).</span><span class="nf">deliver_later</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="# Si deseas enviar el correo ahora usa #deliver_now
UserMailer.welcome(@user).deliver_now
# Si deseas enviar el correo a través de Active Job usa #deliver_later
UserMailer.welcome(@user).deliver_later
">Copy</button>
</div>
<p>NOTA: Usar la cola asincrónica desde una tarea Rake (por ejemplo, para
enviar un correo usando <code>.deliver_later</code>) generalmente no funcionará porque Rake
probablemente terminará, causando que el grupo de hilos en el mismo proceso sea eliminado,
antes de que se procesen todos/alguno de los correos <code>.deliver_later</code>. Para evitar este problema, usa
<code>.deliver_now</code> o ejecuta una cola persistente en desarrollo.</p><h2 id="internacionalización"><a class="anchorlink" href="#internacionalización"><span>10</span> Internacionalización</a></h2><p>Cada trabajo usa el <code>I18n.locale</code> establecido cuando se creó el trabajo. Esto es útil si envías
correos electrónicos de manera asincrónica:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="no">I18n</span><span class="p">.</span><span class="nf">locale</span> <span class="o">=</span> <span class="ss">:eo</span>
<span class="no">UserMailer</span><span class="p">.</span><span class="nf">welcome</span><span class="p">(</span><span class="vi">@user</span><span class="p">).</span><span class="nf">deliver_later</span> <span class="c1"># El correo será localizado a esperanto.</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="I18n.locale = :eo
UserMailer.welcome(@user).deliver_later # El correo será localizado a esperanto.
">Copy</button>
</div>
<h2 id="tipos-soportados-para-argumentos"><a class="anchorlink" href="#tipos-soportados-para-argumentos"><span>11</span> Tipos Soportados para Argumentos</a></h2><p>ActiveJob soporta los siguientes tipos de argumentos por defecto:</p>
<ul>
<li>Tipos básicos (<code>NilClass</code>, <code>String</code>, <code>Integer</code>, <code>Float</code>, <code>BigDecimal</code>, <code>TrueClass</code>, <code>FalseClass</code>)</li>
<li><code>Symbol</code></li>
<li><code>Date</code></li>
<li><code>Time</code></li>
<li><code>DateTime</code></li>
<li><code>ActiveSupport::TimeWithZone</code></li>
<li><code>ActiveSupport::Duration</code></li>
<li><code>Hash</code> (Las claves deben ser de tipo <code>String</code> o <code>Symbol</code>)</li>
<li><code>ActiveSupport::HashWithIndifferentAccess</code></li>
<li><code>Array</code></li>
<li><code>Range</code></li>
<li><code>Module</code></li>
<li><code>Class</code></li>
</ul>
<h3 id="globalid"><a class="anchorlink" href="#globalid"><span>11.1</span> GlobalID</a></h3><p>Active Job soporta <a href="https://github.com/rails/globalid/blob/main/README.md">GlobalID</a> para parámetros. Esto hace posible pasar
objetos Active Record en vivo a tu trabajo en lugar de pares clase/id, que luego tienes
que deserializar manualmente. Antes, los trabajos se verían así:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">TrashableCleanupJob</span> <span class="o"><</span> <span class="no">ApplicationJob</span>
<span class="k">def</span> <span class="nf">perform</span><span class="p">(</span><span class="n">trashable_class</span><span class="p">,</span> <span class="n">trashable_id</span><span class="p">,</span> <span class="n">depth</span><span class="p">)</span>
<span class="n">trashable</span> <span class="o">=</span> <span class="n">trashable_class</span><span class="p">.</span><span class="nf">constantize</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="n">trashable_id</span><span class="p">)</span>
<span class="n">trashable</span><span class="p">.</span><span class="nf">cleanup</span><span class="p">(</span><span class="n">depth</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class TrashableCleanupJob < ApplicationJob
def perform(trashable_class, trashable_id, depth)
trashable = trashable_class.constantize.find(trashable_id)
trashable.cleanup(depth)
end
end
">Copy</button>
</div>
<p>Ahora simplemente puedes hacer:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">TrashableCleanupJob</span> <span class="o"><</span> <span class="no">ApplicationJob</span>
<span class="k">def</span> <span class="nf">perform</span><span class="p">(</span><span class="n">trashable</span><span class="p">,</span> <span class="n">depth</span><span class="p">)</span>
<span class="n">trashable</span><span class="p">.</span><span class="nf">cleanup</span><span class="p">(</span><span class="n">depth</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class TrashableCleanupJob < ApplicationJob
def perform(trashable, depth)
trashable.cleanup(depth)
end
end
">Copy</button>
</div>
<p>Esto funciona con cualquier clase que mezcle <code>GlobalID::Identification</code>, que
por defecto se ha mezclado en las clases Active Record.</p><h3 id="serializadores"><a class="anchorlink" href="#serializadores"><span>11.2</span> Serializadores</a></h3><p>Puedes extender la lista de tipos de argumentos soportados. Solo necesitas definir tu propio serializador:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># app/serializers/money_serializer.rb</span>
<span class="k">class</span> <span class="nc">MoneySerializer</span> <span class="o"><</span> <span class="no">ActiveJob</span><span class="o">::</span><span class="no">Serializers</span><span class="o">::</span><span class="no">ObjectSerializer</span>
<span class="c1"># Verifica si un argumento debe ser serializado por este serializador.</span>
<span class="k">def</span> <span class="nf">serialize?</span><span class="p">(</span><span class="n">argument</span><span class="p">)</span>
<span class="n">argument</span><span class="p">.</span><span class="nf">is_a?</span> <span class="no">Money</span>
<span class="k">end</span>
<span class="c1"># Convierte un objeto a una representación más simple usando tipos de objetos soportados.</span>
<span class="c1"># La representación recomendada es un Hash con una clave específica. Las claves solo pueden ser de tipos básicos.</span>
<span class="c1"># Deberías llamar a `super` para añadir el tipo de serializador personalizado al hash.</span>
<span class="k">def</span> <span class="nf">serialize</span><span class="p">(</span><span class="n">money</span><span class="p">)</span>
<span class="k">super</span><span class="p">(</span>
<span class="s2">"amount"</span> <span class="o">=></span> <span class="n">money</span><span class="p">.</span><span class="nf">amount</span><span class="p">,</span>
<span class="s2">"currency"</span> <span class="o">=></span> <span class="n">money</span><span class="p">.</span><span class="nf">currency</span>
<span class="p">)</span>
<span class="k">end</span>
<span class="c1"># Convierte un valor serializado en un objeto adecuado.</span>
<span class="k">def</span> <span class="nf">deserialize</span><span class="p">(</span><span class="nb">hash</span><span class="p">)</span>
<span class="no">Money</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="nb">hash</span><span class="p">[</span><span class="s2">"amount"</span><span class="p">],</span> <span class="nb">hash</span><span class="p">[</span><span class="s2">"currency"</span><span class="p">])</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class MoneySerializer < ActiveJob::Serializers::ObjectSerializer
# Verifica si un argumento debe ser serializado por este serializador.
def serialize?(argument)
argument.is_a? Money
end
# Convierte un objeto a una representación más simple usando tipos de objetos soportados.
# La representación recomendada es un Hash con una clave específica. Las claves solo pueden ser de tipos básicos.
# Deberías llamar a `super` para añadir el tipo de serializador personalizado al hash.
def serialize(money)
super(
"amount" => money.amount,
"currency" => money.currency
)
end
# Convierte un valor serializado en un objeto adecuado.
def deserialize(hash)
Money.new(hash["amount"], hash["currency"])
end
end
">Copy</button>
</div>
<p>y añade este serializador a la lista:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># config/initializers/custom_serializers.rb</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">application</span><span class="p">.</span><span class="nf">config</span><span class="p">.</span><span class="nf">active_job</span><span class="p">.</span><span class="nf">custom_serializers</span> <span class="o"><<</span> <span class="no">MoneySerializer</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="Rails.application.config.active_job.custom_serializers << MoneySerializer
">Copy</button>
</div>
<p>Ten en cuenta que la recarga de código cargado automáticamente durante la inicialización no está soportada. Por lo tanto, se recomienda
configurar los serializadores para que se carguen solo una vez, por ejemplo, modificando <code>config/application.rb</code> de esta manera:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># config/application.rb</span>
<span class="k">module</span> <span class="nn">YourApp</span>
<span class="k">class</span> <span class="nc">Application</span> <span class="o"><</span> <span class="no">Rails</span><span class="o">::</span><span class="no">Application</span>
<span class="n">config</span><span class="p">.</span><span class="nf">autoload_once_paths</span> <span class="o"><<</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">root</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="s1">'app'</span><span class="p">,</span> <span class="s1">'serializers'</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="module YourApp
class Application < Rails::Application
config.autoload_once_paths << Rails.root.join('app', 'serializers')
end
end
">Copy</button>
</div>
<h2 id="excepciones"><a class="anchorlink" href="#excepciones"><span>12</span> Excepciones</a></h2><p>Las excepciones levantadas durante la ejecución del trabajo pueden manejarse con
<a href="https://edgeapi.rubyonrails.org/classes/ActiveSupport/Rescuable/ClassMethods.html#method-i-rescue_from"><code>rescue_from</code></a>:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">GuestsCleanupJob</span> <span class="o"><</span> <span class="no">ApplicationJob</span>
<span class="n">queue_as</span> <span class="ss">:default</span>
<span class="n">rescue_from</span><span class="p">(</span><span class="no">ActiveRecord</span><span class="o">::</span><span class="no">RecordNotFound</span><span class="p">)</span> <span class="k">do</span> <span class="o">|</span><span class="n">exception</span><span class="o">|</span>
<span class="c1"># Hacer algo con la excepción</span>
<span class="k">end</span>
<span class="k">def</span> <span class="nf">perform</span>
<span class="c1"># Hacer algo después</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class GuestsCleanupJob < ApplicationJob
queue_as :default
rescue_from(ActiveRecord::RecordNotFound) do |exception|
# Hacer algo con la excepción
end
def perform
# Hacer algo después
end
end
">Copy</button>
</div>
<p>Si una excepción de un trabajo no se rescata, entonces el trabajo se considera "fallido".</p><h3 id="reintentando-o-descartando-trabajos-fallidos"><a class="anchorlink" href="#reintentando-o-descartando-trabajos-fallidos"><span>12.1</span> Reintentando o Descartando Trabajos Fallidos</a></h3><p>Un trabajo fallido no se reintentará, a menos que se configure de otra manera.</p><p>Es posible reintentar o descartar un trabajo fallido usando <a href="https://edgeapi.rubyonrails.org/classes/ActiveJob/Exceptions/ClassMethods.html#method-i-retry_on"><code>retry_on</code></a> o
<a href="https://edgeapi.rubyonrails.org/classes/ActiveJob/Exceptions/ClassMethods.html#method-i-discard_on"><code>discard_on</code></a>, respectivamente. Por ejemplo:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">RemoteServiceJob</span> <span class="o"><</span> <span class="no">ApplicationJob</span>
<span class="n">retry_on</span> <span class="no">CustomAppException</span> <span class="c1"># por defecto espera 3s, 5 intentos</span>
<span class="n">discard_on</span> <span class="no">ActiveJob</span><span class="o">::</span><span class="no">DeserializationError</span>
<span class="k">def</span> <span class="nf">perform</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">)</span>
<span class="c1"># Podría lanzar CustomAppException o ActiveJob::DeserializationError</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class RemoteServiceJob < ApplicationJob
retry_on CustomAppException # por defecto espera 3s, 5 intentos
discard_on ActiveJob::DeserializationError
def perform(*args)
# Podría lanzar CustomAppException o ActiveJob::DeserializationError
end
end
">Copy</button>
</div>
<h3 id="deserialización"><a class="anchorlink" href="#deserialización"><span>12.2</span> Deserialización</a></h3><p>GlobalID permite serializar objetos Active Record completos pasados a <code>#perform</code>.</p><p>Si un registro pasado se elimina después de que el trabajo sea encolado pero antes de que se llame al método <code>#perform</code>,
Active Job lanzará una excepción <a href="https://edgeapi.rubyonrails.org/classes/ActiveJob/DeserializationError.html"><code>ActiveJob::DeserializationError</code></a>.</p><h2 id="pruebas-de-trabajos"><a class="anchorlink" href="#pruebas-de-trabajos"><span>13</span> Pruebas de Trabajos</a></h2><p>Puedes encontrar instrucciones detalladas sobre cómo probar tus trabajos en la
<a href="testing.html#testing-jobs">guía de pruebas</a>.</p><h2 id="depuración"><a class="anchorlink" href="#depuración"><span>14</span> Depuración</a></h2><p>Si necesitas ayuda para averiguar de dónde vienen los trabajos, puedes habilitar <a href="debugging_rails_applications.html#verbose-enqueue-logs">registro detallado</a>.</p>
<hr>
<h3>Comentarios</h3>
<p>
Se te anima a ayudar a mejorar la calidad de esta guía.
</p>
<p>
Por favor contribuye si ves algún error tipográfico o errores fácticos.
Para comenzar, puedes leer nuestra sección de <a href="https://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation">contribuciones a la documentación</a>.