-
Notifications
You must be signed in to change notification settings - Fork 0
/
caching_with_rails.html
792 lines (748 loc) · 82.9 KB
/
caching_with_rails.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
<!doctype html>
<html dir="ltr" lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Caché con Rails: Una Visión General — 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="Caché con Rails: Una Visión General — Ruby on Rails Guides" />
<meta name="description" content="NO LEA ESTE ARCHIVO EN GITHUB, LAS GUÍAS ESTÁN PUBLICADAS EN https://guides.rubyonrails.org.Caché con Rails: Una Visión GeneralEsta guía es una introducción a cómo acelerar tu aplicación Rails con caché.Caché significa almacenar contenido generado durante el ciclo de solicitud-respuesta y reutilizarlo al responder a solicitudes similares.El caché es a menudo la forma más efectiva de mejorar el rendimiento de una aplicación. A través del caché, los sitios web que funcionan en un solo servidor con una sola base de datos pueden soportar una carga de miles de usuarios concurrentes.Rails proporciona un conjunto de características de caché de serie. Esta guía te enseñará el alcance y propósito de cada una de ellas. Domina estas técnicas y tus aplicaciones Rails podrán servir millones de vistas sin tiempos de respuesta exorbitantes o facturas de servidor.Después de leer esta guía, sabrás: Caché de fragmentos y caché de muñeca rusa. Cómo gestionar las dependencias de caché. Almacenes de caché alternativos. Soporte para GET condicional." />
<meta property="og:description" content="NO LEA ESTE ARCHIVO EN GITHUB, LAS GUÍAS ESTÁN PUBLICADAS EN https://guides.rubyonrails.org.Caché con Rails: Una Visión GeneralEsta guía es una introducción a cómo acelerar tu aplicación Rails con caché.Caché significa almacenar contenido generado durante el ciclo de solicitud-respuesta y reutilizarlo al responder a solicitudes similares.El caché es a menudo la forma más efectiva de mejorar el rendimiento de una aplicación. A través del caché, los sitios web que funcionan en un solo servidor con una sola base de datos pueden soportar una carga de miles de usuarios concurrentes.Rails proporciona un conjunto de características de caché de serie. Esta guía te enseñará el alcance y propósito de cada una de ellas. Domina estas técnicas y tus aplicaciones Rails podrán servir millones de vistas sin tiempos de respuesta exorbitantes o facturas de servidor.Después de leer esta guía, sabrás: Caché de fragmentos y caché de muñeca rusa. Cómo gestionar las dependencias de caché. Almacenes de caché alternativos. Soporte para GET condicional." />
<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 LEA 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>Caché con Rails: Una Visión General</h1><p>Esta guía es una introducción a cómo acelerar tu aplicación Rails con caché.</p><p>Caché significa almacenar contenido generado durante el ciclo de solicitud-respuesta y reutilizarlo al responder a solicitudes similares.</p><p>El caché es a menudo la forma más efectiva de mejorar el rendimiento de una aplicación. A través del caché, los sitios web que funcionan en un solo servidor con una sola base de datos pueden soportar una carga de miles de usuarios concurrentes.</p><p>Rails proporciona un conjunto de características de caché de serie. Esta guía te enseñará el alcance y propósito de cada una de ellas. Domina estas técnicas y tus aplicaciones Rails podrán servir millones de vistas sin tiempos de respuesta exorbitantes o facturas de servidor.</p><p>Después de leer esta guía, sabrás:</p>
<ul>
<li>Caché de fragmentos y caché de muñeca rusa.</li>
<li>Cómo gestionar las dependencias de caché.</li>
<li>Almacenes de caché alternativos.</li>
<li>Soporte para GET condicional.</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="#caché-básico">Caché Básico</a>
<ul>
<li><a href="#caché-de-página">Caché de Página</a></li>
<li><a href="#caché-de-acción">Caché de Acción</a></li>
<li><a href="#caché-de-fragmento">Caché de Fragmento</a></li>
<li><a href="#caché-de-muñeca-rusa">Caché de Muñeca Rusa</a></li>
<li><a href="#caché-de-parcial-compartido">Caché de Parcial Compartido</a></li>
<li><a href="#gestión-de-dependencias">Gestión de Dependencias</a></li>
<li><a href="#caché-de-bajo-nivel">Caché de Bajo Nivel</a></li>
<li><a href="#caché-de-sql">Caché de SQL</a></li>
</ul></li>
<li><a href="#almacenes-de-caché">Almacenes de Caché</a>
<ul>
<li><a href="#configuración">Configuración</a></li>
<li><a href="#activesupport-cache-store"><code>ActiveSupport::Cache::Store</code></a></li>
<li><a href="#activesupport-cache-memorystore"><code>ActiveSupport::Cache::MemoryStore</code></a></li>
<li><a href="#activesupport-cache-filestore"><code>ActiveSupport::Cache::FileStore</code></a></li>
<li><a href="#activesupport-cache-memcachestore"><code>ActiveSupport::Cache::MemCacheStore</code></a></li>
<li><a href="#activesupport-cache-rediscachestore"><code>ActiveSupport::Cache::RedisCacheStore</code></a></li>
<li><a href="#activesupport-cache-nullstore"><code>ActiveSupport::Cache::NullStore</code></a></li>
<li><a href="#almacenes-de-caché-personalizados">Almacenes de Caché Personalizados</a></li>
</ul></li>
<li><a href="#claves-de-caché">Claves de Caché</a></li>
<li><a href="#soporte-para-get-condicional">Soporte para GET Condicional</a>
<ul>
<li><a href="#etags-fuertes-vs-débiles">ETags Fuertes vs. Débiles</a></li>
</ul></li>
<li><a href="#caché-en-desarrollo">Caché en Desarrollo</a></li>
<li><a href="#referencias">Referencias</a></li>
</ol>
</nav>
<hr>
</div>
</section>
<main id="container">
<div class="wrapper">
<div id="mainCol">
<h2 id="caché-básico"><a class="anchorlink" href="#caché-básico"><span>1</span> Caché Básico</a></h2><p>Esta es una introducción a tres tipos de técnicas de caché: caché de página, caché de acción y caché de fragmento. Por defecto, Rails proporciona caché de fragmento. Para usar caché de página y caché de acción, necesitarás agregar <code>actionpack-page_caching</code> y <code>actionpack-action_caching</code> a tu <code>Gemfile</code>.</p><p>Por defecto, el caché de Action Controller solo está habilitado en tu entorno de producción. Puedes experimentar con el caché localmente ejecutando <code>rails dev:cache</code>, o configurando <a href="configuring.html#config-action-controller-perform-caching"><code>config.action_controller.perform_caching</code></a> a <code>true</code> en <code>config/environments/development.rb</code>.</p><p>NOTA: Cambiar el valor de <code>config.action_controller.perform_caching</code> solo tendrá efecto en el caché proporcionado por Action Controller. Por ejemplo, no impactará el caché de bajo nivel, que abordamos <a href="#caché-de-bajo-nivel">más abajo</a>.</p><h3 id="caché-de-página"><a class="anchorlink" href="#caché-de-página"><span>1.1</span> Caché de Página</a></h3><p>La caché de página es un mecanismo de Rails que permite que la solicitud de una página generada sea cumplida por el servidor web (es decir, Apache o NGINX) sin tener que pasar por toda la pila de Rails. Aunque esto es súper rápido, no se puede aplicar a todas las situaciones (como páginas que necesitan autenticación). Además, debido a que el servidor web está sirviendo un archivo directamente desde el sistema de archivos, necesitarás implementar la expiración de caché.</p><div class="interstitial info"><p>La caché de página ha sido eliminada de Rails 4. Consulta la <a href="https://github.com/rails/actionpack-page_caching">gema actionpack-page_caching</a>.</p></div><h3 id="caché-de-acción"><a class="anchorlink" href="#caché-de-acción"><span>1.2</span> Caché de Acción</a></h3><p>La caché de página no se puede usar para acciones que tienen filtros previos, por ejemplo, páginas que requieren autenticación. Aquí es donde entra la caché de acción. La caché de acción funciona como la caché de página, excepto que la solicitud web entrante llega a la pila de Rails para que se puedan ejecutar filtros previos antes de que se sirva la caché. Esto permite que se ejecuten autenticaciones y otras restricciones mientras se sigue sirviendo el resultado de la salida de una copia en caché.</p><div class="interstitial info"><p>La caché de acción ha sido eliminada de Rails 4. Consulta la <a href="https://github.com/rails/actionpack-action_caching">gema actionpack-action_caching</a>. Consulta la <a href="https://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works">visión general de expiración de caché basada en claves de DHH</a> para el método preferido actualmente.</p></div><h3 id="caché-de-fragmento"><a class="anchorlink" href="#caché-de-fragmento"><span>1.3</span> Caché de Fragmento</a></h3><p>Las aplicaciones web dinámicas generalmente construyen páginas con una variedad de componentes, no todos con las mismas características de caché. Cuando diferentes partes de la página necesitan ser almacenadas en caché y expirar por separado, puedes usar la caché de fragmento.</p><p>La caché de fragmento permite que un fragmento de lógica de vista se envuelva en un bloque de caché y se sirva desde el almacén de caché cuando llegue la siguiente solicitud.</p><p>Por ejemplo, si quisieras almacenar en caché cada producto en una página, podrías usar este código:</p><div class="interstitial code">
<pre><code class="highlight erb"><span class="cp"><%</span> <span class="vi">@products</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">product</span><span class="o">|</span> <span class="cp">%></span>
<span class="cp"><%</span> <span class="n">cache</span> <span class="n">product</span> <span class="k">do</span> <span class="cp">%></span>
<span class="cp"><%=</span> <span class="n">render</span> <span class="n">product</span> <span class="cp">%></span>
<span class="cp"><%</span> <span class="k">end</span> <span class="cp">%></span>
<span class="cp"><%</span> <span class="k">end</span> <span class="cp">%></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<% @products.each do |product| %>
<% cache product do %>
<%= render product %>
<% end %>
<% end %>
">Copy</button>
</div>
<p>Cuando tu aplicación recibe su primera solicitud para esta página, Rails escribirá una nueva entrada de caché con una clave única. Una clave se ve algo así:</p><div class="interstitial code">
<pre><code class="highlight plaintext">views/products/index:bea67108094918eeba42cd4a6e786901/products/1
</code></pre>
<button class="clipboard-button" data-clipboard-text="views/products/index:bea67108094918eeba42cd4a6e786901/products/1
">Copy</button>
</div>
<p>La cadena de caracteres en el medio es un digest de árbol de plantilla. Es un digest hash calculado basado en el contenido del fragmento de vista que estás almacenando en caché. Si cambias el fragmento de vista (por ejemplo, cambia el HTML), el digest cambiará, expirando el archivo existente.</p><p>Una versión de caché, derivada del registro del producto, se almacena en la entrada de caché. Cuando se toca el producto, la versión de caché cambia, y cualquier fragmento en caché que contenga la versión anterior se ignora.</p><div class="interstitial info"><p>Los almacenes de caché como Memcached eliminarán automáticamente los archivos de caché antiguos.</p></div><p>Si deseas almacenar en caché un fragmento bajo ciertas condiciones, puedes usar <code>cache_if</code> o <code>cache_unless</code>:</p><div class="interstitial code">
<pre><code class="highlight erb"><span class="cp"><%</span> <span class="n">cache_if</span> <span class="n">admin?</span><span class="p">,</span> <span class="n">product</span> <span class="k">do</span> <span class="cp">%></span>
<span class="cp"><%=</span> <span class="n">render</span> <span class="n">product</span> <span class="cp">%></span>
<span class="cp"><%</span> <span class="k">end</span> <span class="cp">%></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<% cache_if admin?, product do %>
<%= render product %>
<% end %>
">Copy</button>
</div>
<h4 id="caché-de-colección"><a class="anchorlink" href="#caché-de-colección"><span>1.3.1</span> Caché de Colección</a></h4><p>El helper <code>render</code> también puede almacenar en caché plantillas individuales renderizadas para una colección. Incluso puede superar el ejemplo anterior con <code>each</code> leyendo todas las plantillas de caché a la vez en lugar de una por una. Esto se hace pasando <code>cached: true</code> al renderizar la colección:</p><div class="interstitial code">
<pre><code class="highlight erb"><span class="cp"><%=</span> <span class="n">render</span> <span class="ss">partial: </span><span class="s1">'products/product'</span><span class="p">,</span> <span class="ss">collection: </span><span class="vi">@products</span><span class="p">,</span> <span class="ss">cached: </span><span class="kp">true</span> <span class="cp">%></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<%= render partial: 'products/product', collection: @products, cached: true %>
">Copy</button>
</div>
<p>Todas las plantillas en caché de renderizaciones anteriores se recuperarán a la vez con mucha mayor velocidad. Además, las plantillas que aún no han sido almacenadas en caché se escribirán en caché y se recuperarán en el siguiente renderizado.</p><p>La clave de caché puede ser configurada. En el ejemplo a continuación, se prefija con la localización actual para asegurar que diferentes localizaciones de la página del producto no se sobrescriban entre sí:</p><div class="interstitial code">
<pre><code class="highlight erb"><span class="cp"><%=</span> <span class="n">render</span> <span class="ss">partial: </span><span class="s1">'products/product'</span><span class="p">,</span>
<span class="ss">collection: </span><span class="vi">@products</span><span class="p">,</span>
<span class="ss">cached: </span><span class="o">-></span><span class="p">(</span><span class="n">product</span><span class="p">)</span> <span class="p">{</span> <span class="p">[</span><span class="no">I18n</span><span class="p">.</span><span class="nf">locale</span><span class="p">,</span> <span class="n">product</span><span class="p">]</span> <span class="p">}</span> <span class="cp">%></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<%= render partial: 'products/product',
collection: @products,
cached: ->(product) { [I18n.locale, product] } %>
">Copy</button>
</div>
<h3 id="caché-de-muñeca-rusa"><a class="anchorlink" href="#caché-de-muñeca-rusa"><span>1.4</span> Caché de Muñeca Rusa</a></h3><p>Es posible que desees anidar fragmentos en caché dentro de otros fragmentos en caché. Esto se llama caché de muñeca rusa.</p><p>La ventaja de la caché de muñeca rusa es que si se actualiza un solo producto, todos los demás fragmentos internos se pueden reutilizar al regenerar el fragmento externo.</p><p>Como se explicó en la sección anterior, un archivo en caché expirará si el valor de <code>updated_at</code> cambia para un registro del que depende directamente el archivo en caché. Sin embargo, esto no expirará ninguna caché en la que el fragmento esté anidado.</p><p>Por ejemplo, considera la siguiente vista:</p><div class="interstitial code">
<pre><code class="highlight erb"><span class="cp"><%</span> <span class="n">cache</span> <span class="n">product</span> <span class="k">do</span> <span class="cp">%></span>
<span class="cp"><%=</span> <span class="n">render</span> <span class="n">product</span><span class="p">.</span><span class="nf">games</span> <span class="cp">%></span>
<span class="cp"><%</span> <span class="k">end</span> <span class="cp">%></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<% cache product do %>
<%= render product.games %>
<% end %>
">Copy</button>
</div>
<p>Que a su vez renderiza esta vista:</p><div class="interstitial code">
<pre><code class="highlight erb"><span class="cp"><%</span> <span class="n">cache</span> <span class="n">game</span> <span class="k">do</span> <span class="cp">%></span>
<span class="cp"><%=</span> <span class="n">render</span> <span class="n">game</span> <span class="cp">%></span>
<span class="cp"><%</span> <span class="k">end</span> <span class="cp">%></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<% cache game do %>
<%= render game %>
<% end %>
">Copy</button>
</div>
<p>Si se cambia algún atributo del juego, el valor de <code>updated_at</code> se establecerá en la hora actual, expirando así la caché. Sin embargo, como <code>updated_at</code> no se cambiará para el objeto producto, esa caché no expirará y tu aplicación servirá datos obsoletos. Para solucionar esto, vinculamos los modelos juntos con el método <code>touch</code>:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">Product</span> <span class="o"><</span> <span class="no">ApplicationRecord</span>
<span class="n">has_many</span> <span class="ss">:games</span>
<span class="k">end</span>
<span class="k">class</span> <span class="nc">Game</span> <span class="o"><</span> <span class="no">ApplicationRecord</span>
<span class="n">belongs_to</span> <span class="ss">:product</span><span class="p">,</span> <span class="ss">touch: </span><span class="kp">true</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class Product < ApplicationRecord
has_many :games
end
class Game < ApplicationRecord
belongs_to :product, touch: true
end
">Copy</button>
</div>
<p>Con <code>touch</code> establecido en <code>true</code>, cualquier acción que cambie <code>updated_at</code> para un registro de juego también lo cambiará para el producto asociado, expirando así la caché.</p><h3 id="caché-de-parcial-compartido"><a class="anchorlink" href="#caché-de-parcial-compartido"><span>1.5</span> Caché de Parcial Compartido</a></h3><p>Es posible compartir parciales y caché asociado entre archivos con diferentes tipos MIME. Por ejemplo, el caché de parcial compartido permite a los escritores de plantillas compartir un parcial entre archivos HTML y JavaScript. Cuando las plantillas se recopilan en el archivo de resolución de plantillas, solo incluyen la extensión del lenguaje de plantillas y no el tipo MIME. Debido a esto, las plantillas pueden ser usadas para múltiples tipos MIME. Tanto las solicitudes HTML como JavaScript responderán al siguiente código:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">render</span><span class="p">(</span><span class="ss">partial: </span><span class="s1">'hotels/hotel'</span><span class="p">,</span> <span class="ss">collection: </span><span class="vi">@hotels</span><span class="p">,</span> <span class="ss">cached: </span><span class="kp">true</span><span class="p">)</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="render(partial: 'hotels/hotel', collection: @hotels, cached: true)
">Copy</button>
</div>
<p>Cargará un archivo llamado <code>hotels/hotel.erb</code>.</p><p>Otra opción es incluir el atributo <code>formats</code> al parcial para renderizar.</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">render</span><span class="p">(</span><span class="ss">partial: </span><span class="s1">'hotels/hotel'</span><span class="p">,</span> <span class="ss">collection: </span><span class="vi">@hotels</span><span class="p">,</span> <span class="ss">formats: :html</span><span class="p">,</span> <span class="ss">cached: </span><span class="kp">true</span><span class="p">)</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="render(partial: 'hotels/hotel', collection: @hotels, formats: :html, cached: true)
">Copy</button>
</div>
<p>Cargará un archivo llamado <code>hotels/hotel.html.erb</code> en cualquier tipo MIME de archivo, por ejemplo, podrías incluir este parcial en un archivo JavaScript.</p><h3 id="gestión-de-dependencias"><a class="anchorlink" href="#gestión-de-dependencias"><span>1.6</span> Gestión de Dependencias</a></h3><p>Para invalidar correctamente la caché, necesitas definir adecuadamente las dependencias de caché. Rails es lo suficientemente inteligente como para manejar casos comunes, por lo que no tienes que especificar nada. Sin embargo, a veces, cuando estás manejando helpers personalizados, por ejemplo, necesitas definirlos explícitamente.</p><h4 id="dependencias-implícitas"><a class="anchorlink" href="#dependencias-implícitas"><span>1.6.1</span> Dependencias Implícitas</a></h4><p>La mayoría de las dependencias de plantillas se pueden derivar de llamadas a <code>render</code> en la propia plantilla. Aquí hay algunos ejemplos de llamadas a render que <code>ActionView::Digestor</code> sabe cómo decodificar:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">render</span> <span class="ss">partial: </span><span class="s2">"comments/comment"</span><span class="p">,</span> <span class="ss">collection: </span><span class="n">commentable</span><span class="p">.</span><span class="nf">comments</span>
<span class="n">render</span> <span class="s2">"comments/comments"</span>
<span class="n">render</span> <span class="s1">'comments/comments'</span>
<span class="n">render</span><span class="p">(</span><span class="s1">'comments/comments'</span><span class="p">)</span>
<span class="n">render</span> <span class="s2">"header"</span> <span class="c1"># se traduce a render("comments/header")</span>
<span class="n">render</span><span class="p">(</span><span class="vi">@topic</span><span class="p">)</span> <span class="c1"># se traduce a render("topics/topic")</span>
<span class="n">render</span><span class="p">(</span><span class="n">topics</span><span class="p">)</span> <span class="c1"># se traduce a render("topics/topic")</span>
<span class="n">render</span><span class="p">(</span><span class="n">message</span><span class="p">.</span><span class="nf">topics</span><span class="p">)</span> <span class="c1"># se traduce a render("topics/topic")</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="render partial: "comments/comment", collection: commentable.comments
render "comments/comments"
render 'comments/comments'
render('comments/comments')
render "header" # se traduce a render("comments/header")
render(@topic) # se traduce a render("topics/topic")
render(topics) # se traduce a render("topics/topic")
render(message.topics) # se traduce a render("topics/topic")
">Copy</button>
</div>
<p>Por otro lado, algunas llamadas necesitan ser cambiadas para que el caché funcione correctamente. Por ejemplo, si estás pasando una colección personalizada, necesitarás cambiar:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">render</span> <span class="vi">@project</span><span class="p">.</span><span class="nf">documents</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">published: </span><span class="kp">true</span><span class="p">)</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="render @project.documents.where(published: true)
">Copy</button>
</div>
<p>a:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">render</span> <span class="ss">partial: </span><span class="s2">"documents/document"</span><span class="p">,</span> <span class="ss">collection: </span><span class="vi">@project</span><span class="p">.</span><span class="nf">documents</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">published: </span><span class="kp">true</span><span class="p">)</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="render partial: "documents/document", collection: @project.documents.where(published: true)
">Copy</button>
</div>
<h4 id="dependencias-explícitas"><a class="anchorlink" href="#dependencias-explícitas"><span>1.6.2</span> Dependencias Explícitas</a></h4><p>A veces tendrás dependencias de plantillas que no se pueden derivar en absoluto. Este es típicamente el caso cuando el renderizado ocurre en helpers. Aquí tienes un ejemplo:</p><div class="interstitial code">
<pre><code class="highlight erb"><span class="cp"><%=</span> <span class="n">render_sortable_todolists</span> <span class="vi">@project</span><span class="p">.</span><span class="nf">todolists</span> <span class="cp">%></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<%= render_sortable_todolists @project.todolists %>
">Copy</button>
</div>
<p>Necesitarás usar un formato especial de comentario para indicarlas:</p><div class="interstitial code">
<pre><code class="highlight erb"><span class="c"><%# Template Dependency: todolists/todolist %></span>
<span class="cp"><%=</span> <span class="n">render_sortable_todolists</span> <span class="vi">@project</span><span class="p">.</span><span class="nf">todolists</span> <span class="cp">%></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<%# Template Dependency: todolists/todolist %>
<%= render_sortable_todolists @project.todolists %>
">Copy</button>
</div>
<p>En algunos casos, como una configuración de herencia de tabla única, podrías tener un montón de dependencias explícitas. En lugar de escribir cada plantilla, puedes usar un comodín para que coincida con cualquier plantilla en un directorio:</p><div class="interstitial code">
<pre><code class="highlight erb"><span class="c"><%# Template Dependency: events/* %></span>
<span class="cp"><%=</span> <span class="n">render_categorizable_events</span> <span class="vi">@person</span><span class="p">.</span><span class="nf">events</span> <span class="cp">%></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<%# Template Dependency: events/* %>
<%= render_categorizable_events @person.events %>
">Copy</button>
</div>
<p>En cuanto al caché de colección, si la plantilla parcial no comienza con una llamada de caché limpia, aún puedes beneficiarte del caché de colección agregando un formato especial de comentario en cualquier parte de la plantilla, como:</p><div class="interstitial code">
<pre><code class="highlight erb"><span class="c"><%# Template Collection: notification %></span>
<span class="cp"><%</span> <span class="n">my_helper_that_calls_cache</span><span class="p">(</span><span class="n">some_arg</span><span class="p">,</span> <span class="n">notification</span><span class="p">)</span> <span class="k">do</span> <span class="cp">%></span>
<span class="cp"><%=</span> <span class="n">notification</span><span class="p">.</span><span class="nf">name</span> <span class="cp">%></span>
<span class="cp"><%</span> <span class="k">end</span> <span class="cp">%></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<%# Template Collection: notification %>
<% my_helper_that_calls_cache(some_arg, notification) do %>
<%= notification.name %>
<% end %>
">Copy</button>
</div>
<h4 id="dependencias-externas"><a class="anchorlink" href="#dependencias-externas"><span>1.6.3</span> Dependencias Externas</a></h4><p>Si usas un método helper, por ejemplo, dentro de un bloque de caché y luego actualizas ese helper, tendrás que actualizar la caché también. No importa realmente cómo lo hagas, pero el MD5 del archivo de plantilla debe cambiar. Una recomendación es simplemente ser explícito en un comentario, como:</p><div class="interstitial code">
<pre><code class="highlight erb"><span class="c"><%# Helper Dependency Updated: Jul 28, 2015 at 7pm %></span>
<span class="cp"><%=</span> <span class="n">some_helper_method</span><span class="p">(</span><span class="n">person</span><span class="p">)</span> <span class="cp">%></span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="<%# Helper Dependency Updated: Jul 28, 2015 at 7pm %>
<%= some_helper_method(person) %>
">Copy</button>
</div>
<h3 id="caché-de-bajo-nivel"><a class="anchorlink" href="#caché-de-bajo-nivel"><span>1.7</span> Caché de Bajo Nivel</a></h3><p>A veces necesitas almacenar en caché un valor particular o el resultado de una consulta en lugar de almacenar en caché fragmentos de vista. El mecanismo de caché de Rails funciona muy bien para almacenar cualquier información serializable.</p><p>La forma más eficiente de implementar caché de bajo nivel es usando el método <code>Rails.cache.fetch</code>. Este método hace tanto la lectura como la escritura en la caché. Cuando se pasa solo un argumento, la clave se obtiene y el valor de la caché se devuelve. Si se pasa un bloque, ese bloque se ejecutará en caso de que falte la caché. El valor de retorno del bloque se escribirá en la caché bajo la clave de caché dada, y ese valor de retorno se devolverá. En caso de acierto de caché, el valor en caché se devolverá sin ejecutar el bloque.</p><p>Considera el siguiente ejemplo. Una aplicación tiene un modelo <code>Product</code> con un método de instancia que busca el precio del producto en un sitio web competidor. Los datos devueltos por este método serían perfectos para el caché de bajo nivel:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">Product</span> <span class="o"><</span> <span class="no">ApplicationRecord</span>
<span class="k">def</span> <span class="nf">competing_price</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">cache</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="s2">"</span><span class="si">#{</span><span class="n">cache_key_with_version</span><span class="si">}</span><span class="s2">/competing_price"</span><span class="p">,</span> <span class="ss">expires_in: </span><span class="mi">12</span><span class="p">.</span><span class="nf">hours</span><span class="p">)</span> <span class="k">do</span>
<span class="no">Competitor</span><span class="o">::</span><span class="no">API</span><span class="p">.</span><span class="nf">find_price</span><span class="p">(</span><span class="nb">id</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class Product < ApplicationRecord
def competing_price
Rails.cache.fetch("#{cache_key_with_version}/competing_price", expires_in: 12.hours) do
Competitor::API.find_price(id)
end
end
end
">Copy</button>
</div>
<p>NOTA: Observa que en este ejemplo usamos el método <code>cache_key_with_version</code>, por lo que la clave de caché resultante será algo como <code>products/233-20140225082222765838000/competing_price</code>. <code>cache_key_with_version</code> genera una cadena basada en el nombre de la clase del modelo, <code>id</code> y los atributos <code>updated_at</code>. Esta es una convención común y tiene el beneficio de invalidar la caché cada vez que se actualiza el producto. En general, cuando usas caché de bajo nivel, necesitas generar una clave de caché.</p><h4 id="evitar-almacenar-en-caché-instancias-de-objetos-active-record"><a class="anchorlink" href="#evitar-almacenar-en-caché-instancias-de-objetos-active-record"><span>1.7.1</span> Evitar Almacenar en Caché Instancias de Objetos Active Record</a></h4><p>Considera este ejemplo, que almacena en caché una lista de objetos Active Record que representan superusuarios:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># super_admins es una consulta SQL costosa, así que no la ejecutes con demasiada frecuencia</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">cache</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="s2">"super_admin_users"</span><span class="p">,</span> <span class="ss">expires_in: </span><span class="mi">12</span><span class="p">.</span><span class="nf">hours</span><span class="p">)</span> <span class="k">do</span>
<span class="no">User</span><span class="p">.</span><span class="nf">super_admins</span><span class="p">.</span><span class="nf">to_a</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="# super_admins es una consulta SQL costosa, así que no la ejecutes con demasiada frecuencia
Rails.cache.fetch("super_admin_users", expires_in: 12.hours) do
User.super_admins.to_a
end
">Copy</button>
</div>
<p>Debes <strong>evitar</strong> este patrón. ¿Por qué? Porque la instancia podría cambiar. En producción, los atributos en ella podrían diferir, o el registro podría ser eliminado. Y en desarrollo, funciona de manera poco confiable con almacenes de caché que recargan el código cuando realizas cambios.</p><p>En su lugar, almacena en caché el ID u otro tipo de datos primitivo. Por ejemplo:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># super_admins es una consulta SQL costosa, así que no la ejecutes con demasiada frecuencia</span>
<span class="n">ids</span> <span class="o">=</span> <span class="no">Rails</span><span class="p">.</span><span class="nf">cache</span><span class="p">.</span><span class="nf">fetch</span><span class="p">(</span><span class="s2">"super_admin_user_ids"</span><span class="p">,</span> <span class="ss">expires_in: </span><span class="mi">12</span><span class="p">.</span><span class="nf">hours</span><span class="p">)</span> <span class="k">do</span>
<span class="no">User</span><span class="p">.</span><span class="nf">super_admins</span><span class="p">.</span><span class="nf">pluck</span><span class="p">(</span><span class="ss">:id</span><span class="p">)</span>
<span class="k">end</span>
<span class="no">User</span><span class="p">.</span><span class="nf">where</span><span class="p">(</span><span class="ss">id: </span><span class="n">ids</span><span class="p">).</span><span class="nf">to_a</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="# super_admins es una consulta SQL costosa, así que no la ejecutes con demasiada frecuencia
ids = Rails.cache.fetch("super_admin_user_ids", expires_in: 12.hours) do
User.super_admins.pluck(:id)
end
User.where(id: ids).to_a
">Copy</button>
</div>
<h3 id="caché-de-sql"><a class="anchorlink" href="#caché-de-sql"><span>1.8</span> Caché de SQL</a></h3><p>El caché de consultas es una característica de Rails que almacena en caché el conjunto de resultados devuelto por cada consulta. Si Rails encuentra la misma consulta nuevamente para esa solicitud, usará el conjunto de resultados en caché en lugar de ejecutar la consulta contra la base de datos nuevamente.</p><p>Por ejemplo:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">ProductsController</span> <span class="o"><</span> <span class="no">ApplicationController</span>
<span class="k">def</span> <span class="nf">index</span>
<span class="c1"># Ejecutar una consulta de búsqueda</span>
<span class="vi">@products</span> <span class="o">=</span> <span class="no">Product</span><span class="p">.</span><span class="nf">all</span>
<span class="c1"># ...</span>
<span class="c1"># Ejecutar la misma consulta nuevamente</span>
<span class="vi">@products</span> <span class="o">=</span> <span class="no">Product</span><span class="p">.</span><span class="nf">all</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class ProductsController < ApplicationController
def index
# Ejecutar una consulta de búsqueda
@products = Product.all
# ...
# Ejecutar la misma consulta nuevamente
@products = Product.all
end
end
">Copy</button>
</div>
<p>La segunda vez que se ejecuta la misma consulta contra la base de datos, en realidad no va a golpear la base de datos. La primera vez que se devuelve el resultado de la consulta, se almacena en el caché de consultas (en memoria) y la segunda vez se extrae de la memoria.</p><p>Sin embargo, es importante tener en cuenta que los cachés de consulta se crean al inicio de una acción y se destruyen al final de esa acción, y por lo tanto, solo persisten durante la duración de la acción. Si deseas almacenar resultados de consultas de manera más persistente, puedes hacerlo con caché de bajo nivel.</p><h2 id="almacenes-de-caché"><a class="anchorlink" href="#almacenes-de-caché"><span>2</span> Almacenes de Caché</a></h2><p>Rails proporciona diferentes almacenes para los datos en caché (aparte de SQL y caché de página).</p><h3 id="configuración"><a class="anchorlink" href="#configuración"><span>2.1</span> Configuración</a></h3><p>Puedes configurar el almacén de caché predeterminado de tu aplicación configurando la opción de configuración <code>config.cache_store</code>. Otros parámetros se pueden pasar como argumentos al constructor del almacén de caché:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">config</span><span class="p">.</span><span class="nf">cache_store</span> <span class="o">=</span> <span class="ss">:memory_store</span><span class="p">,</span> <span class="p">{</span> <span class="ss">size: </span><span class="mi">64</span><span class="p">.</span><span class="nf">megabytes</span> <span class="p">}</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="config.cache_store = :memory_store, { size: 64.megabytes }
">Copy</button>
</div>
<p>Alternativamente, puedes configurar <code>ActionController::Base.cache_store</code> fuera de un bloque de configuración.</p><p>Puedes acceder al caché llamando a <code>Rails.cache</code>.</p><h4 id="opciones-de-conexión-de-pool"><a class="anchorlink" href="#opciones-de-conexión-de-pool"><span>2.1.1</span> Opciones de Conexión de Pool</a></h4><p>Por defecto, <a href="#activesupport-cache-memcachestore"><code>:mem_cache_store</code></a> y
<a href="#activesupport-cache-rediscachestore"><code>:redis_cache_store</code></a> están configurados para usar
pool de conexiones. Esto significa que si estás usando Puma, u otro servidor con hilos,
puedes tener múltiples hilos realizando consultas al almacén de caché al mismo tiempo.</p><p>Si deseas desactivar el pool de conexiones, configura la opción <code>:pool</code> a <code>false</code> al configurar el almacén de caché:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">config</span><span class="p">.</span><span class="nf">cache_store</span> <span class="o">=</span> <span class="ss">:mem_cache_store</span><span class="p">,</span> <span class="s2">"cache.example.com"</span><span class="p">,</span> <span class="p">{</span> <span class="ss">pool: </span><span class="kp">false</span> <span class="p">}</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="config.cache_store = :mem_cache_store, "cache.example.com", { pool: false }
">Copy</button>
</div>
<p>También puedes anular la configuración predeterminada del pool proporcionando opciones individuales a la opción <code>:pool</code>:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">config</span><span class="p">.</span><span class="nf">cache_store</span> <span class="o">=</span> <span class="ss">:mem_cache_store</span><span class="p">,</span> <span class="s2">"cache.example.com"</span><span class="p">,</span> <span class="p">{</span> <span class="ss">pool: </span><span class="p">{</span> <span class="ss">size: </span><span class="mi">32</span><span class="p">,</span> <span class="ss">timeout: </span><span class="mi">1</span> <span class="p">}</span> <span class="p">}</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="config.cache_store = :mem_cache_store, "cache.example.com", { pool: { size: 32, timeout: 1 } }
">Copy</button>
</div>
<ul>
<li><p><code>:size</code> - Esta opción establece el número de conexiones por proceso (por defecto es 5).</p></li>
<li><p><code>:timeout</code> - Esta opción establece el número de segundos para esperar una conexión (por defecto es 5). Si no hay conexión disponible dentro del tiempo de espera, se generará un <code>Timeout::Error</code>.</p></li>
</ul>
<h3 id="activesupport-cache-store"><a class="anchorlink" href="#activesupport-cache-store"><span>2.2</span> <code>ActiveSupport::Cache::Store</code></a></h3><p><a href="https://edgeapi.rubyonrails.org/classes/ActiveSupport/Cache/Store.html"><code>ActiveSupport::Cache::Store</code></a> proporciona la base para interactuar con el caché en Rails. Esta es una clase abstracta, y no puedes usarla por sí sola. En su lugar, debes usar una implementación concreta de la clase vinculada a un motor de almacenamiento. Rails viene con varias implementaciones, documentadas a continuación.</p><p>Los métodos principales de la API son <a href="https://edgeapi.rubyonrails.org/classes/ActiveSupport/Cache/Store.html#method-i-read"><code>read</code></a>, <a href="https://edgeapi.rubyonrails.org/classes/ActiveSupport/Cache/Store.html#method-i-write"><code>write</code></a>, <a href="https://edgeapi.rubyonrails.org/classes/ActiveSupport/Cache/Store.html#method-i-delete"><code>delete</code></a>, <a href="https://edgeapi.rubyonrails.org/classes/ActiveSupport/Cache/Store.html#method-i-exist-3F"><code>exist?</code></a>, y <a href="https://edgeapi.rubyonrails.org/classes/ActiveSupport/Cache/Store.html#method-i-fetch"><code>fetch</code></a>.</p><p>Las opciones pasadas al constructor del almacén de caché serán tratadas como opciones predeterminadas para los métodos de API apropiados.</p><h3 id="activesupport-cache-memorystore"><a class="anchorlink" href="#activesupport-cache-memorystore"><span>2.3</span> <code>ActiveSupport::Cache::MemoryStore</code></a></h3><p><a href="https://edgeapi.rubyonrails.org/classes/ActiveSupport/Cache/MemoryStore.html"><code>ActiveSupport::Cache::MemoryStore</code></a> mantiene las entradas en memoria en el mismo proceso Ruby. El almacén de caché tiene un tamaño limitado especificado enviando la opción <code>:size</code> al inicializador (el valor predeterminado es 32Mb). Cuando el caché supera el tamaño asignado, se producirá una limpieza y se eliminarán las entradas menos recientemente utilizadas.</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">config</span><span class="p">.</span><span class="nf">cache_store</span> <span class="o">=</span> <span class="ss">:memory_store</span><span class="p">,</span> <span class="p">{</span> <span class="ss">size: </span><span class="mi">64</span><span class="p">.</span><span class="nf">megabytes</span> <span class="p">}</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="config.cache_store = :memory_store, { size: 64.megabytes }
">Copy</button>
</div>
<p>Si estás ejecutando múltiples procesos de servidor Ruby on Rails (lo cual es el caso si estás usando Phusion Passenger o el modo de clúster de Puma), entonces tus instancias de procesos de servidor Rails no podrán compartir datos de caché entre sí. Este almacén de caché no es apropiado para implementaciones de aplicaciones grandes. Sin embargo, puede funcionar bien para sitios pequeños y de bajo tráfico con solo un par de procesos de servidor, así como para entornos de desarrollo y prueba.</p><p>Los nuevos proyectos de Rails están configurados para usar esta implementación en el entorno de desarrollo por defecto.</p><p>NOTA: Dado que los procesos no compartirán datos de caché al usar <code>:memory_store</code>, no será posible leer, escribir o expirar manualmente el caché a través de la consola de Rails.</p><h3 id="activesupport-cache-filestore"><a class="anchorlink" href="#activesupport-cache-filestore"><span>2.4</span> <code>ActiveSupport::Cache::FileStore</code></a></h3><p><a href="https://edgeapi.rubyonrails.org/classes/ActiveSupport/Cache/FileStore.html"><code>ActiveSupport::Cache::FileStore</code></a> utiliza el sistema de archivos para almacenar entradas. La ruta al directorio donde se almacenarán los archivos del almacén debe especificarse al inicializar el caché.</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">config</span><span class="p">.</span><span class="nf">cache_store</span> <span class="o">=</span> <span class="ss">:file_store</span><span class="p">,</span> <span class="s2">"/path/to/cache/directory"</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="config.cache_store = :file_store, "/path/to/cache/directory"
">Copy</button>
</div>
<p>Con este almacén de caché, múltiples procesos de servidor en el mismo host pueden compartir un caché. Este almacén de caché es apropiado para sitios de tráfico bajo a medio que se sirven desde uno o dos hosts. Los procesos de servidor que se ejecutan en diferentes hosts podrían compartir un caché utilizando un sistema de archivos compartido, pero esa configuración no se recomienda.</p><p>Dado que el caché crecerá hasta que el disco esté lleno, se recomienda limpiar periódicamente las entradas antiguas.</p><p>Esta es la implementación de almacén de caché predeterminada (en <code>"#{root}/tmp/cache/"</code>) si no se proporciona explícitamente <code>config.cache_store</code>.</p><h3 id="activesupport-cache-memcachestore"><a class="anchorlink" href="#activesupport-cache-memcachestore"><span>2.5</span> <code>ActiveSupport::Cache::MemCacheStore</code></a></h3><p><a href="https://edgeapi.rubyonrails.org/classes/ActiveSupport/Cache/MemCacheStore.html"><code>ActiveSupport::Cache::MemCacheStore</code></a> utiliza el servidor <code>memcached</code> de Danga para proporcionar un caché centralizado para tu aplicación. Rails utiliza la gema <code>dalli</code> incluida por defecto. Este es actualmente el almacén de caché más popular para sitios web de producción. Puede usarse para proporcionar un clúster de caché compartido único con muy alto rendimiento y redundancia.</p><p>Al inicializar el caché, debes especificar las direcciones de todos los servidores memcached en tu clúster, o asegurarte de que la variable de entorno <code>MEMCACHE_SERVERS</code> se haya configurado adecuadamente.</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">config</span><span class="p">.</span><span class="nf">cache_store</span> <span class="o">=</span> <span class="ss">:mem_cache_store</span><span class="p">,</span> <span class="s2">"cache-1.example.com"</span><span class="p">,</span> <span class="s2">"cache-2.example.com"</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="config.cache_store = :mem_cache_store, "cache-1.example.com", "cache-2.example.com"
">Copy</button>
</div>
<p>Si no se especifican, asumirá que memcached se está ejecutando en localhost en el puerto predeterminado (<code>127.0.0.1:11211</code>), pero esta no es una configuración ideal para sitios más grandes.</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">config</span><span class="p">.</span><span class="nf">cache_store</span> <span class="o">=</span> <span class="ss">:mem_cache_store</span> <span class="c1"># Caerá en $MEMCACHE_SERVERS, luego 127.0.0.1:11211</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="config.cache_store = :mem_cache_store # Caerá en $MEMCACHE_SERVERS, luego 127.0.0.1:11211
">Copy</button>
</div>
<p>Consulta la <a href="https://www.rubydoc.info/gems/dalli/Dalli/Client#initialize-instance_method">documentación de <code>Dalli::Client</code></a> para conocer los tipos de direcciones compatibles.</p><p>El método <a href="https://edgeapi.rubyonrails.org/classes/ActiveSupport/Cache/MemCacheStore.html#method-i-write"><code>write</code></a> (y <code>fetch</code>) en este caché acepta opciones adicionales que aprovechan las características específicas de memcached.</p><h3 id="activesupport-cache-rediscachestore"><a class="anchorlink" href="#activesupport-cache-rediscachestore"><span>2.6</span> <code>ActiveSupport::Cache::RedisCacheStore</code></a></h3><p><a href="https://edgeapi.rubyonrails.org/classes/ActiveSupport/Cache/RedisCacheStore.html"><code>ActiveSupport::Cache::RedisCacheStore</code></a> aprovecha el soporte de Redis para la expulsión automática cuando alcanza la memoria máxima, permitiéndole comportarse de manera muy similar a un servidor de caché Memcached.</p><p>Nota de implementación: Redis no expira claves por defecto, así que ten cuidado de usar un servidor Redis dedicado para caché. ¡No llenes tu servidor Redis persistente con datos de caché volátiles! Lee la <a href="https://redis.io/topics/lru-cache">guía de configuración del servidor de caché Redis</a> en detalle.</p><p>Para un servidor Redis solo para caché, configura <code>maxmemory-policy</code> en una de las variantes de allkeys. Redis 4+ soporta la expulsión menos frecuentemente usada (<code>allkeys-lfu</code>), una excelente opción predeterminada. Redis 3 y versiones anteriores deberían usar la expulsión menos recientemente usada (<code>allkeys-lru</code>).</p><p>Configura los tiempos de espera de lectura y escritura de caché relativamente bajos. Regenerar un valor en caché es a menudo más rápido que esperar más de un segundo para recuperarlo. Tanto los tiempos de espera de lectura como de escritura predeterminados son de 1 segundo, pero pueden configurarse más bajos si tu red es consistentemente de baja latencia.</p><p>Por defecto, el almacén de caché intentará reconectarse a Redis una vez si la conexión falla durante una solicitud.</p><p>Las lecturas y escrituras de caché nunca generan excepciones; simplemente devuelven <code>nil</code>, comportándose como si no hubiera nada en el caché. Para saber si tu caché está teniendo excepciones, puedes proporcionar un <code>error_handler</code> para reportar a un servicio de recopilación de excepciones. Debe aceptar tres argumentos con nombre: <code>method</code>, el método del almacén de caché que se llamó originalmente; <code>returning</code>, el valor que se devolvió al usuario, típicamente <code>nil</code>; y <code>exception</code>, la excepción que fue rescatada.</p><p>Para comenzar, agrega la gema redis a tu Gemfile:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">gem</span> <span class="s2">"redis"</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="gem "redis"
">Copy</button>
</div>
<p>Finalmente, agrega la configuración en el archivo <code>config/environments/*.rb</code> relevante:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">config</span><span class="p">.</span><span class="nf">cache_store</span> <span class="o">=</span> <span class="ss">:redis_cache_store</span><span class="p">,</span> <span class="p">{</span> <span class="ss">url: </span><span class="no">ENV</span><span class="p">[</span><span class="s2">"REDIS_URL"</span><span class="p">]</span> <span class="p">}</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="config.cache_store = :redis_cache_store, { url: ENV["REDIS_URL"] }
">Copy</button>
</div>
<p>Un almacén de caché Redis más complejo y de producción podría verse algo así:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">cache_servers</span> <span class="o">=</span> <span class="sx">%w(redis://cache-01:6379/0 redis://cache-02:6379/0)</span>
<span class="n">config</span><span class="p">.</span><span class="nf">cache_store</span> <span class="o">=</span> <span class="ss">:redis_cache_store</span><span class="p">,</span> <span class="p">{</span> <span class="ss">url: </span><span class="n">cache_servers</span><span class="p">,</span>
<span class="ss">connect_timeout: </span><span class="mi">30</span><span class="p">,</span> <span class="c1"># Predeterminado a 1 segundo</span>
<span class="ss">read_timeout: </span><span class="mf">0.2</span><span class="p">,</span> <span class="c1"># Predeterminado a 1 segundo</span>
<span class="ss">write_timeout: </span><span class="mf">0.2</span><span class="p">,</span> <span class="c1"># Predeterminado a 1 segundo</span>
<span class="ss">reconnect_attempts: </span><span class="mi">2</span><span class="p">,</span> <span class="c1"># Predeterminado a 1</span>
<span class="ss">error_handler: </span><span class="o">-></span> <span class="p">(</span><span class="nb">method</span><span class="p">:,</span> <span class="n">returning</span><span class="p">:,</span> <span class="n">exception</span><span class="p">:)</span> <span class="p">{</span>
<span class="c1"># Reportar errores a Sentry como advertencias</span>
<span class="no">Sentry</span><span class="p">.</span><span class="nf">capture_exception</span> <span class="n">exception</span><span class="p">,</span> <span class="ss">level: </span><span class="s1">'warning'</span><span class="p">,</span>
<span class="ss">tags: </span><span class="p">{</span> <span class="ss">method: </span><span class="nb">method</span><span class="p">,</span> <span class="ss">returning: </span><span class="n">returning</span> <span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="cache_servers = %w(redis://cache-01:6379/0 redis://cache-02:6379/0)
config.cache_store = :redis_cache_store, { url: cache_servers,
connect_timeout: 30, # Predeterminado a 1 segundo
read_timeout: 0.2, # Predeterminado a 1 segundo
write_timeout: 0.2, # Predeterminado a 1 segundo
reconnect_attempts: 2, # Predeterminado a 1
error_handler: -> (method:, returning:, exception:) {
# Reportar errores a Sentry como advertencias
Sentry.capture_exception exception, level: 'warning',
tags: { method: method, returning: returning }
}
}
">Copy</button>
</div>
<h3 id="activesupport-cache-nullstore"><a class="anchorlink" href="#activesupport-cache-nullstore"><span>2.7</span> <code>ActiveSupport::Cache::NullStore</code></a></h3><p><a href="https://edgeapi.rubyonrails.org/classes/ActiveSupport/Cache/NullStore.html"><code>ActiveSupport::Cache::NullStore</code></a> está limitado a cada solicitud web, y borra los valores almacenados al final de una solicitud. Está destinado a ser usado en entornos de desarrollo y prueba. Puede ser muy útil cuando tienes código que interactúa directamente con <code>Rails.cache</code> pero el caché interfiere con ver los resultados de los cambios de código.</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">config</span><span class="p">.</span><span class="nf">cache_store</span> <span class="o">=</span> <span class="ss">:null_store</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="config.cache_store = :null_store
">Copy</button>
</div>
<h3 id="almacenes-de-caché-personalizados"><a class="anchorlink" href="#almacenes-de-caché-personalizados"><span>2.8</span> Almacenes de Caché Personalizados</a></h3><p>Puedes crear tu propio almacén de caché personalizado simplemente extendiendo <code>ActiveSupport::Cache::Store</code> e implementando los métodos apropiados. De esta manera, puedes intercambiar cualquier cantidad de tecnologías de caché en tu aplicación Rails.</p><p>Para usar un almacén de caché personalizado, simplemente configura el almacén de caché a una nueva instancia de tu clase personalizada.</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">config</span><span class="p">.</span><span class="nf">cache_store</span> <span class="o">=</span> <span class="no">MyCacheStore</span><span class="p">.</span><span class="nf">new</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="config.cache_store = MyCacheStore.new
">Copy</button>
</div>
<h2 id="claves-de-caché"><a class="anchorlink" href="#claves-de-caché"><span>3</span> Claves de Caché</a></h2><p>Las claves utilizadas en un caché pueden ser cualquier objeto que responda a <code>cache_key</code> o <code>to_param</code>. Puedes implementar el método <code>cache_key</code> en tus clases si necesitas generar claves personalizadas. Active Record generará claves basadas en el nombre de la clase y el id del registro.</p><p>Puedes usar Hashes y Arrays de valores como claves de caché.</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="c1"># Esta es una clave de caché legal</span>
<span class="no">Rails</span><span class="p">.</span><span class="nf">cache</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="ss">site: </span><span class="s2">"mysite"</span><span class="p">,</span> <span class="ss">owners: </span><span class="p">[</span><span class="n">owner_1</span><span class="p">,</span> <span class="n">owner_2</span><span class="p">])</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="# Esta es una clave de caché legal
Rails.cache.read(site: "mysite", owners: [owner_1, owner_2])
">Copy</button>
</div>
<p>Las claves que uses en <code>Rails.cache</code> no serán las mismas que las utilizadas realmente con el motor de almacenamiento. Pueden ser modificadas con un namespace o alteradas para cumplir con las restricciones del backend tecnológico. Esto significa, por ejemplo, que no puedes guardar valores con <code>Rails.cache</code> y luego intentar extraerlos con la gema <code>dalli</code>. Sin embargo, tampoco necesitas preocuparte por exceder el límite de tamaño de memcached o violar las reglas de sintaxis.</p><h2 id="soporte-para-get-condicional"><a class="anchorlink" href="#soporte-para-get-condicional"><span>4</span> Soporte para GET Condicional</a></h2><p>Los GET condicionales son una característica de la especificación HTTP que proporciona una forma para que los servidores web informen a los navegadores que la respuesta a una solicitud GET no ha cambiado desde la última solicitud y se puede extraer de manera segura del caché del navegador.</p><p>Funcionan utilizando los encabezados <code>HTTP_IF_NONE_MATCH</code> y <code>HTTP_IF_MODIFIED_SINCE</code> para pasar de ida y vuelta tanto un identificador de contenido único como la marca de tiempo de cuando el contenido fue cambiado por última vez. Si el navegador realiza una solicitud donde el identificador de contenido (ETag) o la marca de tiempo de última modificación coincide con la versión del servidor, entonces el servidor solo necesita enviar una respuesta vacía con un estado no modificado.</p><p>Es responsabilidad del servidor (es decir, nuestra) buscar una marca de tiempo de última modificación y el encabezado if-none-match y determinar si enviar o no la respuesta completa. Con el soporte de GET condicional en Rails, esta es una tarea bastante fácil:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">ProductsController</span> <span class="o"><</span> <span class="no">ApplicationController</span>
<span class="k">def</span> <span class="nf">show</span>
<span class="vi">@product</span> <span class="o">=</span> <span class="no">Product</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="ss">:id</span><span class="p">])</span>
<span class="c1"># Si la solicitud es obsoleta según la marca de tiempo y el valor etag dados</span>
<span class="c1"># (es decir, necesita ser procesada nuevamente), entonces ejecuta este bloque</span>
<span class="k">if</span> <span class="n">stale?</span><span class="p">(</span><span class="ss">last_modified: </span><span class="vi">@product</span><span class="p">.</span><span class="nf">updated_at</span><span class="p">.</span><span class="nf">utc</span><span class="p">,</span> <span class="ss">etag: </span><span class="vi">@product</span><span class="p">.</span><span class="nf">cache_key_with_version</span><span class="p">)</span>
<span class="n">respond_to</span> <span class="k">do</span> <span class="o">|</span><span class="n">wants</span><span class="o">|</span>
<span class="c1"># ... procesamiento normal de la respuesta</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># Si la solicitud está fresca (es decir, no está modificada), entonces no necesitas hacer</span>
<span class="c1"># nada. El render predeterminado verifica esto usando los parámetros</span>
<span class="c1"># utilizados en la llamada anterior a stale? y enviará automáticamente un</span>
<span class="c1"># :not_modified. Así que eso es todo, has terminado.</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class ProductsController < ApplicationController
def show
@product = Product.find(params[:id])
# Si la solicitud es obsoleta según la marca de tiempo y el valor etag dados
# (es decir, necesita ser procesada nuevamente), entonces ejecuta este bloque
if stale?(last_modified: @product.updated_at.utc, etag: @product.cache_key_with_version)
respond_to do |wants|
# ... procesamiento normal de la respuesta
end
end
# Si la solicitud está fresca (es decir, no está modificada), entonces no necesitas hacer
# nada. El render predeterminado verifica esto usando los parámetros
# utilizados en la llamada anterior a stale? y enviará automáticamente un
# :not_modified. Así que eso es todo, has terminado.
end
end
">Copy</button>
</div>
<p>En lugar de un hash de opciones, también puedes simplemente pasar un modelo. Rails usará los métodos <code>updated_at</code> y <code>cache_key_with_version</code> para establecer <code>last_modified</code> y <code>etag</code>:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">ProductsController</span> <span class="o"><</span> <span class="no">ApplicationController</span>
<span class="k">def</span> <span class="nf">show</span>
<span class="vi">@product</span> <span class="o">=</span> <span class="no">Product</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="ss">:id</span><span class="p">])</span>
<span class="k">if</span> <span class="n">stale?</span><span class="p">(</span><span class="vi">@product</span><span class="p">)</span>
<span class="n">respond_to</span> <span class="k">do</span> <span class="o">|</span><span class="n">wants</span><span class="o">|</span>
<span class="c1"># ... procesamiento normal de la respuesta</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class ProductsController < ApplicationController
def show
@product = Product.find(params[:id])
if stale?(@product)
respond_to do |wants|
# ... procesamiento normal de la respuesta
end
end
end
end
">Copy</button>
</div>
<p>Si no tienes ningún procesamiento especial de respuesta y estás usando el mecanismo de renderizado predeterminado (es decir, no estás usando <code>respond_to</code> ni llamando a render tú mismo), entonces tienes un helper fácil en <code>fresh_when</code>:</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">ProductsController</span> <span class="o"><</span> <span class="no">ApplicationController</span>
<span class="c1"># Esto enviará automáticamente un :not_modified si la solicitud está fresca,</span>
<span class="c1"># y renderizará la plantilla predeterminada (product.*) si está obsoleta.</span>
<span class="k">def</span> <span class="nf">show</span>
<span class="vi">@product</span> <span class="o">=</span> <span class="no">Product</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="ss">:id</span><span class="p">])</span>
<span class="n">fresh_when</span> <span class="ss">last_modified: </span><span class="vi">@product</span><span class="p">.</span><span class="nf">published_at</span><span class="p">.</span><span class="nf">utc</span><span class="p">,</span> <span class="ss">etag: </span><span class="vi">@product</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class ProductsController < ApplicationController
# Esto enviará automáticamente un :not_modified si la solicitud está fresca,
# y renderizará la plantilla predeterminada (product.*) si está obsoleta.
def show
@product = Product.find(params[:id])
fresh_when last_modified: @product.published_at.utc, etag: @product
end
end
">Copy</button>
</div>
<p>A veces queremos almacenar en caché la respuesta, por ejemplo, una página estática, que nunca se expira. Para lograr esto, podemos usar el helper <code>http_cache_forever</code> y al hacerlo el navegador y los proxies la almacenarán en caché indefinidamente.</p><p>Por defecto, las respuestas en caché serán privadas, almacenadas solo en el navegador web del usuario. Para permitir que los proxies almacenen en caché la respuesta, configura <code>public: true</code> para indicar que pueden servir la respuesta en caché a todos los usuarios.</p><p>Usando este helper, el encabezado <code>last_modified</code> se establece en <code>Time.new(2011, 1, 1).utc</code> y el encabezado <code>expires</code> se establece en 100 años.</p><p>ADVERTENCIA: Usa este método con cuidado, ya que el navegador/proxy no podrá invalidar la respuesta en caché a menos que se borre forzosamente la caché del navegador.</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">HomeController</span> <span class="o"><</span> <span class="no">ApplicationController</span>
<span class="k">def</span> <span class="nf">index</span>
<span class="n">http_cache_forever</span><span class="p">(</span><span class="ss">public: </span><span class="kp">true</span><span class="p">)</span> <span class="k">do</span>
<span class="n">render</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class HomeController < ApplicationController
def index
http_cache_forever(public: true) do
render
end
end
end
">Copy</button>
</div>
<h3 id="etags-fuertes-vs-débiles"><a class="anchorlink" href="#etags-fuertes-vs-débiles"><span>4.1</span> ETags Fuertes vs. Débiles</a></h3><p>Rails genera ETags débiles por defecto. Los ETags débiles permiten que las respuestas semánticamente equivalentes tengan los mismos ETags, incluso si sus cuerpos no coinciden exactamente. Esto es útil cuando no queremos que la página se regenere por cambios menores en el cuerpo de la respuesta.</p><p>Los ETags débiles tienen un prefijo <code>W/</code> para diferenciarlos de los ETags fuertes.</p><div class="interstitial code">
<pre><code class="highlight plaintext">W/"618bbc92e2d35ea1945008b42799b0e7" → ETag Débil
"618bbc92e2d35ea1945008b42799b0e7" → ETag Fuerte
</code></pre>
<button class="clipboard-button" data-clipboard-text="W/"618bbc92e2d35ea1945008b42799b0e7" → ETag Débil
"618bbc92e2d35ea1945008b42799b0e7" → ETag Fuerte
">Copy</button>
</div>
<p>A diferencia del ETag débil, el ETag fuerte implica que la respuesta debe ser exactamente la misma e idéntica byte por byte. Útil cuando se realizan solicitudes de rango dentro de un archivo grande de video o PDF. Algunos CDNs solo soportan ETags fuertes, como Akamai. Si absolutamente necesitas generar un ETag fuerte, se puede hacer de la siguiente manera.</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="k">class</span> <span class="nc">ProductsController</span> <span class="o"><</span> <span class="no">ApplicationController</span>
<span class="k">def</span> <span class="nf">show</span>
<span class="vi">@product</span> <span class="o">=</span> <span class="no">Product</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="n">params</span><span class="p">[</span><span class="ss">:id</span><span class="p">])</span>
<span class="n">fresh_when</span> <span class="ss">last_modified: </span><span class="vi">@product</span><span class="p">.</span><span class="nf">published_at</span><span class="p">.</span><span class="nf">utc</span><span class="p">,</span> <span class="ss">strong_etag: </span><span class="vi">@product</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="class ProductsController < ApplicationController
def show
@product = Product.find(params[:id])
fresh_when last_modified: @product.published_at.utc, strong_etag: @product
end
end
">Copy</button>
</div>
<p>También puedes establecer el ETag fuerte directamente en la respuesta.</p><div class="interstitial code">
<pre><code class="highlight ruby"><span class="n">response</span><span class="p">.</span><span class="nf">strong_etag</span> <span class="o">=</span> <span class="n">response</span><span class="p">.</span><span class="nf">body</span> <span class="c1"># => "618bbc92e2d35ea1945008b42799b0e7"</span>
</code></pre>
<button class="clipboard-button" data-clipboard-text="response.strong_etag = response.body # => "618bbc92e2d35ea1945008b42799b0e7"
">Copy</button>
</div>
<h2 id="caché-en-desarrollo"><a class="anchorlink" href="#caché-en-desarrollo"><span>5</span> Caché en Desarrollo</a></h2><p>Por defecto, el caché está <em>habilitado</em> en modo desarrollo con
<a href="#activesupport-cache-memorystore"><code>:memory_store</code></a>.</p><p>Rails también proporciona el comando rails <code>dev:cache</code> para
activar/desactivar fácilmente el caché de Action Controller.</p><div class="interstitial code">
<pre><code class="highlight console"><span class="gp">$</span><span class="w"> </span><span class="nb">bin/rails </span>dev:cache
<span class="go">El modo de desarrollo ahora está siendo almacenado en caché.
</span><span class="gp">$</span><span class="w"> </span><span class="nb">bin/rails </span>dev:cache
<span class="go">El modo de desarrollo ya no está siendo almacenado en caché.
</span></code></pre>
<button class="clipboard-button" data-clipboard-text="bin/rails dev:cache
bin/rails dev:cache
">Copy</button>
</div>
<p>Para desactivar el caché, configura <code>cache_store</code> a <a href="#activesupport-cache-nullstore"><code>:null_store</code></a></p><h2 id="referencias"><a class="anchorlink" href="#referencias"><span>6</span> Referencias</a></h2>
<ul>
<li><a href="https://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works">Artículo de DHH sobre expiración basada en claves</a></li>
<li><a href="http://railscasts.com/episodes/387-cache-digests">Railscast de Ryan Bates sobre digests de caché</a></li>
</ul>
<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>.
</p>
<p>
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
<a href="https://edgeguides.rubyonrails.org">Guías Edge</a> primero para verificar
si los problemas ya están resueltos o no en la rama principal.
Revisa las <a href="ruby_on_rails_guides_guidelines.html">Guías de Ruby on Rails</a>
para estilo y convenciones.
</p>
<p>
Si por alguna razón detectas algo que corregir pero no puedes hacerlo tú mismo, por favor
<a href="https://github.com/rails/rails/issues">abre un issue</a>.
</p>
<p>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 <a href="https://discuss.rubyonrails.org/c/rubyonrails-docs">Foro oficial de Ruby on Rails</a>.
</p>
</div>
</div>
</main>
<hr class="hide" />
<footer id="page_footer">
<div class="wrapper">
<p>Este trabajo está bajo una <a href="https://creativecommons.org/licenses/by-sa/4.0/">Licencia Creative Commons Atribución-CompartirIgual 4.0 Internacional</a></p>
<p>"Rails", "Ruby on Rails" y el logotipo de Rails son marcas registradas de David Heinemeier Hansson. Todos los derechos reservados.</p>
<p> Esta traducción fue generada por openAi e <a href="http://latinadeveloper.com/">Isis Harris.</a></p>
</div>
</footer>
</body>
</html>